diff --git a/Source/Core/Common/Assembler/AssemblerShared.cpp b/Source/Core/Common/Assembler/AssemblerShared.cpp new file mode 100644 index 0000000000..77d7e737f9 --- /dev/null +++ b/Source/Core/Common/Assembler/AssemblerShared.cpp @@ -0,0 +1,26 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Common/Assembler/AssemblerShared.h" + +#include + +namespace Common::GekkoAssembler +{ +std::string AssemblerError::FormatError() const +{ + const char* space_char = col == 0 ? "" : " "; + + std::string_view line_str = error_line; + if (line_str.back() == '\n') + { + line_str = line_str.substr(0, line_str.length() - 1); + } + + return fmt::format("Error on line {0} col {1}:\n" + " {2}\n" + " {3:{4}}{5:^^{6}}\n" + "{7}", + line + 1, col + 1, line_str, space_char, col, '^', len, message); +} +} // namespace Common::GekkoAssembler diff --git a/Source/Core/Common/Assembler/AssemblerShared.h b/Source/Core/Common/Assembler/AssemblerShared.h new file mode 100644 index 0000000000..23bf5be0ce --- /dev/null +++ b/Source/Core/Common/Assembler/AssemblerShared.h @@ -0,0 +1,545 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +namespace Common::GekkoAssembler +{ +struct Interval +{ + size_t begin; + size_t len; + constexpr size_t End() const { return begin + len; } +}; + +struct AssemblerError +{ + std::string message; + std::string_view error_line; + size_t line; + size_t col; + size_t len; + + std::string FormatError() const; +}; + +template +using Tagged = std::pair; +template +constexpr const Tag& TagOf(const Tagged& val) +{ + return std::get<0>(val); +} +template +constexpr Tag& TagOf(Tagged& val) +{ + return std::get<0>(val); +} +template +constexpr const T& ValueOf(const Tagged& val) +{ + return std::get<1>(val); +} +template +constexpr T& ValueOf(Tagged& val) +{ + return std::get<1>(val); +} + +template +using FailureOr = std::variant; +template +constexpr bool IsFailure(const FailureOr& var) +{ + return std::holds_alternative(var); +} +template +constexpr AssemblerError& GetFailure(FailureOr& var) +{ + return std::get(var); +} +template +constexpr const AssemblerError& GetFailure(const FailureOr& var) +{ + return std::get(var); +} +template +constexpr const T& GetT(const FailureOr& var) +{ + return std::get(var); +} +template +constexpr T& GetT(FailureOr& var) +{ + return std::get(var); +} + +enum class GekkoDirective +{ + Byte, + _2byte, + _4byte, + _8byte, + Float, + Double, + Locate, + PadAlign, + Align, + Zeros, + Skip, + DefVar, + Ascii, + Asciz +}; + +enum class GekkoMnemonic : size_t +{ + Add, + Addc, + Adde, + Addi, + Addic, + AddicDot, + Addis, + Addme, + Addze, + Divw, + Divwu, + Mulhw, + Mulhwu, + Mulli, + Mullw, + Neg, + Subf, + Subfc, + Subfe, + Subfic, + Subfme, + Subfze, + Cmp, + Cmpi, + Cmpl, + Cmpli, + And, + Andc, + AndiDot, + AndisDot, + Cntlzw, + Eqv, + Extsb, + Extsh, + Nand, + Nor, + Or, + Orc, + Ori, + Oris, + Xor, + Xori, + Xoris, + Rlwimi, + Rlwinm, + Rlwnm, + Slw, + Sraw, + Srawi, + Srw, + Fadd, + Fadds, + Fdiv, + Fdivs, + Fmul, + Fmuls, + Fres, + Frsqrte, + Fsub, + Fsubs, + Fsel, + Fmadd, + Fmadds, + Fmsub, + Fmsubs, + Fnmadd, + Fnmadds, + Fnmsub, + Fnmsubs, + Fctiw, + Fctiwz, + Frsp, + Fcmpo, + Fcmpu, + Mcrfs, + Mffs, + Mtfsb0, + Mtfsb1, + Mtfsf, + Mtfsfi, + Lbz, + Lbzu, + Lbzux, + Lbzx, + Lha, + Lhau, + Lhaux, + Lhax, + Lhz, + Lhzu, + Lhzux, + Lhzx, + Lwz, + Lwzu, + Lwzux, + Lwzx, + Stb, + Stbu, + Stbux, + Stbx, + Sth, + Sthu, + Sthux, + Sthx, + Stw, + Stwu, + Stwux, + Stwx, + Lhbrx, + Lwbrx, + Sthbrx, + Stwbrx, + Lmw, + Stmw, + Lswi, + Lswx, + Stswi, + Stswx, + Eieio, + Isync, + Lwarx, + StwcxDot, + Sync, + Lfd, + Lfdu, + Lfdux, + Lfdx, + Lfs, + Lfsu, + Lfsux, + Lfsx, + Stfd, + Stfdu, + Stfdux, + Stfdx, + Stfiwx, + Stfs, + Stfsu, + Stfsux, + Stfsx, + Fabs, + Fmr, + Fnabs, + Fneg, + B, + Bc, + Bcctr, + Bclr, + Crand, + Crandc, + Creqv, + Crnand, + Crnor, + Cror, + Crorc, + Crxor, + Mcrf, + Rfi, + Sc, + Tw, + Twi, + Mcrxr, + Mfcr, + Mfmsr, + Mfspr_nobitswap, + Mftb_nobitswap, + Mtcrf, + Mtmsr, + Mtspr_nobitswap, + Dcbf, + Dcbi, + Dcbst, + Dcbt, + Dcbtst, + Dcbz, + Icbi, + Mfsr, + Mfsrin, + Mtsr, + Mtsrin, + Tlbie, + Tlbsync, + Eciwx, + Ecowx, + Psq_lx, + Psq_stx, + Psq_lux, + Psq_stux, + Psq_l, + Psq_lu, + Psq_st, + Psq_stu, + Ps_div, + Ps_sub, + Ps_add, + Ps_sel, + Ps_res, + Ps_mul, + Ps_rsqrte, + Ps_msub, + Ps_madd, + Ps_nmsub, + Ps_nmadd, + Ps_neg, + Ps_mr, + Ps_nabs, + Ps_abs, + Ps_sum0, + Ps_sum1, + Ps_muls0, + Ps_muls1, + Ps_madds0, + Ps_madds1, + Ps_cmpu0, + Ps_cmpo0, + Ps_cmpu1, + Ps_cmpo1, + Ps_merge00, + Ps_merge01, + Ps_merge10, + Ps_merge11, + Dcbz_l, + LastMnemonic = Dcbz_l, + InvalidMnemonic, +}; + +enum class ExtendedGekkoMnemonic : size_t +{ + Subi, + Subis, + Subic, + SubicDot, + Sub, + Subc, + Cmpwi, + Cmpw, + Cmplwi, + Cmplw, + Extlwi, + Extrwi, + Inslwi, + Insrwi, + Rotlwi, + Rotrwi, + Rotlw, + Slwi, + Srwi, + Clrlwi, + Clrrwi, + Clrlslwi, + Bt, + Bf, + Bdnz, + Bdnzt, + Bdnzf, + Bdz, + Bdzt, + Bdzf, + BtPredict, + BfPredict, + BdnzPredict, + BdnztPredict, + BdnzfPredict, + BdzPredict, + BdztPredict, + BdzfPredict, + Blr, + Btlr, + Bflr, + Bdnzlr, + Bdnztlr, + Bdnzflr, + Bdzlr, + Bdztlr, + Bdzflr, + BtlrPredict, + BflrPredict, + BdnzlrPredict, + BdnztlrPredict, + BdnzflrPredict, + BdzlrPredict, + BdztlrPredict, + BdzflrPredict, + Bctr, + Btctr, + Bfctr, + BtctrPredict, + BfctrPredict, + Blt, + Ble, + Beq, + Bge, + Bgt, + Bnl, + Bne, + Bng, + Bso, + Bns, + Bun, + Bnu, + BltPredict, + BlePredict, + BeqPredict, + BgePredict, + BgtPredict, + BnlPredict, + BnePredict, + BngPredict, + BsoPredict, + BnsPredict, + BunPredict, + BnuPredict, + Bltlr, + Blelr, + Beqlr, + Bgelr, + Bgtlr, + Bnllr, + Bnelr, + Bnglr, + Bsolr, + Bnslr, + Bunlr, + Bnulr, + BltlrPredict, + BlelrPredict, + BeqlrPredict, + BgelrPredict, + BgtlrPredict, + BnllrPredict, + BnelrPredict, + BnglrPredict, + BsolrPredict, + BnslrPredict, + BunlrPredict, + BnulrPredict, + Bltctr, + Blectr, + Beqctr, + Bgectr, + Bgtctr, + Bnlctr, + Bnectr, + Bngctr, + Bsoctr, + Bnsctr, + Bunctr, + Bnuctr, + BltctrPredict, + BlectrPredict, + BeqctrPredict, + BgectrPredict, + BgtctrPredict, + BnlctrPredict, + BnectrPredict, + BngctrPredict, + BsoctrPredict, + BnsctrPredict, + BunctrPredict, + BnuctrPredict, + Crset, + Crclr, + Crmove, + Crnot, + Twlt, + Twlti, + Twle, + Twlei, + Tweq, + Tweqi, + Twge, + Twgei, + Twgt, + Twgti, + Twnl, + Twnli, + Twne, + Twnei, + Twng, + Twngi, + Twllt, + Twllti, + Twlle, + Twllei, + Twlge, + Twlgei, + Twlgt, + Twlgti, + Twlnl, + Twlnli, + Twlng, + Twlngi, + Trap, + Mtxer, + Mfxer, + Mtlr, + Mflr, + Mtctr, + Mfctr, + Mtdsisr, + Mfdsisr, + Mtdar, + Mfdar, + Mtdec, + Mfdec, + Mtsdr1, + Mfsdr1, + Mtsrr0, + Mfsrr0, + Mtsrr1, + Mfsrr1, + Mtasr, + Mfasr, + Mtear, + Mfear, + Mttbl, + Mftbl, + Mttbu, + Mftbu, + Mtsprg, + Mfsprg, + Mtibatu, + Mfibatu, + Mtibatl, + Mfibatl, + Mtdbatu, + Mfdbatu, + Mtdbatl, + Mfdbatl, + Nop, + Li, + Lis, + La, + Mr, + Not, + Mtcr, + Mfspr, + Mftb, + Mtspr, + LastMnemonic = Mtspr, + InvalidMnemonic +}; +} // namespace Common::GekkoAssembler diff --git a/Source/Core/Common/Assembler/AssemblerTables.cpp b/Source/Core/Common/Assembler/AssemblerTables.cpp new file mode 100644 index 0000000000..ba6c8ad5d4 --- /dev/null +++ b/Source/Core/Common/Assembler/AssemblerTables.cpp @@ -0,0 +1,1482 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Common/Assembler/AssemblerTables.h" + +#include "Common/Assembler/AssemblerShared.h" +#include "Common/Assembler/CaseInsensitiveDict.h" +#include "Common/CommonTypes.h" + +namespace Common::GekkoAssembler::detail +{ +namespace +{ +constexpr size_t PLAIN_MNEMONIC = 0x0; +constexpr size_t RECORD_BIT = 0x1; +constexpr size_t OVERFLOW_EXCEPTION = 0x2; +// Since RC/OE are mutually exclusive from LK/AA, they can occupy the same slot +constexpr size_t LINK_BIT = 0x1; +constexpr size_t ABSOLUTE_ADDRESS_BIT = 0x2; + +// Compile-time helpers for mnemonic generation +// Generate inclusive mask [left, right] -- MSB=0 LSB=31 +constexpr u32 Mask(u32 left, u32 right) +{ + return static_cast(((u64{1} << (32 - left)) - 1) & ~((u64{1} << (31 - right)) - 1)); +} +constexpr u32 InsertVal(u32 val, u32 left, u32 right) +{ + return val << (31 - right) & Mask(left, right); +} +constexpr u32 InsertOpcode(u32 opcode) +{ + return InsertVal(opcode, 0, 5); +} +constexpr u32 SprBitswap(u32 spr) +{ + return ((spr & 0b0000011111) << 5) | ((spr & 0b1111100000) >> 5); +} + +constexpr MnemonicDesc INVALID_MNEMONIC = {0, 0, {}}; +constexpr ExtendedMnemonicDesc INVALID_EXT_MNEMONIC = {0, nullptr}; + +// All operands as referenced by the Gekko/Broadway user manual +// See section 12.1.2 under Chapter 12 +constexpr OperandDesc _A = OperandDesc{Mask(11, 15), {16, false}}; +constexpr OperandDesc _B = OperandDesc{Mask(16, 20), {11, false}}; +constexpr OperandDesc _BD = OperandDesc{Mask(16, 29), {0, true}}; +constexpr OperandDesc _BI = OperandDesc{Mask(11, 15), {16, false}}; +constexpr OperandDesc _BO = OperandDesc{Mask(6, 10), {21, false}}; +constexpr OperandDesc _C = OperandDesc{Mask(21, 25), {6, false}}; +constexpr OperandDesc _Crba = OperandDesc{Mask(11, 15), {16, false}}; +constexpr OperandDesc _Crbb = OperandDesc{Mask(16, 20), {11, false}}; +constexpr OperandDesc _Crbd = OperandDesc{Mask(6, 10), {21, false}}; +constexpr OperandDesc _Crfd = OperandDesc{Mask(6, 8), {23, false}}; +constexpr OperandDesc _Crfs = OperandDesc{Mask(11, 13), {18, false}}; +constexpr OperandDesc _CRM = OperandDesc{Mask(12, 19), {12, false}}; +constexpr OperandDesc _D = OperandDesc{Mask(6, 10), {21, false}}; +constexpr OperandDesc _FM = OperandDesc{Mask(7, 14), {17, false}}; +constexpr OperandDesc _W1 = OperandDesc{Mask(16, 16), {15, false}}; +constexpr OperandDesc _W2 = OperandDesc{Mask(21, 21), {10, false}}; +constexpr OperandDesc _IMM = OperandDesc{Mask(16, 19), {12, false}}; +constexpr OperandDesc _L = OperandDesc{Mask(10, 10), {21, false}}; +constexpr OperandDesc _LI = OperandDesc{Mask(6, 29), {0, true}}; +constexpr OperandDesc _MB = OperandDesc{Mask(21, 25), {6, false}}; +constexpr OperandDesc _ME = OperandDesc{Mask(26, 30), {1, false}}; +constexpr OperandDesc _NB = OperandDesc{Mask(16, 20), {11, false}}; +constexpr OperandDesc _Offd = OperandDesc{Mask(16, 31), {0, true}}; +constexpr OperandDesc _OffdPs = OperandDesc{Mask(20, 31), {0, true}}; +constexpr OperandDesc _S = OperandDesc{Mask(6, 10), {21, false}}; +constexpr OperandDesc _SH = OperandDesc{Mask(16, 20), {11, false}}; +constexpr OperandDesc _SIMM = OperandDesc{Mask(16, 31), {0, true}}; +constexpr OperandDesc _SPR = OperandDesc{Mask(11, 20), {11, false}}; +constexpr OperandDesc _SR = OperandDesc{Mask(12, 15), {16, false}}; +constexpr OperandDesc _TO = OperandDesc{Mask(6, 10), {21, false}}; +constexpr OperandDesc _TPR = OperandDesc{Mask(11, 20), {11, false}}; +constexpr OperandDesc _UIMM = OperandDesc{Mask(16, 31), {0, false}}; +constexpr OperandDesc _I1 = OperandDesc{Mask(17, 19), {12, false}}; +constexpr OperandDesc _I2 = OperandDesc{Mask(22, 24), {7, false}}; +} // namespace + +void OperandList::Insert(size_t before, u32 val) +{ + overfill = count == MAX_OPERANDS; + for (size_t i = before + 1; i <= count && i < MAX_OPERANDS; i++) + { + std::swap(list[i], list[before]); + } + + list[before] = Tagged({0, 0}, val); + if (!overfill) + { + count++; + } +} + +// OperandDesc holds the shift position for an operand, as well as the mask +// Whether the user provided a valid input for an operand can be determined by the mask +u32 OperandDesc::MaxVal() const +{ + const u32 mask_sh = mask >> shift; + if (is_signed) + { + const u32 mask_hibit = (mask_sh & (mask_sh ^ (mask_sh >> 1))); + return mask_hibit - 1; + } + return mask_sh; +} + +u32 OperandDesc::MinVal() const +{ + if (is_signed) + { + return ~MaxVal(); + } + return 0; +} + +u32 OperandDesc::TruncBits() const +{ + const u32 mask_sh = mask >> shift; + const u32 mask_lobit = mask_sh & (mask_sh ^ (mask_sh << 1)); + return mask_lobit - 1; +} + +bool OperandDesc::Fits(u32 val) const +{ + const u32 mask_sh = mask >> shift; + if (is_signed) + { + // Get high bit and low bit from a range mask + const u32 mask_hibit = mask_sh & (mask_sh ^ (mask_sh >> 1)); + const u32 mask_lobit = mask_sh & (mask_sh ^ (mask_sh << 1)); + // Positive max is (signbit - 1) + // Negative min is ~(Positive Max) + const u32 positive_max = mask_hibit - 1; + const u32 negative_max = ~positive_max; + // Truncated bits are any bits right of the mask that are 0 after shifting + const u32 truncate_bits = mask_lobit - 1; + return (val <= positive_max || val >= negative_max) && !(val & truncate_bits); + } + return (mask_sh & val) == val; +} + +u32 OperandDesc::Fit(u32 val) const +{ + return (val << shift) & mask; +} + +/////////////////// +// PARSER TABLES // +/////////////////// + +extern const CaseInsensitiveDict sprg_map = { + {"xer", 1}, {"lr", 8}, {"ctr", 9}, {"dsisr", 18}, {"dar", 19}, + {"dec", 22}, {"sdr1", 25}, {"srr0", 26}, {"srr1", 27}, {"sprg0", 272}, + {"sprg1", 273}, {"sprg2", 274}, {"sprg3", 275}, {"ear", 282}, {"tbl", 284}, + {"tbu", 285}, {"ibat0u", 528}, {"ibat0l", 529}, {"ibat1u", 530}, {"ibat1l", 531}, + {"ibat2u", 532}, {"ibat2l", 533}, {"ibat3u", 534}, {"ibat3l", 535}, {"dbat0u", 536}, + {"dbat0l", 537}, {"dbat1u", 538}, {"dbat1l", 539}, {"dbat2u", 540}, {"dbat2l", 541}, + {"dbat3u", 542}, {"dbat3l", 543}, {"gqr0", 912}, {"gqr1", 913}, {"gqr2", 914}, + {"gqr3", 915}, {"gqr4", 916}, {"gqr5", 917}, {"gqr6", 918}, {"gqr7", 919}, + {"hid2", 920}, {"wpar", 921}, {"dma_u", 922}, {"dma_l", 923}, {"ummcr0", 936}, + {"upmc1", 937}, {"upmc2", 938}, {"usia", 939}, {"ummcr1", 940}, {"upmc3", 941}, + {"upmc4", 942}, {"usda", 943}, {"mmcr0", 952}, {"pmc1", 953}, {"pmc2", 954}, + {"sia", 955}, {"mmcr1", 956}, {"pmc3", 957}, {"pmc4", 958}, {"sda", 959}, + {"hid0", 1008}, {"hid1", 1009}, {"iabr", 1010}, {"dabr", 1013}, {"l2cr", 1017}, + {"ictc", 1019}, {"thrm1", 1020}, {"thrm2", 1021}, {"thrm3", 1022}}; + +extern const CaseInsensitiveDict directives_map = { + {"byte", GekkoDirective::Byte}, {"2byte", GekkoDirective::_2byte}, + {"4byte", GekkoDirective::_4byte}, {"8byte", GekkoDirective::_8byte}, + {"float", GekkoDirective::Float}, {"double", GekkoDirective::Double}, + {"locate", GekkoDirective::Locate}, {"padalign", GekkoDirective::PadAlign}, + {"align", GekkoDirective::Align}, {"zeros", GekkoDirective::Zeros}, + {"skip", GekkoDirective::Skip}, {"defvar", GekkoDirective::DefVar}, + {"ascii", GekkoDirective::Ascii}, {"asciz", GekkoDirective::Asciz}, +}; + +#define MNEMONIC(mnemonic_str, mnemonic_enum, variant_bits, alg) \ + { \ + mnemonic_str, \ + { \ + static_cast(mnemonic_enum) * VARIANT_PERMUTATIONS + (variant_bits), alg \ + } \ + } +#define PLAIN_MNEMONIC(mnemonic_str, mnemonic_enum, alg) \ + MNEMONIC(mnemonic_str, mnemonic_enum, PLAIN_MNEMONIC, alg) +#define RC_MNEMONIC(mnemonic_str, mnemonic_enum, alg) \ + MNEMONIC(mnemonic_str, mnemonic_enum, PLAIN_MNEMONIC, alg), \ + MNEMONIC(mnemonic_str ".", mnemonic_enum, RECORD_BIT, alg) +#define OERC_MNEMONIC(mnemonic_str, mnemonic_enum, alg) \ + MNEMONIC(mnemonic_str, mnemonic_enum, PLAIN_MNEMONIC, alg), \ + MNEMONIC(mnemonic_str ".", mnemonic_enum, RECORD_BIT, alg), \ + MNEMONIC(mnemonic_str "o", mnemonic_enum, OVERFLOW_EXCEPTION, alg), \ + MNEMONIC(mnemonic_str "o.", mnemonic_enum, (RECORD_BIT | OVERFLOW_EXCEPTION), alg) +#define LK_MNEMONIC(mnemonic_str, mnemonic_enum, alg) \ + MNEMONIC(mnemonic_str, mnemonic_enum, PLAIN_MNEMONIC, alg), \ + MNEMONIC(mnemonic_str "l", mnemonic_enum, LINK_BIT, alg) +#define AALK_MNEMONIC(mnemonic_str, mnemonic_enum, alg) \ + MNEMONIC(mnemonic_str, mnemonic_enum, PLAIN_MNEMONIC, alg), \ + MNEMONIC(mnemonic_str "l", mnemonic_enum, LINK_BIT, alg), \ + MNEMONIC(mnemonic_str "a", mnemonic_enum, ABSOLUTE_ADDRESS_BIT, alg), \ + MNEMONIC(mnemonic_str "la", mnemonic_enum, (LINK_BIT | ABSOLUTE_ADDRESS_BIT), alg) + +extern const CaseInsensitiveDict mnemonic_tokens = { + OERC_MNEMONIC("add", GekkoMnemonic::Add, ParseAlg::Op3), + OERC_MNEMONIC("addc", GekkoMnemonic::Addc, ParseAlg::Op3), + OERC_MNEMONIC("adde", GekkoMnemonic::Adde, ParseAlg::Op3), + PLAIN_MNEMONIC("addi", GekkoMnemonic::Addi, ParseAlg::Op3), + PLAIN_MNEMONIC("addic", GekkoMnemonic::Addic, ParseAlg::Op3), + PLAIN_MNEMONIC("addic.", GekkoMnemonic::AddicDot, ParseAlg::Op3), + PLAIN_MNEMONIC("addis", GekkoMnemonic::Addis, ParseAlg::Op3), + OERC_MNEMONIC("addme", GekkoMnemonic::Addme, ParseAlg::Op2), + OERC_MNEMONIC("addze", GekkoMnemonic::Addze, ParseAlg::Op2), + RC_MNEMONIC("and", GekkoMnemonic::And, ParseAlg::Op3), + RC_MNEMONIC("andc", GekkoMnemonic::Andc, ParseAlg::Op3), + PLAIN_MNEMONIC("andi.", GekkoMnemonic::AndiDot, ParseAlg::Op3), + PLAIN_MNEMONIC("andis.", GekkoMnemonic::AndisDot, ParseAlg::Op3), + AALK_MNEMONIC("b", GekkoMnemonic::B, ParseAlg::Op1), + AALK_MNEMONIC("bc", GekkoMnemonic::Bc, ParseAlg::Op3), + LK_MNEMONIC("bcctr", GekkoMnemonic::Bcctr, ParseAlg::Op2), + LK_MNEMONIC("bclr", GekkoMnemonic::Bclr, ParseAlg::Op2), + PLAIN_MNEMONIC("cmp", GekkoMnemonic::Cmp, ParseAlg::Op4), + PLAIN_MNEMONIC("cmpi", GekkoMnemonic::Cmpi, ParseAlg::Op4), + PLAIN_MNEMONIC("cmpl", GekkoMnemonic::Cmpl, ParseAlg::Op4), + PLAIN_MNEMONIC("cmpli", GekkoMnemonic::Cmpli, ParseAlg::Op4), + RC_MNEMONIC("cntlzw", GekkoMnemonic::Cntlzw, ParseAlg::Op2), + PLAIN_MNEMONIC("crand", GekkoMnemonic::Crand, ParseAlg::Op3), + PLAIN_MNEMONIC("crandc", GekkoMnemonic::Crandc, ParseAlg::Op3), + PLAIN_MNEMONIC("creqv", GekkoMnemonic::Creqv, ParseAlg::Op3), + PLAIN_MNEMONIC("crnand", GekkoMnemonic::Crnand, ParseAlg::Op3), + PLAIN_MNEMONIC("crnor", GekkoMnemonic::Crnor, ParseAlg::Op3), + PLAIN_MNEMONIC("cror", GekkoMnemonic::Cror, ParseAlg::Op3), + PLAIN_MNEMONIC("crorc", GekkoMnemonic::Crorc, ParseAlg::Op3), + PLAIN_MNEMONIC("crxor", GekkoMnemonic::Crxor, ParseAlg::Op3), + PLAIN_MNEMONIC("dcbf", GekkoMnemonic::Dcbf, ParseAlg::Op2), + PLAIN_MNEMONIC("dcbi", GekkoMnemonic::Dcbi, ParseAlg::Op2), + PLAIN_MNEMONIC("dcbst", GekkoMnemonic::Dcbst, ParseAlg::Op2), + PLAIN_MNEMONIC("dcbt", GekkoMnemonic::Dcbt, ParseAlg::Op2), + PLAIN_MNEMONIC("dcbtst", GekkoMnemonic::Dcbtst, ParseAlg::Op2), + PLAIN_MNEMONIC("dcbz", GekkoMnemonic::Dcbz, ParseAlg::Op2), + PLAIN_MNEMONIC("dcbz_l", GekkoMnemonic::Dcbz_l, ParseAlg::Op2), + OERC_MNEMONIC("divw", GekkoMnemonic::Divw, ParseAlg::Op3), + OERC_MNEMONIC("divwu", GekkoMnemonic::Divwu, ParseAlg::Op3), + PLAIN_MNEMONIC("eciwx", GekkoMnemonic::Eciwx, ParseAlg::Op3), + PLAIN_MNEMONIC("ecowx", GekkoMnemonic::Ecowx, ParseAlg::Op3), + PLAIN_MNEMONIC("eieio", GekkoMnemonic::Eieio, ParseAlg::None), + RC_MNEMONIC("eqv", GekkoMnemonic::Eqv, ParseAlg::Op3), + RC_MNEMONIC("extsb", GekkoMnemonic::Extsb, ParseAlg::Op2), + RC_MNEMONIC("extsh", GekkoMnemonic::Extsh, ParseAlg::Op2), + RC_MNEMONIC("fabs", GekkoMnemonic::Fabs, ParseAlg::Op2), + RC_MNEMONIC("fadd", GekkoMnemonic::Fadd, ParseAlg::Op3), + RC_MNEMONIC("fadds", GekkoMnemonic::Fadds, ParseAlg::Op3), + PLAIN_MNEMONIC("fcmpo", GekkoMnemonic::Fcmpo, ParseAlg::Op3), + PLAIN_MNEMONIC("fcmpu", GekkoMnemonic::Fcmpu, ParseAlg::Op3), + RC_MNEMONIC("fctiw", GekkoMnemonic::Fctiw, ParseAlg::Op2), + RC_MNEMONIC("fctiwz", GekkoMnemonic::Fctiwz, ParseAlg::Op2), + RC_MNEMONIC("fdiv", GekkoMnemonic::Fdiv, ParseAlg::Op3), + RC_MNEMONIC("fdivs", GekkoMnemonic::Fdivs, ParseAlg::Op3), + RC_MNEMONIC("fmadd", GekkoMnemonic::Fmadd, ParseAlg::Op4), + RC_MNEMONIC("fmadds", GekkoMnemonic::Fmadds, ParseAlg::Op4), + RC_MNEMONIC("fmr", GekkoMnemonic::Fmr, ParseAlg::Op2), + RC_MNEMONIC("fmsub", GekkoMnemonic::Fmsub, ParseAlg::Op4), + RC_MNEMONIC("fmsubs", GekkoMnemonic::Fmsubs, ParseAlg::Op4), + RC_MNEMONIC("fmul", GekkoMnemonic::Fmul, ParseAlg::Op3), + RC_MNEMONIC("fmuls", GekkoMnemonic::Fmuls, ParseAlg::Op3), + RC_MNEMONIC("fnabs", GekkoMnemonic::Fnabs, ParseAlg::Op2), + RC_MNEMONIC("fneg", GekkoMnemonic::Fneg, ParseAlg::Op2), + RC_MNEMONIC("fnmadd", GekkoMnemonic::Fnmadd, ParseAlg::Op4), + RC_MNEMONIC("fnmadds", GekkoMnemonic::Fnmadds, ParseAlg::Op4), + RC_MNEMONIC("fnmsub", GekkoMnemonic::Fnmsub, ParseAlg::Op4), + RC_MNEMONIC("fnmsubs", GekkoMnemonic::Fnmsubs, ParseAlg::Op4), + RC_MNEMONIC("fres", GekkoMnemonic::Fres, ParseAlg::Op2), + RC_MNEMONIC("frsp", GekkoMnemonic::Frsp, ParseAlg::Op2), + RC_MNEMONIC("frsqrte", GekkoMnemonic::Frsqrte, ParseAlg::Op2), + RC_MNEMONIC("fsel", GekkoMnemonic::Fsel, ParseAlg::Op4), + RC_MNEMONIC("fsub", GekkoMnemonic::Fsub, ParseAlg::Op3), + RC_MNEMONIC("fsubs", GekkoMnemonic::Fsubs, ParseAlg::Op3), + PLAIN_MNEMONIC("icbi", GekkoMnemonic::Icbi, ParseAlg::Op2), + PLAIN_MNEMONIC("isync", GekkoMnemonic::Isync, ParseAlg::None), + PLAIN_MNEMONIC("lbz", GekkoMnemonic::Lbz, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lbzu", GekkoMnemonic::Lbzu, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lbzux", GekkoMnemonic::Lbzux, ParseAlg::Op3), + PLAIN_MNEMONIC("lbzx", GekkoMnemonic::Lbzx, ParseAlg::Op3), + PLAIN_MNEMONIC("lfd", GekkoMnemonic::Lfd, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lfdu", GekkoMnemonic::Lfdu, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lfdux", GekkoMnemonic::Lfdux, ParseAlg::Op3), + PLAIN_MNEMONIC("lfdx", GekkoMnemonic::Lfdx, ParseAlg::Op3), + PLAIN_MNEMONIC("lfs", GekkoMnemonic::Lfs, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lfsu", GekkoMnemonic::Lfsu, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lfsux", GekkoMnemonic::Lfsux, ParseAlg::Op3), + PLAIN_MNEMONIC("lfsx", GekkoMnemonic::Lfsx, ParseAlg::Op3), + PLAIN_MNEMONIC("lha", GekkoMnemonic::Lha, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lhau", GekkoMnemonic::Lhau, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lhaux", GekkoMnemonic::Lhaux, ParseAlg::Op3), + PLAIN_MNEMONIC("lhax", GekkoMnemonic::Lhax, ParseAlg::Op3), + PLAIN_MNEMONIC("lhbrx", GekkoMnemonic::Lhbrx, ParseAlg::Op3), + PLAIN_MNEMONIC("lhz", GekkoMnemonic::Lhz, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lhzu", GekkoMnemonic::Lhzu, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lhzux", GekkoMnemonic::Lhzux, ParseAlg::Op3), + PLAIN_MNEMONIC("lhzx", GekkoMnemonic::Lhzx, ParseAlg::Op3), + PLAIN_MNEMONIC("lmw", GekkoMnemonic::Lmw, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lswi", GekkoMnemonic::Lswi, ParseAlg::Op3), + PLAIN_MNEMONIC("lswx", GekkoMnemonic::Lswx, ParseAlg::Op3), + PLAIN_MNEMONIC("lwarx", GekkoMnemonic::Lwarx, ParseAlg::Op3), + PLAIN_MNEMONIC("lwbrx", GekkoMnemonic::Lwbrx, ParseAlg::Op3), + PLAIN_MNEMONIC("lwz", GekkoMnemonic::Lwz, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lwzu", GekkoMnemonic::Lwzu, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("lwzux", GekkoMnemonic::Lwzux, ParseAlg::Op3), + PLAIN_MNEMONIC("lwzx", GekkoMnemonic::Lwzx, ParseAlg::Op3), + PLAIN_MNEMONIC("mcrf", GekkoMnemonic::Mcrf, ParseAlg::Op2), + PLAIN_MNEMONIC("mcrfs", GekkoMnemonic::Mcrfs, ParseAlg::Op2), + PLAIN_MNEMONIC("mcrxr", GekkoMnemonic::Mcrxr, ParseAlg::Op1), + PLAIN_MNEMONIC("mfcr", GekkoMnemonic::Mfcr, ParseAlg::Op1), + RC_MNEMONIC("mffs", GekkoMnemonic::Mffs, ParseAlg::Op1), + PLAIN_MNEMONIC("mfmsr", GekkoMnemonic::Mfmsr, ParseAlg::Op1), + PLAIN_MNEMONIC("mfspr_nobitswap", GekkoMnemonic::Mfspr_nobitswap, ParseAlg::Op2), + PLAIN_MNEMONIC("mfsr", GekkoMnemonic::Mfsr, ParseAlg::Op2), + PLAIN_MNEMONIC("mfsrin", GekkoMnemonic::Mfsrin, ParseAlg::Op2), + PLAIN_MNEMONIC("mftb_nobitswap", GekkoMnemonic::Mftb_nobitswap, ParseAlg::Op2), + PLAIN_MNEMONIC("mtcrf", GekkoMnemonic::Mtcrf, ParseAlg::Op2), + RC_MNEMONIC("mtfsb0", GekkoMnemonic::Mtfsb0, ParseAlg::Op1), + RC_MNEMONIC("mtfsb1", GekkoMnemonic::Mtfsb1, ParseAlg::Op1), + RC_MNEMONIC("mtfsf", GekkoMnemonic::Mtfsf, ParseAlg::Op2), + RC_MNEMONIC("mtfsfi", GekkoMnemonic::Mtfsfi, ParseAlg::Op2), + PLAIN_MNEMONIC("mtmsr", GekkoMnemonic::Mtmsr, ParseAlg::Op1), + PLAIN_MNEMONIC("mtspr_nobitswap", GekkoMnemonic::Mtspr_nobitswap, ParseAlg::Op2), + PLAIN_MNEMONIC("mtsr", GekkoMnemonic::Mtsr, ParseAlg::Op2), + PLAIN_MNEMONIC("mtsrin", GekkoMnemonic::Mtsrin, ParseAlg::Op2), + RC_MNEMONIC("mulhw", GekkoMnemonic::Mulhw, ParseAlg::Op3), + RC_MNEMONIC("mulhwu", GekkoMnemonic::Mulhwu, ParseAlg::Op3), + PLAIN_MNEMONIC("mulli", GekkoMnemonic::Mulli, ParseAlg::Op3), + OERC_MNEMONIC("mullw", GekkoMnemonic::Mullw, ParseAlg::Op3), + RC_MNEMONIC("nand", GekkoMnemonic::Nand, ParseAlg::Op3), + OERC_MNEMONIC("neg", GekkoMnemonic::Neg, ParseAlg::Op2), + RC_MNEMONIC("nor", GekkoMnemonic::Nor, ParseAlg::Op3), + RC_MNEMONIC("or", GekkoMnemonic::Or, ParseAlg::Op3), + RC_MNEMONIC("orc", GekkoMnemonic::Orc, ParseAlg::Op3), + PLAIN_MNEMONIC("ori", GekkoMnemonic::Ori, ParseAlg::Op3), + PLAIN_MNEMONIC("oris", GekkoMnemonic::Oris, ParseAlg::Op3), + PLAIN_MNEMONIC("psq_l", GekkoMnemonic::Psq_l, ParseAlg::Op1Off1Op2), + PLAIN_MNEMONIC("psq_lu", GekkoMnemonic::Psq_lu, ParseAlg::Op1Off1Op2), + PLAIN_MNEMONIC("psq_lux", GekkoMnemonic::Psq_lux, ParseAlg::Op5), + PLAIN_MNEMONIC("psq_lx", GekkoMnemonic::Psq_lx, ParseAlg::Op5), + PLAIN_MNEMONIC("psq_st", GekkoMnemonic::Psq_st, ParseAlg::Op1Off1Op2), + PLAIN_MNEMONIC("psq_stu", GekkoMnemonic::Psq_stu, ParseAlg::Op1Off1Op2), + PLAIN_MNEMONIC("psq_stux", GekkoMnemonic::Psq_stux, ParseAlg::Op5), + PLAIN_MNEMONIC("psq_stx", GekkoMnemonic::Psq_stx, ParseAlg::Op5), + RC_MNEMONIC("ps_abs", GekkoMnemonic::Ps_abs, ParseAlg::Op2), + RC_MNEMONIC("ps_add", GekkoMnemonic::Ps_add, ParseAlg::Op3), + PLAIN_MNEMONIC("ps_cmpo0", GekkoMnemonic::Ps_cmpo0, ParseAlg::Op3), + PLAIN_MNEMONIC("ps_cmpo1", GekkoMnemonic::Ps_cmpo1, ParseAlg::Op3), + PLAIN_MNEMONIC("ps_cmpu0", GekkoMnemonic::Ps_cmpu0, ParseAlg::Op3), + PLAIN_MNEMONIC("ps_cmpu1", GekkoMnemonic::Ps_cmpu1, ParseAlg::Op3), + RC_MNEMONIC("ps_div", GekkoMnemonic::Ps_div, ParseAlg::Op3), + RC_MNEMONIC("ps_madd", GekkoMnemonic::Ps_madd, ParseAlg::Op4), + RC_MNEMONIC("ps_madds0", GekkoMnemonic::Ps_madds0, ParseAlg::Op4), + RC_MNEMONIC("ps_madds1", GekkoMnemonic::Ps_madds1, ParseAlg::Op4), + RC_MNEMONIC("ps_merge00", GekkoMnemonic::Ps_merge00, ParseAlg::Op3), + RC_MNEMONIC("ps_merge01", GekkoMnemonic::Ps_merge01, ParseAlg::Op3), + RC_MNEMONIC("ps_merge10", GekkoMnemonic::Ps_merge10, ParseAlg::Op3), + RC_MNEMONIC("ps_merge11", GekkoMnemonic::Ps_merge11, ParseAlg::Op3), + RC_MNEMONIC("ps_mr", GekkoMnemonic::Ps_mr, ParseAlg::Op2), + RC_MNEMONIC("ps_msub", GekkoMnemonic::Ps_msub, ParseAlg::Op4), + RC_MNEMONIC("ps_mul", GekkoMnemonic::Ps_mul, ParseAlg::Op3), + RC_MNEMONIC("ps_muls0", GekkoMnemonic::Ps_muls0, ParseAlg::Op3), + RC_MNEMONIC("ps_muls1", GekkoMnemonic::Ps_muls1, ParseAlg::Op3), + RC_MNEMONIC("ps_nabs", GekkoMnemonic::Ps_nabs, ParseAlg::Op2), + RC_MNEMONIC("ps_neg", GekkoMnemonic::Ps_neg, ParseAlg::Op2), + RC_MNEMONIC("ps_nmadd", GekkoMnemonic::Ps_nmadd, ParseAlg::Op4), + RC_MNEMONIC("ps_nmsub", GekkoMnemonic::Ps_nmsub, ParseAlg::Op4), + RC_MNEMONIC("ps_res", GekkoMnemonic::Ps_res, ParseAlg::Op2), + RC_MNEMONIC("ps_rsqrte", GekkoMnemonic::Ps_rsqrte, ParseAlg::Op2), + RC_MNEMONIC("ps_sel", GekkoMnemonic::Ps_sel, ParseAlg::Op4), + RC_MNEMONIC("ps_sub", GekkoMnemonic::Ps_sub, ParseAlg::Op3), + RC_MNEMONIC("ps_sum0", GekkoMnemonic::Ps_sum0, ParseAlg::Op4), + RC_MNEMONIC("ps_sum1", GekkoMnemonic::Ps_sum1, ParseAlg::Op4), + PLAIN_MNEMONIC("rfi", GekkoMnemonic::Rfi, ParseAlg::None), + RC_MNEMONIC("rlwimi", GekkoMnemonic::Rlwimi, ParseAlg::Op5), + RC_MNEMONIC("rlwinm", GekkoMnemonic::Rlwinm, ParseAlg::Op5), + RC_MNEMONIC("rlwnm", GekkoMnemonic::Rlwnm, ParseAlg::Op5), + PLAIN_MNEMONIC("sc", GekkoMnemonic::Sc, ParseAlg::None), + RC_MNEMONIC("slw", GekkoMnemonic::Slw, ParseAlg::Op3), + RC_MNEMONIC("sraw", GekkoMnemonic::Sraw, ParseAlg::Op3), + RC_MNEMONIC("srawi", GekkoMnemonic::Srawi, ParseAlg::Op3), + RC_MNEMONIC("srw", GekkoMnemonic::Srw, ParseAlg::Op3), + PLAIN_MNEMONIC("stb", GekkoMnemonic::Stb, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("stbu", GekkoMnemonic::Stbu, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("stbux", GekkoMnemonic::Stbux, ParseAlg::Op3), + PLAIN_MNEMONIC("stbx", GekkoMnemonic::Stbx, ParseAlg::Op3), + PLAIN_MNEMONIC("stfd", GekkoMnemonic::Stfd, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("stfdu", GekkoMnemonic::Stfdu, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("stfdux", GekkoMnemonic::Stfdux, ParseAlg::Op3), + PLAIN_MNEMONIC("stfdx", GekkoMnemonic::Stfdx, ParseAlg::Op3), + PLAIN_MNEMONIC("stfiwx", GekkoMnemonic::Stfiwx, ParseAlg::Op3), + PLAIN_MNEMONIC("stfs", GekkoMnemonic::Stfs, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("stfsu", GekkoMnemonic::Stfsu, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("stfsux", GekkoMnemonic::Stfsux, ParseAlg::Op3), + PLAIN_MNEMONIC("stfsx", GekkoMnemonic::Stfsx, ParseAlg::Op3), + PLAIN_MNEMONIC("sth", GekkoMnemonic::Sth, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("sthbrx", GekkoMnemonic::Sthbrx, ParseAlg::Op3), + PLAIN_MNEMONIC("sthu", GekkoMnemonic::Sthu, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("sthux", GekkoMnemonic::Sthux, ParseAlg::Op3), + PLAIN_MNEMONIC("sthx", GekkoMnemonic::Sthx, ParseAlg::Op3), + PLAIN_MNEMONIC("stmw", GekkoMnemonic::Stmw, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("stswi", GekkoMnemonic::Stswi, ParseAlg::Op3), + PLAIN_MNEMONIC("stswx", GekkoMnemonic::Stswx, ParseAlg::Op3), + PLAIN_MNEMONIC("stw", GekkoMnemonic::Stw, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("stwbrx", GekkoMnemonic::Stwbrx, ParseAlg::Op3), + PLAIN_MNEMONIC("stwcx.", GekkoMnemonic::StwcxDot, ParseAlg::Op3), + PLAIN_MNEMONIC("stwu", GekkoMnemonic::Stwu, ParseAlg::Op1Off1), + PLAIN_MNEMONIC("stwux", GekkoMnemonic::Stwux, ParseAlg::Op3), + PLAIN_MNEMONIC("stwx", GekkoMnemonic::Stwx, ParseAlg::Op3), + OERC_MNEMONIC("subf", GekkoMnemonic::Subf, ParseAlg::Op3), + OERC_MNEMONIC("subfc", GekkoMnemonic::Subfc, ParseAlg::Op3), + OERC_MNEMONIC("subfe", GekkoMnemonic::Subfe, ParseAlg::Op3), + PLAIN_MNEMONIC("subfic", GekkoMnemonic::Subfic, ParseAlg::Op3), + OERC_MNEMONIC("subfme", GekkoMnemonic::Subfme, ParseAlg::Op2), + OERC_MNEMONIC("subfze", GekkoMnemonic::Subfze, ParseAlg::Op2), + PLAIN_MNEMONIC("sync", GekkoMnemonic::Sync, ParseAlg::None), + PLAIN_MNEMONIC("tlbie", GekkoMnemonic::Tlbie, ParseAlg::Op1), + PLAIN_MNEMONIC("tlbsync", GekkoMnemonic::Tlbsync, ParseAlg::None), + PLAIN_MNEMONIC("tw", GekkoMnemonic::Tw, ParseAlg::Op3), + PLAIN_MNEMONIC("twi", GekkoMnemonic::Twi, ParseAlg::Op3), + RC_MNEMONIC("xor", GekkoMnemonic::Xor, ParseAlg::Op3), + PLAIN_MNEMONIC("xori", GekkoMnemonic::Xori, ParseAlg::Op3), + PLAIN_MNEMONIC("xoris", GekkoMnemonic::Xoris, ParseAlg::Op3), +}; + +#define PSEUDO(mnemonic, base, variant_bits, alg) \ + { \ + mnemonic, { static_cast(base) * VARIANT_PERMUTATIONS + (variant_bits), alg } \ + } +#define PLAIN_PSEUDO(mnemonic, base, alg) PSEUDO(mnemonic, base, PLAIN_MNEMONIC, alg) +#define RC_PSEUDO(mnemonic, base, alg) \ + PSEUDO(mnemonic, base, PLAIN_MNEMONIC, alg), PSEUDO(mnemonic ".", base, RECORD_BIT, alg) +#define OERC_PSEUDO(mnemonic, base, alg) \ + PSEUDO(mnemonic, base, PLAIN_MNEMONIC, alg), PSEUDO(mnemonic ".", base, RECORD_BIT, alg), \ + PSEUDO(mnemonic "o", base, OVERFLOW_EXCEPTION, alg), \ + PSEUDO(mnemonic "o.", base, (RECORD_BIT | OVERFLOW_EXCEPTION), alg) +#define LK_PSEUDO(mnemonic, base, alg) \ + PSEUDO(mnemonic, base, PLAIN_MNEMONIC, alg), PSEUDO(mnemonic "l", base, LINK_BIT, alg) +#define LKAA_PSEUDO(mnemonic, base, alg) \ + PSEUDO(mnemonic, base, PLAIN_MNEMONIC, alg), PSEUDO(mnemonic "l", base, LINK_BIT, alg), \ + PSEUDO(mnemonic "a", base, ABSOLUTE_ADDRESS_BIT, alg), \ + PSEUDO(mnemonic "la", base, (LINK_BIT | ABSOLUTE_ADDRESS_BIT), alg) +#define LKPRED_PSEUDO(mnemonic, base, alg) \ + PSEUDO(mnemonic, base, PLAIN_MNEMONIC, alg), PSEUDO(mnemonic "l", base, LINK_BIT, alg), \ + PSEUDO(mnemonic "-", base, PLAIN_MNEMONIC, alg), PSEUDO(mnemonic "l-", base, LINK_BIT, alg), \ + PSEUDO(mnemonic "+", base##Predict, PLAIN_MNEMONIC, alg), \ + PSEUDO(mnemonic "l+", base##Predict, LINK_BIT, alg) +#define LKAAPRED_PSEUDO(mnemonic, base, alg) \ + PSEUDO(mnemonic, base, PLAIN_MNEMONIC, alg), PSEUDO(mnemonic "l", base, LINK_BIT, alg), \ + PSEUDO(mnemonic "a", base, ABSOLUTE_ADDRESS_BIT, alg), \ + PSEUDO(mnemonic "la", base, (LINK_BIT | ABSOLUTE_ADDRESS_BIT), alg), \ + PSEUDO(mnemonic "-", base, PLAIN_MNEMONIC, alg), PSEUDO(mnemonic "l-", base, LINK_BIT, alg), \ + PSEUDO(mnemonic "a-", base, ABSOLUTE_ADDRESS_BIT, alg), \ + PSEUDO(mnemonic "la-", base, (LINK_BIT | ABSOLUTE_ADDRESS_BIT), alg), \ + PSEUDO(mnemonic "+", base##Predict, PLAIN_MNEMONIC, alg), \ + PSEUDO(mnemonic "l+", base##Predict, LINK_BIT, alg), \ + PSEUDO(mnemonic "a+", base##Predict, ABSOLUTE_ADDRESS_BIT, alg), \ + PSEUDO(mnemonic "la+", base##Predict, (LINK_BIT | ABSOLUTE_ADDRESS_BIT), alg) + +extern const CaseInsensitiveDict extended_mnemonic_tokens = { + PLAIN_PSEUDO("subi", ExtendedGekkoMnemonic::Subi, ParseAlg::Op3), + PLAIN_PSEUDO("subis", ExtendedGekkoMnemonic::Subis, ParseAlg::Op3), + PLAIN_PSEUDO("subic", ExtendedGekkoMnemonic::Subic, ParseAlg::Op3), + PLAIN_PSEUDO("subic.", ExtendedGekkoMnemonic::SubicDot, ParseAlg::Op3), + OERC_PSEUDO("sub", ExtendedGekkoMnemonic::Sub, ParseAlg::Op3), + OERC_PSEUDO("subc", ExtendedGekkoMnemonic::Subc, ParseAlg::Op3), + PLAIN_PSEUDO("cmpwi", ExtendedGekkoMnemonic::Cmpwi, ParseAlg::Op2Or3), + PLAIN_PSEUDO("cmpw", ExtendedGekkoMnemonic::Cmpw, ParseAlg::Op2Or3), + PLAIN_PSEUDO("cmplwi", ExtendedGekkoMnemonic::Cmplwi, ParseAlg::Op2Or3), + PLAIN_PSEUDO("cmplw", ExtendedGekkoMnemonic::Cmplw, ParseAlg::Op2Or3), + RC_PSEUDO("extlwi", ExtendedGekkoMnemonic::Extlwi, ParseAlg::Op4), + RC_PSEUDO("extrwi", ExtendedGekkoMnemonic::Extrwi, ParseAlg::Op4), + RC_PSEUDO("inslwi", ExtendedGekkoMnemonic::Inslwi, ParseAlg::Op4), + RC_PSEUDO("insrwi", ExtendedGekkoMnemonic::Insrwi, ParseAlg::Op4), + RC_PSEUDO("rotlwi", ExtendedGekkoMnemonic::Rotlwi, ParseAlg::Op3), + RC_PSEUDO("rotrwi", ExtendedGekkoMnemonic::Rotrwi, ParseAlg::Op3), + RC_PSEUDO("rotlw", ExtendedGekkoMnemonic::Rotlw, ParseAlg::Op3), + RC_PSEUDO("slwi", ExtendedGekkoMnemonic::Slwi, ParseAlg::Op3), + RC_PSEUDO("srwi", ExtendedGekkoMnemonic::Srwi, ParseAlg::Op3), + RC_PSEUDO("clrlwi", ExtendedGekkoMnemonic::Clrlwi, ParseAlg::Op3), + RC_PSEUDO("clrrwi", ExtendedGekkoMnemonic::Clrrwi, ParseAlg::Op3), + RC_PSEUDO("clrlslwi", ExtendedGekkoMnemonic::Clrlslwi, ParseAlg::Op4), + LKAAPRED_PSEUDO("bt", ExtendedGekkoMnemonic::Bt, ParseAlg::Op2), + LKAAPRED_PSEUDO("bf", ExtendedGekkoMnemonic::Bf, ParseAlg::Op2), + LKAAPRED_PSEUDO("bdnz", ExtendedGekkoMnemonic::Bdnz, ParseAlg::Op1), + LKAAPRED_PSEUDO("bdnzt", ExtendedGekkoMnemonic::Bdnzt, ParseAlg::Op2), + LKAAPRED_PSEUDO("bdnzf", ExtendedGekkoMnemonic::Bdnzf, ParseAlg::Op2), + LKAAPRED_PSEUDO("bdz", ExtendedGekkoMnemonic::Bdz, ParseAlg::Op1), + LKAAPRED_PSEUDO("bdzt", ExtendedGekkoMnemonic::Bdzt, ParseAlg::Op2), + LKAAPRED_PSEUDO("bdzf", ExtendedGekkoMnemonic::Bdzf, ParseAlg::Op2), + LK_PSEUDO("blr", ExtendedGekkoMnemonic::Blr, ParseAlg::None), + LK_PSEUDO("bctr", ExtendedGekkoMnemonic::Bctr, ParseAlg::None), + LKPRED_PSEUDO("btlr", ExtendedGekkoMnemonic::Btlr, ParseAlg::Op1), + LKPRED_PSEUDO("btctr", ExtendedGekkoMnemonic::Btctr, ParseAlg::Op1), + LKPRED_PSEUDO("bflr", ExtendedGekkoMnemonic::Bflr, ParseAlg::Op1), + LKPRED_PSEUDO("bfctr", ExtendedGekkoMnemonic::Bfctr, ParseAlg::Op1), + LKPRED_PSEUDO("bdnzlr", ExtendedGekkoMnemonic::Bdnzlr, ParseAlg::None), + LKPRED_PSEUDO("bdnztlr", ExtendedGekkoMnemonic::Bdnztlr, ParseAlg::Op1), + LKPRED_PSEUDO("bdnzflr", ExtendedGekkoMnemonic::Bdnzflr, ParseAlg::Op1), + LKPRED_PSEUDO("bdzlr", ExtendedGekkoMnemonic::Bdzlr, ParseAlg::None), + LKPRED_PSEUDO("bdztlr", ExtendedGekkoMnemonic::Bdztlr, ParseAlg::Op1), + LKPRED_PSEUDO("bdzflr", ExtendedGekkoMnemonic::Bdzflr, ParseAlg::Op1), + LKAAPRED_PSEUDO("blt", ExtendedGekkoMnemonic::Blt, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("ble", ExtendedGekkoMnemonic::Ble, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("beq", ExtendedGekkoMnemonic::Beq, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("bge", ExtendedGekkoMnemonic::Bge, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("bgt", ExtendedGekkoMnemonic::Bgt, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("bnl", ExtendedGekkoMnemonic::Bnl, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("bne", ExtendedGekkoMnemonic::Bne, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("bng", ExtendedGekkoMnemonic::Bng, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("bso", ExtendedGekkoMnemonic::Bso, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("bns", ExtendedGekkoMnemonic::Bns, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("bun", ExtendedGekkoMnemonic::Bun, ParseAlg::Op1Or2), + LKAAPRED_PSEUDO("bnu", ExtendedGekkoMnemonic::Bnu, ParseAlg::Op1Or2), + LKPRED_PSEUDO("bltlr", ExtendedGekkoMnemonic::Bltlr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bltctr", ExtendedGekkoMnemonic::Bltctr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("blelr", ExtendedGekkoMnemonic::Blelr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("blectr", ExtendedGekkoMnemonic::Blectr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("beqlr", ExtendedGekkoMnemonic::Beqlr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("beqctr", ExtendedGekkoMnemonic::Beqctr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bgelr", ExtendedGekkoMnemonic::Bgelr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bgectr", ExtendedGekkoMnemonic::Bgectr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bgtlr", ExtendedGekkoMnemonic::Bgtlr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bgtctr", ExtendedGekkoMnemonic::Bgtctr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bnllr", ExtendedGekkoMnemonic::Bnllr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bnlctr", ExtendedGekkoMnemonic::Bnlctr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bnelr", ExtendedGekkoMnemonic::Bnelr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bnectr", ExtendedGekkoMnemonic::Bnectr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bnglr", ExtendedGekkoMnemonic::Bnglr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bngctr", ExtendedGekkoMnemonic::Bngctr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bsolr", ExtendedGekkoMnemonic::Bsolr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bsoctr", ExtendedGekkoMnemonic::Bsoctr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bnslr", ExtendedGekkoMnemonic::Bnslr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bnsctr", ExtendedGekkoMnemonic::Bnsctr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bunlr", ExtendedGekkoMnemonic::Bunlr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bunctr", ExtendedGekkoMnemonic::Bunctr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bnulr", ExtendedGekkoMnemonic::Bnulr, ParseAlg::NoneOrOp1), + LKPRED_PSEUDO("bnuctr", ExtendedGekkoMnemonic::Bnuctr, ParseAlg::NoneOrOp1), + PLAIN_PSEUDO("crset", ExtendedGekkoMnemonic::Crset, ParseAlg::Op1), + PLAIN_PSEUDO("crclr", ExtendedGekkoMnemonic::Crclr, ParseAlg::Op1), + PLAIN_PSEUDO("crmove", ExtendedGekkoMnemonic::Crmove, ParseAlg::Op2), + PLAIN_PSEUDO("crnot", ExtendedGekkoMnemonic::Crnot, ParseAlg::Op2), + PLAIN_PSEUDO("twlt", ExtendedGekkoMnemonic::Twlt, ParseAlg::Op2), + PLAIN_PSEUDO("twlti", ExtendedGekkoMnemonic::Twlti, ParseAlg::Op2), + PLAIN_PSEUDO("twle", ExtendedGekkoMnemonic::Twle, ParseAlg::Op2), + PLAIN_PSEUDO("twlei", ExtendedGekkoMnemonic::Twlei, ParseAlg::Op2), + PLAIN_PSEUDO("tweq", ExtendedGekkoMnemonic::Tweq, ParseAlg::Op2), + PLAIN_PSEUDO("tweqi", ExtendedGekkoMnemonic::Tweqi, ParseAlg::Op2), + PLAIN_PSEUDO("twge", ExtendedGekkoMnemonic::Twge, ParseAlg::Op2), + PLAIN_PSEUDO("twgei", ExtendedGekkoMnemonic::Twgei, ParseAlg::Op2), + PLAIN_PSEUDO("twgt", ExtendedGekkoMnemonic::Twgt, ParseAlg::Op2), + PLAIN_PSEUDO("twgti", ExtendedGekkoMnemonic::Twgti, ParseAlg::Op2), + PLAIN_PSEUDO("twnl", ExtendedGekkoMnemonic::Twnl, ParseAlg::Op2), + PLAIN_PSEUDO("twnli", ExtendedGekkoMnemonic::Twnli, ParseAlg::Op2), + PLAIN_PSEUDO("twne", ExtendedGekkoMnemonic::Twne, ParseAlg::Op2), + PLAIN_PSEUDO("twnei", ExtendedGekkoMnemonic::Twnei, ParseAlg::Op2), + PLAIN_PSEUDO("twng", ExtendedGekkoMnemonic::Twng, ParseAlg::Op2), + PLAIN_PSEUDO("twngi", ExtendedGekkoMnemonic::Twngi, ParseAlg::Op2), + PLAIN_PSEUDO("twllt", ExtendedGekkoMnemonic::Twllt, ParseAlg::Op2), + PLAIN_PSEUDO("twllti", ExtendedGekkoMnemonic::Twllti, ParseAlg::Op2), + PLAIN_PSEUDO("twlle", ExtendedGekkoMnemonic::Twlle, ParseAlg::Op2), + PLAIN_PSEUDO("twllei", ExtendedGekkoMnemonic::Twllei, ParseAlg::Op2), + PLAIN_PSEUDO("twlge", ExtendedGekkoMnemonic::Twlge, ParseAlg::Op2), + PLAIN_PSEUDO("twlgei", ExtendedGekkoMnemonic::Twlgei, ParseAlg::Op2), + PLAIN_PSEUDO("twlgt", ExtendedGekkoMnemonic::Twlgt, ParseAlg::Op2), + PLAIN_PSEUDO("twlgti", ExtendedGekkoMnemonic::Twlgti, ParseAlg::Op2), + PLAIN_PSEUDO("twlnl", ExtendedGekkoMnemonic::Twlnl, ParseAlg::Op2), + PLAIN_PSEUDO("twlnli", ExtendedGekkoMnemonic::Twlnli, ParseAlg::Op2), + PLAIN_PSEUDO("twlng", ExtendedGekkoMnemonic::Twlng, ParseAlg::Op2), + PLAIN_PSEUDO("twlngi", ExtendedGekkoMnemonic::Twlngi, ParseAlg::Op2), + PLAIN_PSEUDO("trap", ExtendedGekkoMnemonic::Trap, ParseAlg::None), + PLAIN_PSEUDO("mtxer", ExtendedGekkoMnemonic::Mtxer, ParseAlg::Op1), + PLAIN_PSEUDO("mfxer", ExtendedGekkoMnemonic::Mfxer, ParseAlg::Op1), + PLAIN_PSEUDO("mtlr", ExtendedGekkoMnemonic::Mtlr, ParseAlg::Op1), + PLAIN_PSEUDO("mflr", ExtendedGekkoMnemonic::Mflr, ParseAlg::Op1), + PLAIN_PSEUDO("mtctr", ExtendedGekkoMnemonic::Mtctr, ParseAlg::Op1), + PLAIN_PSEUDO("mfctr", ExtendedGekkoMnemonic::Mfctr, ParseAlg::Op1), + PLAIN_PSEUDO("mtdsisr", ExtendedGekkoMnemonic::Mtdsisr, ParseAlg::Op1), + PLAIN_PSEUDO("mfdsisr", ExtendedGekkoMnemonic::Mfdsisr, ParseAlg::Op1), + PLAIN_PSEUDO("mtdar", ExtendedGekkoMnemonic::Mtdar, ParseAlg::Op1), + PLAIN_PSEUDO("mfdar", ExtendedGekkoMnemonic::Mfdar, ParseAlg::Op1), + PLAIN_PSEUDO("mtdec", ExtendedGekkoMnemonic::Mtdec, ParseAlg::Op1), + PLAIN_PSEUDO("mfdec", ExtendedGekkoMnemonic::Mfdec, ParseAlg::Op1), + PLAIN_PSEUDO("mtsdr1", ExtendedGekkoMnemonic::Mtsdr1, ParseAlg::Op1), + PLAIN_PSEUDO("mfsdr1", ExtendedGekkoMnemonic::Mfsdr1, ParseAlg::Op1), + PLAIN_PSEUDO("mtsrr0", ExtendedGekkoMnemonic::Mtsrr0, ParseAlg::Op1), + PLAIN_PSEUDO("mfsrr0", ExtendedGekkoMnemonic::Mfsrr0, ParseAlg::Op1), + PLAIN_PSEUDO("mtsrr1", ExtendedGekkoMnemonic::Mtsrr1, ParseAlg::Op1), + PLAIN_PSEUDO("mfsrr1", ExtendedGekkoMnemonic::Mfsrr1, ParseAlg::Op1), + PLAIN_PSEUDO("mtasr", ExtendedGekkoMnemonic::Mtasr, ParseAlg::Op1), + PLAIN_PSEUDO("mfasr", ExtendedGekkoMnemonic::Mfasr, ParseAlg::Op1), + PLAIN_PSEUDO("mtear", ExtendedGekkoMnemonic::Mtear, ParseAlg::Op1), + PLAIN_PSEUDO("mfear", ExtendedGekkoMnemonic::Mfear, ParseAlg::Op1), + PLAIN_PSEUDO("mttbl", ExtendedGekkoMnemonic::Mttbl, ParseAlg::Op1), + PLAIN_PSEUDO("mftbl", ExtendedGekkoMnemonic::Mftbl, ParseAlg::Op1), + PLAIN_PSEUDO("mttbu", ExtendedGekkoMnemonic::Mttbu, ParseAlg::Op1), + PLAIN_PSEUDO("mftbu", ExtendedGekkoMnemonic::Mftbu, ParseAlg::Op1), + PLAIN_PSEUDO("mtsprg", ExtendedGekkoMnemonic::Mtsprg, ParseAlg::Op2), + PLAIN_PSEUDO("mfsprg", ExtendedGekkoMnemonic::Mfsprg, ParseAlg::Op2), + PLAIN_PSEUDO("mtibatu", ExtendedGekkoMnemonic::Mtibatu, ParseAlg::Op2), + PLAIN_PSEUDO("mfibatu", ExtendedGekkoMnemonic::Mfibatu, ParseAlg::Op2), + PLAIN_PSEUDO("mtibatl", ExtendedGekkoMnemonic::Mtibatl, ParseAlg::Op2), + PLAIN_PSEUDO("mfibatl", ExtendedGekkoMnemonic::Mfibatl, ParseAlg::Op2), + PLAIN_PSEUDO("mtdbatu", ExtendedGekkoMnemonic::Mtdbatu, ParseAlg::Op2), + PLAIN_PSEUDO("mfdbatu", ExtendedGekkoMnemonic::Mfdbatu, ParseAlg::Op2), + PLAIN_PSEUDO("mtdbatl", ExtendedGekkoMnemonic::Mtdbatl, ParseAlg::Op2), + PLAIN_PSEUDO("mfdbatl", ExtendedGekkoMnemonic::Mfdbatl, ParseAlg::Op2), + PLAIN_PSEUDO("nop", ExtendedGekkoMnemonic::Nop, ParseAlg::None), + PLAIN_PSEUDO("li", ExtendedGekkoMnemonic::Li, ParseAlg::Op2), + PLAIN_PSEUDO("lis", ExtendedGekkoMnemonic::Lis, ParseAlg::Op2), + PLAIN_PSEUDO("la", ExtendedGekkoMnemonic::La, ParseAlg::Op1Off1), + RC_PSEUDO("mr", ExtendedGekkoMnemonic::Mr, ParseAlg::Op2), + RC_PSEUDO("not", ExtendedGekkoMnemonic::Not, ParseAlg::Op2), + PLAIN_PSEUDO("mtcr", ExtendedGekkoMnemonic::Mtcr, ParseAlg::Op1), + PLAIN_PSEUDO("mfspr", ExtendedGekkoMnemonic::Mfspr, ParseAlg::Op2), + PLAIN_PSEUDO("mftb", ExtendedGekkoMnemonic::Mftb, ParseAlg::Op2), + PLAIN_PSEUDO("mtspr", ExtendedGekkoMnemonic::Mtspr, ParseAlg::Op2), +}; + +#undef MNEMONIC +#undef PLAIN_MNEMONIC +#undef RC_MNEMONIC +#undef OERC_MNEMONIC +#undef LK_MNEMONIC +#undef AALK_MNEMONIC +#undef PSEUDO +#undef PLAIN_PSEUDO +#undef RC_PSEUDO +#undef OERC_PSEUDO +#undef LK_PSEUDO +#undef LKAA_PSEUDO +#undef LKPRED_PSEUDO +#undef LKAAPRED_PSEUDO + +////////////////////// +// ASSEMBLER TABLES // +////////////////////// +#define EMIT_MNEMONIC_ENTRY(opcode_val, extra_bits, ...) \ + MnemonicDesc \ + { \ + InsertOpcode(opcode_val) | (extra_bits), \ + static_cast(std::initializer_list{__VA_ARGS__}.size()), \ + { \ + __VA_ARGS__ \ + } \ + } +#define MNEMONIC(opcode_val, extra_bits, ...) \ + EMIT_MNEMONIC_ENTRY(opcode_val, extra_bits, __VA_ARGS__), INVALID_MNEMONIC, INVALID_MNEMONIC, \ + INVALID_MNEMONIC +#define BASIC_MNEMONIC(opcode_val, ...) MNEMONIC(opcode_val, 0, __VA_ARGS__) +#define RC_MNEMONIC(opcode_val, extra_bits, ...) \ + EMIT_MNEMONIC_ENTRY(opcode_val, extra_bits, __VA_ARGS__), \ + EMIT_MNEMONIC_ENTRY(opcode_val, ((extra_bits) | InsertVal(1, 31, 31)), __VA_ARGS__), \ + INVALID_MNEMONIC, INVALID_MNEMONIC +#define OERC_MNEMONIC(opcode_val, extra_bits, ...) \ + EMIT_MNEMONIC_ENTRY(opcode_val, extra_bits, __VA_ARGS__), \ + EMIT_MNEMONIC_ENTRY(opcode_val, ((extra_bits) | InsertVal(1, 31, 31)), __VA_ARGS__), \ + EMIT_MNEMONIC_ENTRY(opcode_val, ((extra_bits) | InsertVal(1, 21, 21)), __VA_ARGS__), \ + EMIT_MNEMONIC_ENTRY( \ + opcode_val, ((extra_bits) | InsertVal(1, 31, 31) | InsertVal(1, 21, 21)), __VA_ARGS__) +#define LK_MNEMONIC(opcode_val, extra_bits, ...) \ + EMIT_MNEMONIC_ENTRY(opcode_val, extra_bits, __VA_ARGS__), \ + EMIT_MNEMONIC_ENTRY(opcode_val, ((extra_bits) | InsertVal(1, 31, 31)), __VA_ARGS__), \ + INVALID_MNEMONIC, INVALID_MNEMONIC +#define AALK_MNEMONIC(opcode_val, extra_bits, ...) \ + EMIT_MNEMONIC_ENTRY(opcode_val, extra_bits, __VA_ARGS__), \ + EMIT_MNEMONIC_ENTRY(opcode_val, ((extra_bits) | InsertVal(0b01, 30, 31)), __VA_ARGS__), \ + EMIT_MNEMONIC_ENTRY(opcode_val, ((extra_bits) | InsertVal(0b10, 30, 31)), __VA_ARGS__), \ + EMIT_MNEMONIC_ENTRY(opcode_val, ((extra_bits) | InsertVal(0b11, 30, 31)), __VA_ARGS__) + +// Defines all basic mnemonics that Broadway/Gekko supports +extern const std::array mnemonics = { + // A-2 + OERC_MNEMONIC(31, InsertVal(266, 22, 30), _D, _A, _B), // add + OERC_MNEMONIC(31, InsertVal(10, 22, 30), _D, _A, _B), // addc + OERC_MNEMONIC(31, InsertVal(138, 22, 30), _D, _A, _B), // adde + BASIC_MNEMONIC(14, _D, _A, _SIMM), // addi + BASIC_MNEMONIC(12, _D, _A, _SIMM), // addic + BASIC_MNEMONIC(13, _D, _A, _SIMM), // addic. + BASIC_MNEMONIC(15, _D, _A, _SIMM), // addis + OERC_MNEMONIC(31, InsertVal(234, 22, 30), _D, _A), // addme + OERC_MNEMONIC(31, InsertVal(202, 22, 30), _D, _A), // addze + OERC_MNEMONIC(31, InsertVal(491, 22, 30), _D, _A, _B), // divw + OERC_MNEMONIC(31, InsertVal(459, 22, 30), _D, _A, _B), // divwu + RC_MNEMONIC(31, InsertVal(75, 22, 30), _D, _A, _B), // mulhw + RC_MNEMONIC(31, InsertVal(11, 22, 30), _D, _A, _B), // mulhwu + BASIC_MNEMONIC(7, _D, _A, _SIMM), // mulli + OERC_MNEMONIC(31, InsertVal(235, 22, 30), _D, _A, _B), // mullw + OERC_MNEMONIC(31, InsertVal(104, 22, 30), _D, _A), // neg + OERC_MNEMONIC(31, InsertVal(40, 22, 30), _D, _A, _B), // subf + OERC_MNEMONIC(31, InsertVal(8, 22, 30), _D, _A, _B), // subfc + OERC_MNEMONIC(31, InsertVal(136, 22, 30), _D, _A, _B), // subfe + BASIC_MNEMONIC(8, _D, _A, _SIMM), // subfic + OERC_MNEMONIC(31, InsertVal(232, 22, 30), _D, _A), // subfme + OERC_MNEMONIC(31, InsertVal(200, 22, 30), _D, _A), // subfze + + // A-3 + MNEMONIC(31, InsertVal(0, 21, 30), _Crfd, _L, _A, _B), // cmp + BASIC_MNEMONIC(11, _Crfd, _L, _A, _SIMM), // cmpi + MNEMONIC(31, InsertVal(32, 21, 30), _Crfd, _L, _A, _B), // cmpl + BASIC_MNEMONIC(10, _Crfd, _L, _A, _UIMM), // cmpli + + // A-4 + RC_MNEMONIC(31, InsertVal(28, 21, 30), _A, _S, _B), // and + RC_MNEMONIC(31, InsertVal(60, 21, 30), _A, _S, _B), // andc + BASIC_MNEMONIC(28, _A, _S, _UIMM), // andi. + BASIC_MNEMONIC(29, _A, _S, _UIMM), // andis. + RC_MNEMONIC(31, InsertVal(26, 21, 30), _A, _S), // cntlzw + RC_MNEMONIC(31, InsertVal(284, 21, 30), _A, _S, _B), // eqv + RC_MNEMONIC(31, InsertVal(954, 21, 30), _A, _S), // extsb + RC_MNEMONIC(31, InsertVal(922, 21, 30), _A, _S), // extsh + RC_MNEMONIC(31, InsertVal(476, 21, 30), _A, _S, _B), // nand + RC_MNEMONIC(31, InsertVal(124, 21, 30), _A, _S, _B), // nor + RC_MNEMONIC(31, InsertVal(444, 21, 30), _A, _S, _B), // or + RC_MNEMONIC(31, InsertVal(412, 21, 30), _A, _S, _B), // orc + BASIC_MNEMONIC(24, _A, _S, _UIMM), // ori + BASIC_MNEMONIC(25, _A, _S, _UIMM), // oris + RC_MNEMONIC(31, InsertVal(316, 21, 30), _A, _S, _B), // xor + BASIC_MNEMONIC(26, _A, _S, _UIMM), // xori + BASIC_MNEMONIC(27, _A, _S, _UIMM), // xoris + + // A-5 + RC_MNEMONIC(20, 0, _A, _S, _SH, _MB, _ME), // rlwimi + RC_MNEMONIC(21, 0, _A, _S, _SH, _MB, _ME), // rlwinm + RC_MNEMONIC(23, 0, _A, _S, _B, _MB, _ME), // rlwnm + + // A-6 + RC_MNEMONIC(31, InsertVal(24, 21, 30), _A, _S, _B), // slw + RC_MNEMONIC(31, InsertVal(792, 21, 30), _A, _S, _B), // sraw + RC_MNEMONIC(31, InsertVal(824, 21, 30), _A, _S, _SH), // srawi + RC_MNEMONIC(31, InsertVal(536, 21, 30), _A, _S, _B), // srw + + // A-7 + RC_MNEMONIC(63, InsertVal(21, 26, 30), _D, _A, _B), // fadd + RC_MNEMONIC(59, InsertVal(21, 26, 30), _D, _A, _B), // fadds + RC_MNEMONIC(63, InsertVal(18, 26, 30), _D, _A, _B), // fdiv + RC_MNEMONIC(59, InsertVal(18, 26, 30), _D, _A, _B), // fdivs + RC_MNEMONIC(63, InsertVal(25, 26, 30), _D, _A, _C), // fmul + RC_MNEMONIC(59, InsertVal(25, 26, 30), _D, _A, _C), // fmuls + RC_MNEMONIC(59, InsertVal(24, 26, 30), _D, _B), // fres + RC_MNEMONIC(63, InsertVal(26, 26, 30), _D, _B), // frsqrte + RC_MNEMONIC(63, InsertVal(20, 26, 30), _D, _A, _B), // fsub + RC_MNEMONIC(59, InsertVal(20, 26, 30), _D, _A, _B), // fsubs + RC_MNEMONIC(63, InsertVal(23, 26, 30), _D, _A, _C, _B), // fsel + + // A-8 + RC_MNEMONIC(63, InsertVal(29, 26, 30), _D, _A, _C, _B), // fmadd + RC_MNEMONIC(59, InsertVal(29, 26, 30), _D, _A, _C, _B), // fmadds + RC_MNEMONIC(63, InsertVal(28, 26, 30), _D, _A, _C, _B), // fmsub + RC_MNEMONIC(59, InsertVal(28, 26, 30), _D, _A, _C, _B), // fmsubs + RC_MNEMONIC(63, InsertVal(31, 26, 30), _D, _A, _C, _B), // fnmadd + RC_MNEMONIC(59, InsertVal(31, 26, 30), _D, _A, _C, _B), // fnmadds + RC_MNEMONIC(63, InsertVal(30, 26, 30), _D, _A, _C, _B), // fnmsub + RC_MNEMONIC(59, InsertVal(30, 26, 30), _D, _A, _C, _B), // fnmsubs + + // A-9 + RC_MNEMONIC(63, InsertVal(14, 21, 30), _D, _B), // fctiw + RC_MNEMONIC(63, InsertVal(15, 21, 30), _D, _B), // fctiwz + RC_MNEMONIC(63, InsertVal(12, 21, 30), _D, _B), // frsp + + // A-10 + MNEMONIC(63, InsertVal(32, 21, 30), _Crfd, _A, _B), // fcmpo + MNEMONIC(63, InsertVal(0, 21, 30), _Crfd, _A, _B), // fcmpu + + // A-11 + MNEMONIC(63, InsertVal(64, 21, 30), _Crfd, _Crfs), // mcrfs + RC_MNEMONIC(63, InsertVal(583, 21, 30), _D), // mffs + RC_MNEMONIC(63, InsertVal(70, 21, 30), _Crbd), // mtfsb0 + RC_MNEMONIC(63, InsertVal(38, 21, 30), _Crbd), // mtfsb1 + RC_MNEMONIC(63, InsertVal(711, 21, 30), _FM, _B), // mtfsf + RC_MNEMONIC(63, InsertVal(134, 21, 30), _Crfd, _IMM), // mtfsfi + + // A-12 + BASIC_MNEMONIC(34, _D, _Offd, _A), // lbz + BASIC_MNEMONIC(35, _D, _Offd, _A), // lbzu + MNEMONIC(31, InsertVal(119, 21, 30), _D, _A, _B), // lbzux + MNEMONIC(31, InsertVal(87, 21, 30), _D, _A, _B), // lbzx + BASIC_MNEMONIC(42, _D, _Offd, _A), // lha + BASIC_MNEMONIC(43, _D, _Offd, _A), // lhau + MNEMONIC(31, InsertVal(375, 21, 30), _D, _A, _B), // lhaux + MNEMONIC(31, InsertVal(343, 21, 30), _D, _A, _B), // lhax + BASIC_MNEMONIC(40, _D, _Offd, _A), // lhz + BASIC_MNEMONIC(41, _D, _Offd, _A), // lhzu + MNEMONIC(31, InsertVal(311, 21, 30), _D, _A, _B), // lhzux + MNEMONIC(31, InsertVal(279, 21, 30), _D, _A, _B), // lhzx + BASIC_MNEMONIC(32, _D, _Offd, _A), // lwz + BASIC_MNEMONIC(33, _D, _Offd, _A), // lwzu + MNEMONIC(31, InsertVal(55, 21, 30), _D, _A, _B), // lwzux + MNEMONIC(31, InsertVal(23, 21, 30), _D, _A, _B), // lwzx + + // A-13 + BASIC_MNEMONIC(38, _S, _Offd, _A), // stb + BASIC_MNEMONIC(39, _S, _Offd, _A), // stbu + MNEMONIC(31, InsertVal(247, 21, 30), _S, _A, _B), // stbux + MNEMONIC(31, InsertVal(215, 21, 30), _S, _A, _B), // stbx + BASIC_MNEMONIC(44, _S, _Offd, _A), // sth + BASIC_MNEMONIC(45, _S, _Offd, _A), // sthu + MNEMONIC(31, InsertVal(439, 21, 30), _S, _A, _B), // sthux + MNEMONIC(31, InsertVal(407, 21, 30), _S, _A, _B), // sthx + BASIC_MNEMONIC(36, _S, _Offd, _A), // stw + BASIC_MNEMONIC(37, _S, _Offd, _A), // stwu + MNEMONIC(31, InsertVal(183, 21, 30), _S, _A, _B), // stwux + MNEMONIC(31, InsertVal(151, 21, 30), _S, _A, _B), // stwx + + // A-14 + MNEMONIC(31, InsertVal(790, 21, 30), _D, _A, _B), // lhbrx + MNEMONIC(31, InsertVal(534, 21, 30), _D, _A, _B), // lwbrx + MNEMONIC(31, InsertVal(918, 21, 30), _S, _A, _B), // sthbrx + MNEMONIC(31, InsertVal(662, 21, 30), _S, _A, _B), // stwbrx + + // A-15 + BASIC_MNEMONIC(46, _D, _Offd, _A), // lmw + BASIC_MNEMONIC(47, _S, _Offd, _A), // stmw + + // A-16 + MNEMONIC(31, InsertVal(597, 21, 30), _D, _A, _NB), // lswi + MNEMONIC(31, InsertVal(533, 21, 30), _D, _A, _B), // lswx + MNEMONIC(31, InsertVal(725, 21, 30), _S, _A, _NB), // stswi + MNEMONIC(31, InsertVal(661, 21, 30), _S, _A, _B), // stswx + + // A-17 + MNEMONIC(31, InsertVal(854, 21, 30)), // eieio + MNEMONIC(19, InsertVal(150, 21, 30)), // isync + MNEMONIC(31, InsertVal(20, 21, 30), _D, _A, _B), // lwarx + MNEMONIC(31, InsertVal(150, 21, 30) | InsertVal(1, 31, 31), _S, _A, _B), // stwcx. + MNEMONIC(31, InsertVal(598, 21, 30)), // sync + + // A-18 + BASIC_MNEMONIC(50, _D, _Offd, _A), // lfd + BASIC_MNEMONIC(51, _D, _Offd, _A), // lfdu + MNEMONIC(31, InsertVal(631, 21, 30), _D, _A, _B), // lfdux + MNEMONIC(31, InsertVal(599, 21, 30), _D, _A, _B), // lfdx + BASIC_MNEMONIC(48, _D, _Offd, _A), // lfs + BASIC_MNEMONIC(49, _D, _Offd, _A), // lfsu + MNEMONIC(31, InsertVal(567, 21, 30), _D, _A, _B), // lfsux + MNEMONIC(31, InsertVal(535, 21, 30), _D, _A, _B), // lfsx + + // A-19 + BASIC_MNEMONIC(54, _S, _Offd, _A), // stfd + BASIC_MNEMONIC(55, _S, _Offd, _A), // stfdu + MNEMONIC(31, InsertVal(759, 21, 30), _S, _A, _B), // stfdux + MNEMONIC(31, InsertVal(727, 21, 30), _S, _A, _B), // stfdx + MNEMONIC(31, InsertVal(983, 21, 30), _S, _A, _B), // stfiwx + BASIC_MNEMONIC(52, _S, _Offd, _A), // stfs + BASIC_MNEMONIC(53, _S, _Offd, _A), // stfsu + MNEMONIC(31, InsertVal(695, 21, 30), _S, _A, _B), // stfsux + MNEMONIC(31, InsertVal(663, 21, 30), _S, _A, _B), // stfsx + + // A-20 + RC_MNEMONIC(63, InsertVal(264, 21, 30), _D, _B), // fabs + RC_MNEMONIC(63, InsertVal(72, 21, 30), _D, _B), // fmr + RC_MNEMONIC(63, InsertVal(136, 21, 30), _D, _B), // fnabs + RC_MNEMONIC(63, InsertVal(40, 21, 30), _D, _B), // fneg + + // A-21 + AALK_MNEMONIC(18, 0, _LI), // b + AALK_MNEMONIC(16, 0, _BO, _BI, _BD), // bc + LK_MNEMONIC(19, InsertVal(528, 21, 30), _BO, _BI), // bcctr + LK_MNEMONIC(19, InsertVal(16, 21, 30), _BO, _BI), // bclr + + // A-22 + MNEMONIC(19, InsertVal(257, 21, 30), _Crbd, _Crba, _Crbb), // crand + MNEMONIC(19, InsertVal(129, 21, 30), _Crbd, _Crba, _Crbb), // crandc + MNEMONIC(19, InsertVal(289, 21, 30), _Crbd, _Crba, _Crbb), // creqv + MNEMONIC(19, InsertVal(225, 21, 30), _Crbd, _Crba, _Crbb), // crnand + MNEMONIC(19, InsertVal(33, 21, 30), _Crbd, _Crba, _Crbb), // crnor + MNEMONIC(19, InsertVal(449, 21, 30), _Crbd, _Crba, _Crbb), // cror + MNEMONIC(19, InsertVal(417, 21, 30), _Crbd, _Crba, _Crbb), // crorc + MNEMONIC(19, InsertVal(193, 21, 30), _Crbd, _Crba, _Crbb), // crxor + MNEMONIC(19, InsertVal(0, 21, 30), _Crfd, _Crfs), // mcrf + + // A-23 + MNEMONIC(19, InsertVal(50, 21, 30)), // rfi + MNEMONIC(17, InsertVal(1, 30, 30)), // sc + + // A-24 + MNEMONIC(31, InsertVal(4, 21, 30), _TO, _A, _B), // tw + BASIC_MNEMONIC(3, _TO, _A, _SIMM), // twi + + // A-25 + MNEMONIC(31, InsertVal(512, 21, 30), _Crfd), // mcrxr + MNEMONIC(31, InsertVal(19, 21, 30), _D), // mfcr + MNEMONIC(31, InsertVal(83, 21, 30), _D), // mfmsr + MNEMONIC(31, InsertVal(339, 21, 30), _D, _SPR), // mfspr + MNEMONIC(31, InsertVal(371, 21, 30), _D, _TPR), // mftb + MNEMONIC(31, InsertVal(144, 21, 30), _CRM, _S), // mtcrf + MNEMONIC(31, InsertVal(146, 21, 30), _S), // mtmsr + MNEMONIC(31, InsertVal(467, 21, 30), _SPR, _D), // mtspr + + // A-26 + MNEMONIC(31, InsertVal(86, 21, 30), _A, _B), // dcbf + MNEMONIC(31, InsertVal(470, 21, 30), _A, _B), // dcbi + MNEMONIC(31, InsertVal(54, 21, 30), _A, _B), // dcbst + MNEMONIC(31, InsertVal(278, 21, 30), _A, _B), // dcbt + MNEMONIC(31, InsertVal(246, 21, 30), _A, _B), // dcbtst + MNEMONIC(31, InsertVal(1014, 21, 30), _A, _B), // dcbz + MNEMONIC(31, InsertVal(982, 21, 30), _A, _B), // icbi + + // A-27 + MNEMONIC(31, InsertVal(595, 21, 30), _D, _SR), // mfsr + MNEMONIC(31, InsertVal(659, 21, 30), _D, _B), // mfsrin + MNEMONIC(31, InsertVal(210, 21, 30), _SR, _S), // mtsr + MNEMONIC(31, InsertVal(242, 21, 30), _S, _B), // mtsrin + + // A-28 + MNEMONIC(31, InsertVal(306, 21, 30), _B), // tlbie + MNEMONIC(31, InsertVal(566, 21, 30)), // tlbsync + + // A-29 + MNEMONIC(31, InsertVal(310, 21, 30), _D, _A, _B), // eciwx + MNEMONIC(31, InsertVal(438, 21, 30), _S, _A, _B), // ecowx + + // A-30 + MNEMONIC(4, InsertVal(6, 25, 30), _D, _A, _B, _W2, _I2), // psq_lx + MNEMONIC(4, InsertVal(7, 25, 30), _S, _A, _B, _W2, _I2), // psq_stx + MNEMONIC(4, InsertVal(38, 25, 30), _D, _A, _B, _W2, _I2), // psq_lux + MNEMONIC(4, InsertVal(39, 25, 30), _S, _A, _B, _W2, _I2), // psq_stux + BASIC_MNEMONIC(56, _D, _OffdPs, _A, _W1, _I1), // psq_l + BASIC_MNEMONIC(57, _D, _OffdPs, _A, _W1, _I1), // psq_lu + BASIC_MNEMONIC(60, _S, _OffdPs, _A, _W1, _I1), // psq_st + BASIC_MNEMONIC(61, _S, _OffdPs, _A, _W1, _I1), // psq_stu + + // A-31 + RC_MNEMONIC(4, InsertVal(18, 26, 30), _D, _A, _B), // ps_div + RC_MNEMONIC(4, InsertVal(20, 26, 30), _D, _A, _B), // ps_sub + RC_MNEMONIC(4, InsertVal(21, 26, 30), _D, _A, _B), // ps_add + RC_MNEMONIC(4, InsertVal(23, 26, 30), _D, _A, _C, _B), // ps_sel + RC_MNEMONIC(4, InsertVal(24, 26, 30), _D, _B), // ps_res + RC_MNEMONIC(4, InsertVal(25, 26, 30), _D, _A, _C), // ps_mul + RC_MNEMONIC(4, InsertVal(26, 26, 30), _D, _B), // ps_rsqrte + RC_MNEMONIC(4, InsertVal(28, 26, 30), _D, _A, _C, _B), // ps_msub + RC_MNEMONIC(4, InsertVal(29, 26, 30), _D, _A, _C, _B), // ps_madd + RC_MNEMONIC(4, InsertVal(30, 26, 30), _D, _A, _C, _B), // ps_nmsub + RC_MNEMONIC(4, InsertVal(31, 26, 30), _D, _A, _C, _B), // ps_nmadd + RC_MNEMONIC(4, InsertVal(40, 21, 30), _D, _B), // ps_neg + RC_MNEMONIC(4, InsertVal(72, 21, 30), _D, _B), // ps_mr + RC_MNEMONIC(4, InsertVal(136, 21, 30), _D, _B), // ps_nabs + RC_MNEMONIC(4, InsertVal(264, 21, 30), _D, _B), // ps_abs + + // A-32 + RC_MNEMONIC(4, InsertVal(10, 26, 30), _D, _A, _C, _B), // ps_sum0 + RC_MNEMONIC(4, InsertVal(11, 26, 30), _D, _A, _C, _B), // ps_sum1 + RC_MNEMONIC(4, InsertVal(12, 26, 30), _D, _A, _C), // ps_muls0 + RC_MNEMONIC(4, InsertVal(13, 26, 30), _D, _A, _C), // ps_muls1 + RC_MNEMONIC(4, InsertVal(14, 26, 30), _D, _A, _C, _B), // ps_madds0 + RC_MNEMONIC(4, InsertVal(15, 26, 30), _D, _A, _C, _B), // ps_madds1 + MNEMONIC(4, InsertVal(0, 21, 30), _Crfd, _A, _B), // ps_cmpu0 + MNEMONIC(4, InsertVal(32, 21, 30), _Crfd, _A, _B), // ps_cmpo0 + MNEMONIC(4, InsertVal(64, 21, 30), _Crfd, _A, _B), // ps_cmpu1 + MNEMONIC(4, InsertVal(96, 21, 30), _Crfd, _A, _B), // ps_cmpo1 + RC_MNEMONIC(4, InsertVal(528, 21, 30), _D, _A, _B), // ps_merge00 + RC_MNEMONIC(4, InsertVal(560, 21, 30), _D, _A, _B), // ps_merge01 + RC_MNEMONIC(4, InsertVal(592, 21, 30), _D, _A, _B), // ps_merge10 + RC_MNEMONIC(4, InsertVal(624, 21, 30), _D, _A, _B), // ps_merge11 + MNEMONIC(4, InsertVal(1014, 21, 30), _A, _B), // dcbz_l +}; + +namespace +{ +// Reused operand translators for extended mnemonics +void NegateSIMM(OperandList& operands) +{ + operands[2] = static_cast(-static_cast(operands[2])); +} + +void SwapOps1And2(OperandList& operands) +{ + std::swap(operands[1], operands[2]); +} + +void SetCompareWordMode(OperandList& operands) +{ + if (operands.count == 2) + { + operands.Insert(0, 0); + } + operands.Insert(1, 0); +} + +template +void FillBOBI(OperandList& operands) +{ + operands.Insert(0, BO); + operands.Insert(1, BI); +} + +template +void BitswapIdx(OperandList& operands) +{ + operands[Idx] = SprBitswap(operands[Idx]); +} + +template +void FillBOBICond(OperandList& operands) +{ + if (operands.count < ParamCount) + { + operands.Insert(0, 0); + } + operands[0] = (operands[0] << 2) | Cond; + operands.Insert(0, BO); +} + +template +void FillBO(OperandList& operands) +{ + operands.Insert(0, BO); +} + +template +void TrapSetTO(OperandList& operands) +{ + operands.Insert(0, TO); +} + +template +void FillMtspr(OperandList& operands) +{ + operands.Insert(0, SPRG); +} + +template +void FillMfspr(OperandList& operands) +{ + operands.Insert(1, SPRG); +} + +template +void FillMtsprBatAndBitswap(OperandList& operands) +{ + operands[0] = SprBitswap(2 * operands[0] + SPRG); +} + +template +void FillMfsprBatAndBitswap(OperandList& operands) +{ + operands[1] = SprBitswap(2 * operands[1] + SPRG); +} +} // namespace + +#define PSEUDO(base, variant_bits, cb) \ + ExtendedMnemonicDesc { static_cast(base) * VARIANT_PERMUTATIONS + variant_bits, cb } +#define PLAIN_PSEUDO(base, cb) \ + PSEUDO(base, PLAIN_MNEMONIC, cb), INVALID_EXT_MNEMONIC, INVALID_EXT_MNEMONIC, INVALID_EXT_MNEMONIC +#define RC_PSEUDO(base, cb) \ + PSEUDO(base, PLAIN_MNEMONIC, cb), PSEUDO(base, RECORD_BIT, cb), INVALID_EXT_MNEMONIC, \ + INVALID_EXT_MNEMONIC +#define OERC_PSEUDO(base, cb) \ + PSEUDO(base, PLAIN_MNEMONIC, cb), PSEUDO(base, RECORD_BIT, cb), \ + PSEUDO(base, OVERFLOW_EXCEPTION, cb), PSEUDO(base, (RECORD_BIT | OVERFLOW_EXCEPTION), cb) +#define LK_PSEUDO(base, cb) \ + PSEUDO(base, PLAIN_MNEMONIC, cb), PSEUDO(base, LINK_BIT, cb), INVALID_EXT_MNEMONIC, \ + INVALID_EXT_MNEMONIC +#define LKAA_PSEUDO(base, cb) \ + PSEUDO(base, PLAIN_MNEMONIC, cb), PSEUDO(base, LINK_BIT, cb), \ + PSEUDO(base, ABSOLUTE_ADDRESS_BIT, cb), PSEUDO(base, (LINK_BIT | ABSOLUTE_ADDRESS_BIT), cb) + +extern const std::array + extended_mnemonics = { + // E.2.1 + PLAIN_PSEUDO(GekkoMnemonic::Addi, NegateSIMM), // subi + PLAIN_PSEUDO(GekkoMnemonic::Addis, NegateSIMM), // subis + PLAIN_PSEUDO(GekkoMnemonic::Addic, NegateSIMM), // subic + PLAIN_PSEUDO(GekkoMnemonic::AddicDot, NegateSIMM), // subic. + + // E.2.2 + OERC_PSEUDO(GekkoMnemonic::Subf, SwapOps1And2), // sub + OERC_PSEUDO(GekkoMnemonic::Subfc, SwapOps1And2), // subc + + // E.3.2 + PLAIN_PSEUDO(GekkoMnemonic::Cmpi, SetCompareWordMode), // cmpwi + PLAIN_PSEUDO(GekkoMnemonic::Cmp, SetCompareWordMode), // cmpw + PLAIN_PSEUDO(GekkoMnemonic::Cmpli, SetCompareWordMode), // cmplwi + PLAIN_PSEUDO(GekkoMnemonic::Cmpl, SetCompareWordMode), // cmplw + + // E.4.2 + RC_PSEUDO(GekkoMnemonic::Rlwinm, ([](OperandList& operands) { + const u32 n = operands[2], b = operands[3]; + operands[2] = b; + operands[3] = 0; + operands.Insert(4, n - 1); + })), // extlwi + RC_PSEUDO(GekkoMnemonic::Rlwinm, ([](OperandList& operands) { + const u32 n = operands[2], b = operands[3]; + operands[2] = b + n; + operands[3] = 32 - n; + operands.Insert(4, 31); + })), // extrwi + RC_PSEUDO(GekkoMnemonic::Rlwimi, ([](OperandList& operands) { + const u32 n = operands[2], b = operands[3]; + operands[2] = 32 - b; + operands[3] = b; + operands.Insert(4, b + n - 1); + })), // inslwi + RC_PSEUDO(GekkoMnemonic::Rlwimi, ([](OperandList& operands) { + const u32 n = operands[2], b = operands[3]; + operands[2] = 32 - (b + n); + operands[3] = b; + operands.Insert(4, b + n - 1); + })), // insrwi + RC_PSEUDO(GekkoMnemonic::Rlwinm, ([](OperandList& operands) { + operands.Insert(3, 0); + operands.Insert(4, 31); + })), // rotlwi + RC_PSEUDO(GekkoMnemonic::Rlwinm, ([](OperandList& operands) { + const u32 n = operands[2]; + operands[2] = 32 - n; + operands.Insert(3, 0); + operands.Insert(4, 31); + })), // rotrwi + RC_PSEUDO(GekkoMnemonic::Rlwnm, ([](OperandList& operands) { + operands.Insert(3, 0); + operands.Insert(4, 31); + })), // rotlw + RC_PSEUDO(GekkoMnemonic::Rlwinm, ([](OperandList& operands) { + const u32 n = operands[2]; + operands.Insert(3, 0); + operands.Insert(4, 31 - n); + })), // slwi + RC_PSEUDO(GekkoMnemonic::Rlwinm, ([](OperandList& operands) { + const u32 n = operands[2]; + operands[2] = 32 - n; + operands.Insert(3, n); + operands.Insert(4, 31); + })), // srwi + RC_PSEUDO(GekkoMnemonic::Rlwinm, ([](OperandList& operands) { + const u32 n = operands[2]; + operands[2] = 0; + operands.Insert(3, n); + operands.Insert(4, 31); + })), // clrlwi + RC_PSEUDO(GekkoMnemonic::Rlwinm, ([](OperandList& operands) { + const u32 n = operands[2]; + operands[2] = 0; + operands.Insert(3, 0); + operands.Insert(4, 31 - n); + })), // clrrwi + RC_PSEUDO(GekkoMnemonic::Rlwinm, ([](OperandList& operands) { + const u32 b = operands[2], n = operands[3]; + operands[2] = n; + operands[3] = b - n; + operands.Insert(4, 31 - n); + })), // clrlslwi + + // E.5.2 + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<12>)), // bt + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<4>)), // bf + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBI<16, 0>)), // bdnz + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<8>)), // bdnzt + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<0>)), // bdnzf + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBI<18, 0>)), // bdz + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<10>)), // bdzt + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<2>)), // bdzf + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<13>)), // bt+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<5>)), // bf+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBI<17, 0>)), // bdnz+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<9>)), // bdnzt+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<1>)), // bdnzf+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBI<19, 0>)), // bdz+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<11>)), // bdzt+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBO<3>)), // bdzf+ + + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBI<20, 0>)), // blr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<12>)), // btlr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<4>)), // bflr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBI<16, 0>)), // bdnzlr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<8>)), // bdnztlr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<0>)), // bdnzflr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBI<18, 0>)), // bdzlr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<10>)), // bdztlr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<2>)), // bdzflr + + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<13>)), // btlr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<5>)), // bflr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBI<17, 0>)), // bdnzlr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<9>)), // bdnztlr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<1>)), // bdnzflr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBI<19, 0>)), // bdzlr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<11>)), // bdztlr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBO<3>)), // bdzflr+ + + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBI<20, 0>)), // bctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBO<12>)), // btctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBO<4>)), // bfctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBO<13>)), // btctr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBO<5>)), // bfctr+ + + // E.5.3 + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<12, 0, 2>)), // blt + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<4, 1, 2>)), // ble + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<12, 2, 2>)), // beq + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<4, 0, 2>)), // bge + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<12, 1, 2>)), // bgt + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<4, 0, 2>)), // bnl + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<4, 2, 2>)), // bne + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<4, 1, 2>)), // bng + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<12, 3, 2>)), // bso + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<4, 3, 2>)), // bns + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<12, 3, 2>)), // bun + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<4, 3, 2>)), // bnu + + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<13, 0, 2>)), // blt+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<5, 1, 2>)), // ble+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<13, 2, 2>)), // beq+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<5, 0, 2>)), // bge+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<13, 1, 2>)), // bgt+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<5, 0, 2>)), // bnl+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<5, 2, 2>)), // bne+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<5, 1, 2>)), // bng+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<13, 3, 2>)), // bso+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<5, 3, 2>)), // bns+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<13, 3, 2>)), // bun+ + LKAA_PSEUDO(GekkoMnemonic::Bc, (FillBOBICond<5, 3, 2>)), // bnu+ + + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<12, 0, 1>)), // bltlr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<4, 1, 1>)), // blelr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<12, 2, 1>)), // beqlr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<4, 0, 1>)), // bgelr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<12, 1, 1>)), // bgtlr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<4, 0, 1>)), // bnllr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<4, 2, 1>)), // bnelr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<4, 1, 1>)), // bnglr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<12, 3, 1>)), // bsolr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<4, 3, 1>)), // bnslr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<12, 3, 1>)), // bunlr + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<4, 3, 1>)), // bnulr + + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<13, 0, 1>)), // bltlr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<5, 1, 1>)), // blelr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<13, 2, 1>)), // beqlr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<5, 0, 1>)), // bgelr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<13, 1, 1>)), // bgtlr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<5, 0, 1>)), // bnllr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<5, 2, 1>)), // bnelr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<5, 1, 1>)), // bnglr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<13, 3, 1>)), // bsolr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<5, 3, 1>)), // bnslr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<13, 3, 1>)), // bunlr+ + LK_PSEUDO(GekkoMnemonic::Bclr, (FillBOBICond<5, 3, 1>)), // bnulr+ + + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<12, 0, 1>)), // bltctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<4, 1, 1>)), // blectr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<12, 2, 1>)), // beqctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<4, 0, 1>)), // bgectr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<12, 1, 1>)), // bgtctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<4, 0, 1>)), // bnlctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<4, 2, 1>)), // bnectr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<4, 1, 1>)), // bngctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<12, 3, 1>)), // bsoctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<4, 3, 1>)), // bnsctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<12, 3, 1>)), // bunctr + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<4, 3, 1>)), // bnuctr + + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<13, 0, 1>)), // bltctr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<5, 1, 1>)), // blectr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<13, 2, 1>)), // beqctr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<5, 0, 1>)), // bgectr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<13, 1, 1>)), // bgtctr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<5, 0, 1>)), // bnlctr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<5, 2, 1>)), // bnectr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<5, 1, 1>)), // bngctr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<13, 3, 1>)), // bsoctr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<5, 3, 1>)), // bnsctr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<13, 3, 1>)), // bunctr+ + LK_PSEUDO(GekkoMnemonic::Bcctr, (FillBOBICond<5, 3, 1>)), // bnuctr+ + + // E.6 + PLAIN_PSEUDO(GekkoMnemonic::Creqv, + [](OperandList& operands) { + operands.Insert(1, operands[0]); + operands.Insert(2, operands[0]); + }), // crset + PLAIN_PSEUDO(GekkoMnemonic::Crxor, + [](OperandList& operands) { + operands.Insert(1, operands[0]); + operands.Insert(2, operands[0]); + }), // crclr + PLAIN_PSEUDO(GekkoMnemonic::Cror, + [](OperandList& operands) { operands.Insert(2, operands[1]); }), // crmove + PLAIN_PSEUDO(GekkoMnemonic::Crnor, + [](OperandList& operands) { operands.Insert(2, operands[1]); }), // crnot + + // E.7 + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<16>), // twlt + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<16>), // twlti + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<20>), // twle + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<20>), // twlei + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<4>), // tweq + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<4>), // tweqi + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<12>), // twge + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<12>), // twgei + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<8>), // twgt + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<8>), // twgti + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<12>), // twnl + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<12>), // twnli + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<24>), // twne + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<24>), // twnei + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<20>), // twng + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<20>), // twngi + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<2>), // twllt + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<2>), // twllti + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<6>), // twlle + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<6>), // twllei + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<5>), // twlge + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<5>), // twlgei + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<1>), // twlgt + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<1>), // twlgti + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<5>), // twlnl + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<5>), // twlnli + PLAIN_PSEUDO(GekkoMnemonic::Tw, TrapSetTO<6>), // twlng + PLAIN_PSEUDO(GekkoMnemonic::Twi, TrapSetTO<6>), // twlngi + PLAIN_PSEUDO(GekkoMnemonic::Tw, + [](OperandList& operands) { + operands.Insert(0, 31); + operands.Insert(1, 0); + operands.Insert(2, 0); + }), // trap + + // E.8 + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtxer + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mfxer + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtlr + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mflr + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtctr + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mfctr + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtdsisr + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mfdsisr + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtdar + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mfdar + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtdec + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mfdec + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtsdr1 + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mfsdr1 + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtsrr0 + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mfsrr0 + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtsrr1 + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mfsrr1 + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtasr + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mfasr + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mtear + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr), // mfear + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mttbl + PLAIN_PSEUDO(GekkoMnemonic::Mftb_nobitswap, FillMfspr), // mftbl + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr), // mttbu + PLAIN_PSEUDO(GekkoMnemonic::Mftb_nobitswap, FillMfspr), // mftbu + PLAIN_PSEUDO( + GekkoMnemonic::Mtspr_nobitswap, + [](OperandList& operands) { operands[0] = SprBitswap(operands[0] + 272); }), // mtsprg + PLAIN_PSEUDO( + GekkoMnemonic::Mfspr_nobitswap, + [](OperandList& operands) { operands[1] = SprBitswap(operands[1] + 272); }), // mfsprg + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtsprBatAndBitswap<528>), // mtibatu + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfsprBatAndBitswap<528>), // mfibatu + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtsprBatAndBitswap<529>), // mtibatl + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfsprBatAndBitswap<529>), // mfibatl + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtsprBatAndBitswap<536>), // mtdbatu + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfsprBatAndBitswap<536>), // mfdbatu + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtsprBatAndBitswap<537>), // mtdbatl + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfsprBatAndBitswap<537>), // mfdbatl + + // E.9 + PLAIN_PSEUDO(GekkoMnemonic::Ori, + [](OperandList& operands) { + operands.Insert(0, 0); + operands.Insert(1, 0); + operands.Insert(2, 0); + }), // nop + PLAIN_PSEUDO(GekkoMnemonic::Addi, + [](OperandList& operands) { operands.Insert(1, 0); }), // li + PLAIN_PSEUDO(GekkoMnemonic::Addis, + [](OperandList& operands) { operands.Insert(1, 0); }), // lis + PLAIN_PSEUDO(GekkoMnemonic::Addi, SwapOps1And2), // la + RC_PSEUDO(GekkoMnemonic::Or, + ([](OperandList& operands) { operands.Insert(2, operands[1]); })), // mr + RC_PSEUDO(GekkoMnemonic::Nor, + ([](OperandList& operands) { operands.Insert(2, operands[1]); })), // not + PLAIN_PSEUDO(GekkoMnemonic::Mtcrf, + [](OperandList& operands) { operands.Insert(0, 0xff); }), // mtcr + + // Additional mnemonics + PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, BitswapIdx<1>), // mfspr + PLAIN_PSEUDO(GekkoMnemonic::Mftb_nobitswap, BitswapIdx<1>), // mfspr + PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, BitswapIdx<0>), // mtspr +}; + +#undef EMIT_MNEMONIC_ENTRY +#undef MNEMONIC +#undef BASIC_MNEMONIC +#undef RC_MNEMONIC +#undef OERC_MNEMONIC +#undef LK_MNEMONIC +#undef AALK_MNEMONIC +#undef PSEUDO +#undef PLAIN_PSEUDO +#undef RC_PSEUDO +#undef OERC_PSEUDO +#undef LK_PSEUDO +#undef LKAA_PSEUDO + +////////////////// +// LEXER TABLES // +////////////////// + +namespace +{ +constexpr TransitionF HasPlusOrMinus = [](char c) { return c == '+' || c == '-'; }; +constexpr TransitionF HasDigit = [](char c) -> bool { return std::isdigit(c); }; +constexpr TransitionF HasE = [](char c) { return c == 'e'; }; +constexpr TransitionF HasDot = [](char c) { return c == '.'; }; + +// Normal string characters +constexpr TransitionF HasNormal = [](char c) { return c != '\n' && c != '"' && c != '\\'; }; +// Invalid characters in string +constexpr TransitionF HasInvalid = [](char c) { return c == '\n'; }; +// Octal digits +constexpr TransitionF HasOctal = [](char c) { return c >= '0' && c <= '7'; }; +// Hex digits +constexpr TransitionF HasHex = [](char c) -> bool { return std::isxdigit(c); }; +// Normal - octal +constexpr TransitionF HasNormalMinusOctal = [](char c) { return HasNormal(c) && !HasOctal(c); }; +// Normal - hex +constexpr TransitionF HasNormalMinusHex = [](char c) { return HasNormal(c) && !HasHex(c); }; +// Escape start +constexpr TransitionF HasEscape = [](char c) { return c == '\\'; }; +// All single-character escapes +constexpr TransitionF HasSCE = [](char c) { return !HasOctal(c) && c != 'x' && c != '\n'; }; +// Hex escape +constexpr TransitionF HasHexStart = [](char c) { return c == 'x'; }; +constexpr TransitionF HasQuote = [](char c) { return c == '"'; }; +} // namespace + +extern const std::vector float_dfa = { + {{DfaEdge(HasPlusOrMinus, 1), DfaEdge(HasDigit, 2), DfaEdge(HasDot, 5)}, + "Invalid float: No numeric value"}, + + {{DfaEdge(HasDigit, 2), DfaEdge(HasDot, 5)}, "Invalid float: No numeric value"}, + + {{DfaEdge(HasDigit, 2), DfaEdge(HasDot, 3), DfaEdge(HasE, 7)}, std::nullopt}, + {{DfaEdge(HasDigit, 4)}, "Invalid float: No numeric value after decimal point"}, + {{DfaEdge(HasDigit, 4), DfaEdge(HasE, 7)}, std::nullopt}, + + {{DfaEdge(HasDigit, 6)}, "Invalid float: No numeric value after decimal point"}, + {{DfaEdge(HasDigit, 6), DfaEdge(HasE, 7)}, std::nullopt}, + + {{DfaEdge(HasDigit, 9), DfaEdge(HasPlusOrMinus, 8)}, + "Invalid float: No numeric value following exponent signifier"}, + {{DfaEdge(HasDigit, 9)}, "Invalid float: No numeric value following exponent signifier"}, + {{DfaEdge(HasDigit, 9)}, std::nullopt}, +}; + +extern const std::vector string_dfa = { + // Base character check + {{DfaEdge(HasNormal, 0), DfaEdge(HasInvalid, 1), DfaEdge(HasQuote, 2), DfaEdge(HasEscape, 3)}, + "Invalid string: No terminating \""}, + + // Invalid (unescaped newline) + {{}, "Invalid string: No terminating \""}, + // String end + {{}, std::nullopt}, + + // Escape character breakout + {{DfaEdge(HasSCE, 0), DfaEdge(HasInvalid, 1), DfaEdge(HasOctal, 4), DfaEdge(HasHexStart, 6)}, + "Invalid string: No terminating \""}, + + // Octal characters, at most 3 + {{DfaEdge(HasNormalMinusOctal, 0), DfaEdge(HasInvalid, 1), DfaEdge(HasQuote, 2), + DfaEdge(HasEscape, 3), DfaEdge(HasOctal, 5)}, + "Invalid string: No terminating \""}, + {{DfaEdge(HasNormal, 0), DfaEdge(HasInvalid, 1), DfaEdge(HasQuote, 2), DfaEdge(HasEscape, 3)}, + "Invalid string: No terminating \""}, + + // Hex characters, 1 or more + {{DfaEdge(HasHex, 7)}, "Invalid string: bad hex escape"}, + {{DfaEdge(HasNormalMinusHex, 0), DfaEdge(HasInvalid, 1), DfaEdge(HasQuote, 2), + DfaEdge(HasEscape, 3), DfaEdge(HasHex, 7)}, + "Invalid string: No terminating \""}, +}; +} // namespace Common::GekkoAssembler::detail diff --git a/Source/Core/Common/Assembler/AssemblerTables.h b/Source/Core/Common/Assembler/AssemblerTables.h new file mode 100644 index 0000000000..ed084208e4 --- /dev/null +++ b/Source/Core/Common/Assembler/AssemblerTables.h @@ -0,0 +1,152 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common/Assembler/AssemblerShared.h" +#include "Common/Assembler/CaseInsensitiveDict.h" +#include "Common/CommonTypes.h" + +namespace Common::GekkoAssembler::detail +{ +/////////////////// +// PARSER TABLES // +/////////////////// +enum class ParseAlg +{ + None, + Op1, + NoneOrOp1, + Op1Off1, + Op2, + Op1Or2, + Op3, + Op2Or3, + Op4, + Op5, + Op1Off1Op2, +}; + +struct ParseInfo +{ + size_t mnemonic_index; + ParseAlg parse_algorithm; +}; + +// Mapping of SPRG names to values +extern const CaseInsensitiveDict sprg_map; +// Mapping of directive names to an enumeration +extern const CaseInsensitiveDict directives_map; +// Mapping of normal Gekko mnemonics to their index and argument form +extern const CaseInsensitiveDict mnemonic_tokens; +// Mapping of extended Gekko mnemonics to their index and argument form +extern const CaseInsensitiveDict extended_mnemonic_tokens; + +////////////////////// +// ASSEMBLER TABLES // +////////////////////// +constexpr size_t MAX_OPERANDS = 5; + +struct OperandList +{ + std::array, MAX_OPERANDS> list; + u32 count; + bool overfill; + + constexpr u32 operator[](size_t index) const { return ValueOf(list[index]); } + constexpr u32& operator[](size_t index) { return ValueOf(list[index]); } + + void Insert(size_t before, u32 val); + + template + void Copy(It begin, It end) + { + count = 0; + for (auto& i : list) + { + if (begin == end) + { + break; + } + i = *begin; + begin++; + count++; + } + overfill = begin != end; + } +}; + +struct OperandDesc +{ + u32 mask; + struct + { + u32 shift : 31; + bool is_signed : 1; + }; + u32 MaxVal() const; + u32 MinVal() const; + u32 TruncBits() const; + + bool Fits(u32 val) const; + u32 Fit(u32 val) const; +}; + +// MnemonicDesc holds the machine-code template for mnemonics +struct MnemonicDesc +{ + // Initial value for a given mnemonic (opcode, func code, LK, AA, OE) + const u32 initial_value; + const u32 operand_count; + // Masks for operands + std::array operand_masks; +}; + +// ExtendedMnemonicDesc holds the name of the mnemonic it transforms to as well as a +// transformer callback to translate the operands into the correct form for the base mnemonic +struct ExtendedMnemonicDesc +{ + size_t mnemonic_index; + void (*transform_operands)(OperandList&); +}; + +static constexpr size_t NUM_MNEMONICS = static_cast(GekkoMnemonic::LastMnemonic) + 1; +static constexpr size_t NUM_EXT_MNEMONICS = + static_cast(ExtendedGekkoMnemonic::LastMnemonic) + 1; +static constexpr size_t VARIANT_PERMUTATIONS = 4; + +// Table for mapping mnemonic+variants to their descriptors +extern const std::array mnemonics; +// Table for mapping extended mnemonic+variants to their descriptors +extern const std::array + extended_mnemonics; + +////////////////// +// LEXER TABLES // +////////////////// + +// In place of the reliace on std::regex, DFAs will be defined for matching sufficiently complex +// tokens This gives an extra benefit of providing reasons for match failures +using TransitionF = bool (*)(char c); +using DfaEdge = std::pair; +struct DfaNode +{ + std::vector edges; + // If nullopt: this is a final node + // If string: invalid reason + std::optional match_failure_reason; +}; + +// Floating point strings that will be accepted by std::stof/std::stod +// regex: [\+-]?(\d+(\.\d+)?|\.\d+)(e[\+-]?\d+)? +extern const std::vector float_dfa; +// C-style strings +// regex: "([^\\\n]|\\([0-7]{1,3}|x[0-9a-fA-F]+|[^x0-7\n]))*" +extern const std::vector string_dfa; +} // namespace Common::GekkoAssembler::detail diff --git a/Source/Core/Common/Assembler/CaseInsensitiveDict.h b/Source/Core/Common/Assembler/CaseInsensitiveDict.h new file mode 100644 index 0000000000..8f6acccc8f --- /dev/null +++ b/Source/Core/Common/Assembler/CaseInsensitiveDict.h @@ -0,0 +1,126 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Common::GekkoAssembler::detail +{ +// Hacky implementation of a case insensitive alphanumeric trie supporting extended entries +// Standing in for std::map to support case-insensitive lookups while allowing string_views in +// lookups +template +class CaseInsensitiveDict +{ +public: + CaseInsensitiveDict(const std::initializer_list>& il) + { + for (auto&& [k, v] : il) + { + Add(k, v); + } + } + + template + V const* Find(const T& key) const + { + auto&& [last_e, it] = TryFind(key); + if (it == key.cend() && last_e->_val) + { + return &*last_e->_val; + } + return nullptr; + } + static constexpr size_t NUM_CONNS = 36 + sizeof...(ExtraMatches); + static constexpr uint32_t INVALID_CONN = static_cast(-1); + +private: + struct TrieEntry + { + std::array _conns; + std::optional _val; + + TrieEntry() { std::fill(_conns.begin(), _conns.end(), INVALID_CONN); } + }; + + constexpr size_t IndexOf(char c) const + { + size_t idx; + if (std::isalpha(c)) + { + idx = std::tolower(c) - 'a'; + } + else if (std::isdigit(c)) + { + idx = c - '0' + 26; + } + else + { + idx = 36; + // Expands to an equivalent for loop over ExtraMatches + if constexpr (sizeof...(ExtraMatches) > 0) + { + (void)((c != ExtraMatches ? ++idx, true : false) && ...); + } + } + return idx; + } + + template + auto TryFind(const T& key) const -> std::pair + { + std::pair ret(&m_root_entry, key.cbegin()); + const auto k_end = key.cend(); + + for (; ret.second != k_end; ret.second++) + { + const size_t idx = IndexOf(*ret.second); + if (idx >= NUM_CONNS || ret.first->_conns[idx] == INVALID_CONN) + { + break; + } + + ret.first = &m_entry_pool[ret.first->_conns[idx]]; + } + + return ret; + } + + template + auto TryFind(const T& key) -> std::pair + { + auto&& [e_const, it] = + const_cast const*>(this)->TryFind(key); + return {const_cast(e_const), it}; + } + + void Add(std::string_view key, const V& val) + { + auto&& [last_e, it] = TryFind(key); + if (it != key.cend()) + { + for (; it != key.cend(); it++) + { + const size_t idx = IndexOf(*it); + if (idx >= NUM_CONNS) + { + break; + } + last_e->_conns[idx] = static_cast(m_entry_pool.size()); + last_e = &m_entry_pool.emplace_back(); + } + } + last_e->_val = val; + } + + TrieEntry m_root_entry; + std::vector m_entry_pool; +}; +} // namespace Common::GekkoAssembler::detail diff --git a/Source/Core/Common/Assembler/GekkoAssembler.cpp b/Source/Core/Common/Assembler/GekkoAssembler.cpp new file mode 100644 index 0000000000..4ee97c59d9 --- /dev/null +++ b/Source/Core/Common/Assembler/GekkoAssembler.cpp @@ -0,0 +1,189 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Common/Assembler/GekkoAssembler.h" + +#include +#include +#include +#include + +#include + +#include "Common/Assembler/AssemblerShared.h" +#include "Common/Assembler/AssemblerTables.h" +#include "Common/Assembler/GekkoIRGen.h" +#include "Common/Assert.h" +#include "Common/CommonTypes.h" + +namespace Common::GekkoAssembler +{ +namespace +{ +using namespace Common::GekkoAssembler::detail; + +FailureOr FillInstruction(const MnemonicDesc& desc, const OperandList& operands, + std::string_view inst_line) +{ + // Parser shouldn't allow this to pass + ASSERT_MSG(COMMON, desc.operand_count == operands.count && !operands.overfill, + "Unexpected operand count mismatch for instruction {}. Expected {} but found {}", + inst_line, desc.operand_count, operands.overfill ? 6 : operands.count); + + u32 instruction = desc.initial_value; + for (u32 i = 0; i < operands.count; i++) + { + if (!desc.operand_masks[i].Fits(operands[i])) + { + std::string message; + const u32 trunc_bits = desc.operand_masks[i].TruncBits(); + if (trunc_bits == 0) + { + if (desc.operand_masks[i].is_signed) + { + message = fmt::format("{:#x} not between {:#x} and {:#x}", static_cast(operands[i]), + static_cast(desc.operand_masks[i].MinVal()), + static_cast(desc.operand_masks[i].MaxVal())); + } + else + { + message = fmt::format("{:#x} not between {:#x} and {:#x}", operands[i], + desc.operand_masks[i].MinVal(), desc.operand_masks[i].MaxVal()); + } + } + else + { + if (desc.operand_masks[i].is_signed) + { + message = fmt::format("{:#x} not between {:#x} and {:#x} or not aligned to {}", + static_cast(operands[i]), + static_cast(desc.operand_masks[i].MinVal()), + static_cast(desc.operand_masks[i].MaxVal()), trunc_bits + 1); + } + else + { + message = fmt::format("{:#x} not between {:#x} and {:#x} or not aligned to {}", + operands[i], desc.operand_masks[i].MinVal(), + desc.operand_masks[i].MaxVal(), trunc_bits + 1); + } + } + return AssemblerError{std::move(message), "", 0, TagOf(operands.list[i]).begin, + TagOf(operands.list[i]).len}; + } + instruction |= desc.operand_masks[i].Fit(operands[i]); + } + return instruction; +} + +void AdjustOperandsForGas(GekkoMnemonic mnemonic, OperandList& ops_list) +{ + switch (mnemonic) + { + case GekkoMnemonic::Cmp: + case GekkoMnemonic::Cmpl: + case GekkoMnemonic::Cmpi: + case GekkoMnemonic::Cmpli: + if (ops_list.count < 4) + { + ops_list.Insert(0, 0); + } + break; + + case GekkoMnemonic::Addis: + // Because GAS wants to allow for addis and lis to work nice with absolute addresses, the + // immediate operand should also "fit" into the _UIMM field, so just turn a valid UIMM into a + // SIMM + if (ops_list[2] >= 0x8000 && ops_list[2] <= 0xffff) + { + ops_list[2] = ops_list[2] - 0x10000; + } + break; + + default: + break; + } +} + +} // namespace + +void CodeBlock::PushBigEndian(u32 val) +{ + instructions.push_back((val >> 24) & 0xff); + instructions.push_back((val >> 16) & 0xff); + instructions.push_back((val >> 8) & 0xff); + instructions.push_back(val & 0xff); +} + +FailureOr> Assemble(std::string_view instruction, + u32 current_instruction_address) +{ + FailureOr parse_result = + detail::ParseToIR(instruction, current_instruction_address); + if (IsFailure(parse_result)) + { + return GetFailure(parse_result); + } + + const auto& parsed_blocks = GetT(parse_result).blocks; + const auto& operands = GetT(parse_result).operand_pool; + std::vector out_blocks; + + for (const detail::IRBlock& parsed_block : parsed_blocks) + { + CodeBlock new_block(parsed_block.block_address); + for (const detail::ChunkVariant& chunk : parsed_block.chunks) + { + if (std::holds_alternative(chunk)) + { + for (const detail::GekkoInstruction& parsed_inst : std::get(chunk)) + { + OperandList adjusted_ops; + ASSERT(parsed_inst.op_interval.len <= MAX_OPERANDS); + adjusted_ops.Copy(operands.begin() + parsed_inst.op_interval.begin, + operands.begin() + parsed_inst.op_interval.End()); + + size_t idx = parsed_inst.mnemonic_index; + if (parsed_inst.is_extended) + { + extended_mnemonics[idx].transform_operands(adjusted_ops); + idx = extended_mnemonics[idx].mnemonic_index; + } + + AdjustOperandsForGas(static_cast(idx >> 2), adjusted_ops); + + FailureOr inst = FillInstruction(mnemonics[idx], adjusted_ops, parsed_inst.raw_text); + if (IsFailure(inst)) + { + GetFailure(inst).error_line = parsed_inst.raw_text; + GetFailure(inst).line = parsed_inst.line_number; + return GetFailure(inst); + } + + new_block.PushBigEndian(GetT(inst)); + } + } + else if (std::holds_alternative(chunk)) + { + detail::ByteChunk byte_arr = std::get(chunk); + new_block.instructions.insert(new_block.instructions.end(), byte_arr.begin(), + byte_arr.end()); + } + else if (std::holds_alternative(chunk)) + { + detail::PadChunk pad_len = std::get(chunk); + new_block.instructions.insert(new_block.instructions.end(), pad_len, 0); + } + else + { + ASSERT(false); + } + } + + if (!new_block.instructions.empty()) + { + out_blocks.emplace_back(std::move(new_block)); + } + } + return out_blocks; +} +} // namespace Common::GekkoAssembler diff --git a/Source/Core/Common/Assembler/GekkoAssembler.h b/Source/Core/Common/Assembler/GekkoAssembler.h new file mode 100644 index 0000000000..ede99532f3 --- /dev/null +++ b/Source/Core/Common/Assembler/GekkoAssembler.h @@ -0,0 +1,29 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/Assembler/AssemblerShared.h" +#include "Common/CommonTypes.h" + +namespace Common::GekkoAssembler +{ +struct CodeBlock +{ + CodeBlock(u32 address) : block_address(address) {} + + void PushBigEndian(u32 val); + + u32 block_address; + std::vector instructions; +}; + +// Common::GekkoAssember::Assemble - Core routine for assembling Gekko/Broadway instructions +// Supports the full Gekko ISA, as well as the extended mnemonics defined by the book "PowerPC +// Microprocessor Family: The Programming Environments" The input assembly is fully parsed and +// assembled with a base address specified by the base_virtual_address +FailureOr> Assemble(std::string_view assembly, u32 base_virtual_address); +} // namespace Common::GekkoAssembler diff --git a/Source/Core/Common/Assembler/GekkoIRGen.cpp b/Source/Core/Common/Assembler/GekkoIRGen.cpp new file mode 100644 index 0000000000..72c284a15e --- /dev/null +++ b/Source/Core/Common/Assembler/GekkoIRGen.cpp @@ -0,0 +1,832 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Common/Assembler/GekkoIRGen.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Common/Assembler/AssemblerShared.h" +#include "Common/Assembler/GekkoParser.h" +#include "Common/Assert.h" +#include "Common/BitUtils.h" + +namespace Common::GekkoAssembler::detail +{ +namespace +{ +class GekkoIRPlugin : public ParsePlugin +{ +public: + GekkoIRPlugin(GekkoIR& result, u32 base_addr) + : m_output_result(result), m_active_var(nullptr), m_operand_scan_begin(0) + { + m_active_block = &m_output_result.blocks.emplace_back(base_addr); + } + virtual ~GekkoIRPlugin() = default; + + void OnDirectivePre(GekkoDirective directive) override; + void OnDirectivePost(GekkoDirective directive) override; + void OnInstructionPre(const ParseInfo& mnemonic_info, bool extended) override; + void OnInstructionPost(const ParseInfo& mnemonic_info, bool extended) override; + void OnOperandPre() override; + void OnOperandPost() override; + void OnResolvedExprPost() override; + void OnOperator(AsmOp operation) override; + void OnTerminal(Terminal type, const AssemblerToken& val) override; + void OnHiaddr(std::string_view id) override; + void OnLoaddr(std::string_view id) override; + void OnCloseParen(ParenType type) override; + void OnLabelDecl(std::string_view name) override; + void OnVarDecl(std::string_view name) override; + void PostParseAction() override; + + u32 CurrentAddress() const; + std::optional LookupVar(std::string_view lab); + std::optional LookupLabel(std::string_view lab); + + template + T& GetChunk(); + + template + void AddBytes(T val); + + void AddStringBytes(std::string_view str, bool null_term); + + void PadAlign(u32 bits); + void PadSpace(size_t space); + + void StartBlock(u32 address); + void StartBlockAlign(u32 bits); + void StartInstruction(size_t mnemonic_index, bool extended); + void FinishInstruction(); + void SaveOperandFixup(size_t str_left, size_t str_right); + + void AddBinaryEvaluator(u32 (*evaluator)(u32, u32)); + void AddUnaryEvaluator(u32 (*evaluator)(u32)); + void AddAbsoluteAddressConv(); + void AddLiteral(u32 lit); + void AddSymbolResolve(std::string_view sym, bool absolute); + + void RunFixups(); + + void EvalOperatorRel(AsmOp operation); + void EvalOperatorAbs(AsmOp operation); + void EvalTerminalRel(Terminal type, const AssemblerToken& tok); + void EvalTerminalAbs(Terminal type, const AssemblerToken& tok); + +private: + enum class EvalMode + { + RelAddrDoublePass, + AbsAddrSinglePass, + }; + + GekkoIR& m_output_result; + + IRBlock* m_active_block; + GekkoInstruction m_build_inst; + u64* m_active_var; + size_t m_operand_scan_begin; + + std::map> m_labels; + std::map> m_constants; + std::set m_symset; + + EvalMode m_evaluation_mode; + + // For operand parsing + std::stack> m_fixup_stack; + std::vector> m_operand_fixups; + size_t m_operand_str_start; + + // For directive parsing + std::vector m_eval_stack; + std::variant, std::vector> m_floats_list; + std::string_view m_string_lit; + GekkoDirective m_active_directive; +}; + +/////////////// +// OVERRIDES // +/////////////// + +void GekkoIRPlugin::OnDirectivePre(GekkoDirective directive) +{ + m_evaluation_mode = EvalMode::AbsAddrSinglePass; + m_active_directive = directive; + m_eval_stack = std::vector{}; + + switch (directive) + { + case GekkoDirective::Float: + m_floats_list = std::vector{}; + break; + case GekkoDirective::Double: + m_floats_list = std::vector{}; + break; + default: + break; + } +} + +void GekkoIRPlugin::OnDirectivePost(GekkoDirective directive) +{ + switch (directive) + { + // .nbyte directives are handled by OnResolvedExprPost + default: + break; + + case GekkoDirective::Float: + case GekkoDirective::Double: + std::visit( + [this](auto&& vec) { + for (auto&& val : vec) + { + AddBytes(val); + } + }, + m_floats_list); + break; + + case GekkoDirective::DefVar: + ASSERT(m_active_var != nullptr); + *m_active_var = m_eval_stack.back(); + m_active_var = nullptr; + break; + + case GekkoDirective::Locate: + StartBlock(static_cast(m_eval_stack.back())); + break; + + case GekkoDirective::Zeros: + PadSpace(static_cast(m_eval_stack.back())); + break; + + case GekkoDirective::Skip: + { + const u32 skip_len = static_cast(m_eval_stack.back()); + if (skip_len > 0) + { + StartBlock(CurrentAddress() + skip_len); + } + break; + } + + case GekkoDirective::PadAlign: + PadAlign(static_cast(m_eval_stack.back())); + break; + + case GekkoDirective::Align: + StartBlockAlign(static_cast(m_eval_stack.back())); + break; + + case GekkoDirective::Ascii: + AddStringBytes(m_string_lit, false); + break; + + case GekkoDirective::Asciz: + AddStringBytes(m_string_lit, true); + break; + } + m_eval_stack = {}; +} + +void GekkoIRPlugin::OnInstructionPre(const ParseInfo& mnemonic_info, bool extended) +{ + m_evaluation_mode = EvalMode::RelAddrDoublePass; + StartInstruction(mnemonic_info.mnemonic_index, extended); +} + +void GekkoIRPlugin::OnInstructionPost(const ParseInfo&, bool) +{ + FinishInstruction(); +} + +void GekkoIRPlugin::OnOperandPre() +{ + m_operand_str_start = m_owner->lexer.ColNumber(); +} + +void GekkoIRPlugin::OnOperandPost() +{ + SaveOperandFixup(m_operand_str_start, m_owner->lexer.ColNumber()); +} + +void GekkoIRPlugin::OnResolvedExprPost() +{ + switch (m_active_directive) + { + case GekkoDirective::Byte: + AddBytes(static_cast(m_eval_stack.back())); + break; + case GekkoDirective::_2byte: + AddBytes(static_cast(m_eval_stack.back())); + break; + case GekkoDirective::_4byte: + AddBytes(static_cast(m_eval_stack.back())); + break; + case GekkoDirective::_8byte: + AddBytes(static_cast(m_eval_stack.back())); + break; + default: + return; + } + m_eval_stack.clear(); +} + +void GekkoIRPlugin::OnOperator(AsmOp operation) +{ + if (m_evaluation_mode == EvalMode::RelAddrDoublePass) + { + EvalOperatorRel(operation); + } + else + { + EvalOperatorAbs(operation); + } +} + +void GekkoIRPlugin::OnTerminal(Terminal type, const AssemblerToken& val) +{ + if (type == Terminal::Str) + { + m_string_lit = val.token_val; + } + else if (m_evaluation_mode == EvalMode::RelAddrDoublePass) + { + EvalTerminalRel(type, val); + } + else + { + EvalTerminalAbs(type, val); + } +} + +void GekkoIRPlugin::OnHiaddr(std::string_view id) +{ + if (m_evaluation_mode == EvalMode::RelAddrDoublePass) + { + AddSymbolResolve(id, true); + AddLiteral(16); + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs >> rhs; }); + AddLiteral(0xffff); + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs & rhs; }); + } + else + { + u32 base; + if (auto lbl = LookupLabel(id); lbl) + { + base = *lbl; + } + else if (auto var = LookupVar(id); var) + { + base = *var; + } + else + { + m_owner->EmitErrorHere(fmt::format("Undefined reference to Label/Constant '{}'", id)); + return; + } + m_eval_stack.push_back((base >> 16) & 0xffff); + } +} + +void GekkoIRPlugin::OnLoaddr(std::string_view id) +{ + if (m_evaluation_mode == EvalMode::RelAddrDoublePass) + { + AddSymbolResolve(id, true); + AddLiteral(0xffff); + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs & rhs; }); + } + else + { + u32 base; + if (auto lbl = LookupLabel(id); lbl) + { + base = *lbl; + } + else if (auto var = LookupVar(id); var) + { + base = *var; + } + else + { + m_owner->EmitErrorHere(fmt::format("Undefined reference to Label/Constant '{}'", id)); + return; + } + + m_eval_stack.push_back(base & 0xffff); + } +} + +void GekkoIRPlugin::OnCloseParen(ParenType type) +{ + if (type != ParenType::RelConv) + { + return; + } + + if (m_evaluation_mode == EvalMode::RelAddrDoublePass) + { + AddAbsoluteAddressConv(); + } + else + { + m_eval_stack.push_back(CurrentAddress()); + EvalOperatorAbs(AsmOp::Sub); + } +} + +void GekkoIRPlugin::OnLabelDecl(std::string_view name) +{ + const std::string name_str(name); + if (m_symset.contains(name_str)) + { + m_owner->EmitErrorHere(fmt::format("Label/Constant {} is already defined", name)); + return; + } + + m_labels[name_str] = m_active_block->BlockEndAddress(); + m_symset.insert(name_str); +} + +void GekkoIRPlugin::OnVarDecl(std::string_view name) +{ + const std::string name_str(name); + if (m_symset.contains(name_str)) + { + m_owner->EmitErrorHere(fmt::format("Label/Constant {} is already defined", name)); + return; + } + + m_active_var = &m_constants[name_str]; + m_symset.insert(name_str); +} + +void GekkoIRPlugin::PostParseAction() +{ + RunFixups(); +} + +////////////////////// +// HELPER FUNCTIONS // +////////////////////// + +u32 GekkoIRPlugin::CurrentAddress() const +{ + return m_active_block->BlockEndAddress(); +} + +std::optional GekkoIRPlugin::LookupVar(std::string_view var) +{ + auto var_it = m_constants.find(var); + return var_it == m_constants.end() ? std::nullopt : std::optional(var_it->second); +} + +std::optional GekkoIRPlugin::LookupLabel(std::string_view lab) +{ + auto label_it = m_labels.find(lab); + return label_it == m_labels.end() ? std::nullopt : std::optional(label_it->second); +} + +void GekkoIRPlugin::AddStringBytes(std::string_view str, bool null_term) +{ + ByteChunk& bytes = GetChunk(); + ConvertStringLiteral(str, &bytes); + if (null_term) + { + bytes.push_back('\0'); + } +} + +template +T& GekkoIRPlugin::GetChunk() +{ + if (!m_active_block->chunks.empty() && std::holds_alternative(m_active_block->chunks.back())) + { + return std::get(m_active_block->chunks.back()); + } + + return std::get(m_active_block->chunks.emplace_back(T{})); +} + +template +void GekkoIRPlugin::AddBytes(T val) +{ + if constexpr (std::is_integral_v) + { + ByteChunk& bytes = GetChunk(); + for (size_t i = sizeof(T) - 1; i > 0; i--) + { + bytes.push_back((val >> (8 * i)) & 0xff); + } + bytes.push_back(val & 0xff); + } + else if constexpr (std::is_same_v) + { + static_assert(sizeof(double) == sizeof(u64)); + AddBytes(BitCast(val)); + } + else + { + // std::is_same_v + static_assert(sizeof(double) == sizeof(u64)); + AddBytes(BitCast(val)); + } +} + +void GekkoIRPlugin::PadAlign(u32 bits) +{ + const u32 align_mask = (1 << bits) - 1; + const u32 current_addr = m_active_block->BlockEndAddress(); + if (current_addr & align_mask) + { + PadChunk& current_pad = GetChunk(); + current_pad += (1 << bits) - (current_addr & align_mask); + } +} + +void GekkoIRPlugin::PadSpace(size_t space) +{ + GetChunk() += space; +} + +void GekkoIRPlugin::StartBlock(u32 address) +{ + m_active_block = &m_output_result.blocks.emplace_back(address); +} + +void GekkoIRPlugin::StartBlockAlign(u32 bits) +{ + const u32 align_mask = (1 << bits) - 1; + const u32 current_addr = m_active_block->BlockEndAddress(); + if (current_addr & align_mask) + { + StartBlock((1 << bits) + (current_addr & ~align_mask)); + } +} + +void GekkoIRPlugin::StartInstruction(size_t mnemonic_index, bool extended) +{ + m_build_inst = GekkoInstruction{ + .mnemonic_index = mnemonic_index, + .raw_text = m_owner->lexer.CurrentLine(), + .line_number = m_owner->lexer.LineNumber(), + .is_extended = extended, + }; + m_operand_scan_begin = m_output_result.operand_pool.size(); +} + +void GekkoIRPlugin::AddBinaryEvaluator(u32 (*evaluator)(u32, u32)) +{ + std::function rhs = std::move(m_fixup_stack.top()); + m_fixup_stack.pop(); + std::function lhs = std::move(m_fixup_stack.top()); + m_fixup_stack.pop(); + m_fixup_stack.emplace([evaluator, lhs = std::move(lhs), rhs = std::move(rhs)]() { + return evaluator(lhs(), rhs()); + }); +} + +void GekkoIRPlugin::AddUnaryEvaluator(u32 (*evaluator)(u32)) +{ + std::function sub = std::move(m_fixup_stack.top()); + m_fixup_stack.pop(); + m_fixup_stack.emplace([evaluator, sub = std::move(sub)]() { return evaluator(sub()); }); +} + +void GekkoIRPlugin::AddAbsoluteAddressConv() +{ + const u32 inst_address = m_active_block->BlockEndAddress(); + std::function sub = std::move(m_fixup_stack.top()); + m_fixup_stack.pop(); + m_fixup_stack.emplace([inst_address, sub = std::move(sub)] { return sub() - inst_address; }); +} + +void GekkoIRPlugin::AddLiteral(u32 lit) +{ + m_fixup_stack.emplace([lit] { return lit; }); +} + +void GekkoIRPlugin::AddSymbolResolve(std::string_view sym, bool absolute) +{ + const u32 source_address = m_active_block->BlockEndAddress(); + AssemblerError err_on_fail = AssemblerError{ + fmt::format("Unresolved symbol '{}'", sym), + m_owner->lexer.CurrentLine(), + m_owner->lexer.LineNumber(), + // Lexer should currently point to the label, as it hasn't been eaten yet + m_owner->lexer.ColNumber(), + sym.size(), + }; + + m_fixup_stack.emplace( + [this, sym, absolute, source_address, err_on_fail = std::move(err_on_fail)] { + auto label_it = m_labels.find(sym); + if (label_it != m_labels.end()) + { + if (absolute) + { + return label_it->second; + } + return label_it->second - source_address; + } + + auto var_it = m_constants.find(sym); + if (var_it != m_constants.end()) + { + return static_cast(var_it->second); + } + + m_owner->error = std::move(err_on_fail); + return u32{0}; + }); +} + +void GekkoIRPlugin::SaveOperandFixup(size_t str_left, size_t str_right) +{ + m_operand_fixups.emplace_back(std::move(m_fixup_stack.top())); + m_fixup_stack.pop(); + m_output_result.operand_pool.emplace_back(Interval{str_left, str_right - str_left}, 0); +} + +void GekkoIRPlugin::RunFixups() +{ + for (size_t i = 0; i < m_operand_fixups.size(); i++) + { + ValueOf(m_output_result.operand_pool[i]) = m_operand_fixups[i](); + if (m_owner->error) + { + return; + } + } +} + +void GekkoIRPlugin::FinishInstruction() +{ + m_build_inst.op_interval.begin = m_operand_scan_begin; + m_build_inst.op_interval.len = m_output_result.operand_pool.size() - m_operand_scan_begin; + GetChunk().emplace_back(m_build_inst); + m_operand_scan_begin = 0; +} + +void GekkoIRPlugin::EvalOperatorAbs(AsmOp operation) +{ +#define EVAL_BINARY_OP(OPERATOR) \ + { \ + u64 rhs = m_eval_stack.back(); \ + m_eval_stack.pop_back(); \ + m_eval_stack.back() = m_eval_stack.back() OPERATOR rhs; \ + } + + switch (operation) + { + case AsmOp::Or: + EVAL_BINARY_OP(|); + break; + case AsmOp::Xor: + EVAL_BINARY_OP(^); + break; + case AsmOp::And: + EVAL_BINARY_OP(&); + break; + case AsmOp::Lsh: + EVAL_BINARY_OP(<<); + break; + case AsmOp::Rsh: + EVAL_BINARY_OP(>>); + break; + case AsmOp::Add: + EVAL_BINARY_OP(+); + break; + case AsmOp::Sub: + EVAL_BINARY_OP(-); + break; + case AsmOp::Mul: + EVAL_BINARY_OP(*); + break; + case AsmOp::Div: + EVAL_BINARY_OP(/); + break; + case AsmOp::Neg: + m_eval_stack.back() = static_cast(-static_cast(m_eval_stack.back())); + break; + case AsmOp::Not: + m_eval_stack.back() = ~m_eval_stack.back(); + break; + } +#undef EVAL_BINARY_OP +#undef EVAL_UNARY_OP +} + +void GekkoIRPlugin::EvalOperatorRel(AsmOp operation) +{ + switch (operation) + { + case AsmOp::Or: + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs | rhs; }); + break; + case AsmOp::Xor: + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs ^ rhs; }); + break; + case AsmOp::And: + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs & rhs; }); + break; + case AsmOp::Lsh: + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs << rhs; }); + break; + case AsmOp::Rsh: + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs >> rhs; }); + break; + case AsmOp::Add: + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs + rhs; }); + break; + case AsmOp::Sub: + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs - rhs; }); + break; + case AsmOp::Mul: + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs * rhs; }); + break; + case AsmOp::Div: + AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs / rhs; }); + break; + case AsmOp::Neg: + AddUnaryEvaluator([](u32 val) { return static_cast(-static_cast(val)); }); + break; + case AsmOp::Not: + AddUnaryEvaluator([](u32 val) { return ~val; }); + break; + } +} + +void GekkoIRPlugin::EvalTerminalRel(Terminal type, const AssemblerToken& tok) +{ + switch (type) + { + case Terminal::Hex: + case Terminal::Dec: + case Terminal::Oct: + case Terminal::Bin: + case Terminal::GPR: + case Terminal::FPR: + case Terminal::SPR: + case Terminal::CRField: + case Terminal::Lt: + case Terminal::Gt: + case Terminal::Eq: + case Terminal::So: + { + std::optional val = tok.EvalToken(); + ASSERT(val.has_value()); + AddLiteral(*val); + break; + } + + case Terminal::Dot: + AddLiteral(CurrentAddress()); + break; + + case Terminal::Id: + { + if (auto label_it = m_labels.find(tok.token_val); label_it != m_labels.end()) + { + AddLiteral(label_it->second - CurrentAddress()); + } + else if (auto var_it = m_constants.find(tok.token_val); var_it != m_constants.end()) + { + AddLiteral(var_it->second); + } + else + { + AddSymbolResolve(tok.token_val, false); + } + break; + } + + // Parser should disallow this from happening + default: + ASSERT(false); + break; + } +} + +void GekkoIRPlugin::EvalTerminalAbs(Terminal type, const AssemblerToken& tok) +{ + switch (type) + { + case Terminal::Hex: + case Terminal::Dec: + case Terminal::Oct: + case Terminal::Bin: + case Terminal::GPR: + case Terminal::FPR: + case Terminal::SPR: + case Terminal::CRField: + case Terminal::Lt: + case Terminal::Gt: + case Terminal::Eq: + case Terminal::So: + { + std::optional val = tok.EvalToken(); + ASSERT(val.has_value()); + m_eval_stack.push_back(*val); + break; + } + + case Terminal::Flt: + { + std::visit( + [&tok](auto&& vec) { + auto opt = tok.EvalToken::value_type>(); + ASSERT(opt.has_value()); + vec.push_back(*opt); + }, + m_floats_list); + break; + } + + case Terminal::Dot: + m_eval_stack.push_back(static_cast(CurrentAddress())); + break; + + case Terminal::Id: + { + if (auto label_it = m_labels.find(tok.token_val); label_it != m_labels.end()) + { + m_eval_stack.push_back(label_it->second); + } + else if (auto var_it = m_constants.find(tok.token_val); var_it != m_constants.end()) + { + m_eval_stack.push_back(var_it->second); + } + else + { + m_owner->EmitErrorHere( + fmt::format("Undefined reference to Label/Constant '{}'", tok.ValStr())); + return; + } + break; + } + + // Parser should disallow this from happening + default: + ASSERT(false); + break; + } +} +} // namespace + +u32 IRBlock::BlockEndAddress() const +{ + return std::accumulate(chunks.begin(), chunks.end(), block_address, + [](u32 acc, const ChunkVariant& chunk) { + size_t size; + if (std::holds_alternative(chunk)) + { + size = std::get(chunk).size() * 4; + } + else if (std::holds_alternative(chunk)) + { + size = std::get(chunk).size(); + } + else if (std::holds_alternative(chunk)) + { + size = std::get(chunk); + } + else + { + ASSERT(false); + size = 0; + } + + return acc + static_cast(size); + }); +} + +FailureOr ParseToIR(std::string_view assembly, u32 base_virtual_address) +{ + GekkoIR ret; + GekkoIRPlugin plugin(ret, base_virtual_address); + + ParseWithPlugin(&plugin, assembly); + + if (plugin.Error()) + { + return FailureOr(std::move(*plugin.Error())); + } + + return std::move(ret); +} + +} // namespace Common::GekkoAssembler::detail diff --git a/Source/Core/Common/Assembler/GekkoIRGen.h b/Source/Core/Common/Assembler/GekkoIRGen.h new file mode 100644 index 0000000000..4a1a56fa66 --- /dev/null +++ b/Source/Core/Common/Assembler/GekkoIRGen.h @@ -0,0 +1,50 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "Common/Assembler/AssemblerShared.h" +#include "Common/Assembler/GekkoLexer.h" +#include "Common/CommonTypes.h" + +namespace Common::GekkoAssembler::detail +{ +struct GekkoInstruction +{ + // Combination of a mnemonic index and variant: + // ( << 2) | () + size_t mnemonic_index = 0; + // Below refers to GekkoParseResult::operand_pool + Interval op_interval = Interval{0, 0}; + // Literal text of this instruction + std::string_view raw_text; + size_t line_number = 0; + bool is_extended = false; +}; + +using InstChunk = std::vector; +using ByteChunk = std::vector; +using PadChunk = size_t; +using ChunkVariant = std::variant; + +struct IRBlock +{ + explicit IRBlock(u32 address) : block_address(address) {} + + u32 BlockEndAddress() const; + + std::vector chunks; + u32 block_address; +}; + +struct GekkoIR +{ + std::vector blocks; + std::vector> operand_pool; +}; + +FailureOr ParseToIR(std::string_view assembly, u32 base_virtual_address); +} // namespace Common::GekkoAssembler::detail diff --git a/Source/Core/Common/Assembler/GekkoLexer.cpp b/Source/Core/Common/Assembler/GekkoLexer.cpp new file mode 100644 index 0000000000..8947802ecb --- /dev/null +++ b/Source/Core/Common/Assembler/GekkoLexer.cpp @@ -0,0 +1,794 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Common/Assembler/GekkoLexer.h" + +#include "Common/Assert.h" + +#include +#include + +namespace Common::GekkoAssembler::detail +{ +namespace +{ +constexpr bool IsOctal(char c) +{ + return c >= '0' && c <= '7'; +} + +constexpr bool IsBinary(char c) +{ + return c == '0' || c == '1'; +} + +template +constexpr T ConvertNib(char c) +{ + if (c >= 'a' && c <= 'f') + { + return static_cast(c - 'a' + 10); + } + if (c >= 'A' && c <= 'F') + { + return static_cast(c - 'A' + 10); + } + return static_cast(c - '0'); +} + +constexpr TokenType SingleCharToken(char ch) +{ + switch (ch) + { + case ',': + return TokenType::Comma; + case '(': + return TokenType::Lparen; + case ')': + return TokenType::Rparen; + case '|': + return TokenType::Pipe; + case '^': + return TokenType::Caret; + case '&': + return TokenType::Ampersand; + case '+': + return TokenType::Plus; + case '-': + return TokenType::Minus; + case '*': + return TokenType::Star; + case '/': + return TokenType::Slash; + case '~': + return TokenType::Tilde; + case '@': + return TokenType::At; + case ':': + return TokenType::Colon; + case '`': + return TokenType::Grave; + case '.': + return TokenType::Dot; + case '\0': + return TokenType::Eof; + case '\n': + return TokenType::Eol; + default: + return TokenType::Invalid; + } +} + +// Convert a string literal into its raw-data form +template +void ConvertStringLiteral(std::string_view literal, std::back_insert_iterator out_it) +{ + for (size_t i = 1; i < literal.size() - 1;) + { + if (literal[i] == '\\') + { + ++i; + if (IsOctal(literal[i])) + { + // Octal escape + char octal_escape = 0; + for (char c = literal[i]; IsOctal(c); c = literal[++i]) + { + octal_escape = (octal_escape << 3) + (c - '0'); + } + out_it = static_cast(octal_escape); + } + else if (literal[i] == 'x') + { + // Hex escape + char hex_escape = 0; + for (char c = literal[++i]; std::isxdigit(c); c = literal[++i]) + { + hex_escape = (hex_escape << 4) + ConvertNib(c); + } + out_it = static_cast(hex_escape); + } + else + { + char simple_escape; + switch (literal[i]) + { + case '\'': + simple_escape = '\x27'; + break; + case '"': + simple_escape = '\x22'; + break; + case '?': + simple_escape = '\x3f'; + break; + case '\\': + simple_escape = '\x5c'; + break; + case 'a': + simple_escape = '\x07'; + break; + case 'b': + simple_escape = '\x08'; + break; + case 'f': + simple_escape = '\x0c'; + break; + case 'n': + simple_escape = '\x0a'; + break; + case 'r': + simple_escape = '\x0d'; + break; + case 't': + simple_escape = '\x09'; + break; + case 'v': + simple_escape = '\x0b'; + break; + default: + simple_escape = literal[i]; + break; + } + out_it = static_cast(simple_escape); + ++i; + } + } + else + { + out_it = static_cast(literal[i]); + ++i; + } + } +} + +template +std::optional EvalIntegral(TokenType tp, std::string_view val) +{ + constexpr auto hex_step = [](T acc, char c) { return acc << 4 | ConvertNib(c); }; + constexpr auto dec_step = [](T acc, char c) { return acc * 10 + (c - '0'); }; + constexpr auto oct_step = [](T acc, char c) { return acc << 3 | (c - '0'); }; + constexpr auto bin_step = [](T acc, char c) { return acc << 1 | (c - '0'); }; + + switch (tp) + { + case TokenType::HexadecimalLit: + return std::accumulate(val.begin() + 2, val.end(), T{0}, hex_step); + case TokenType::DecimalLit: + return std::accumulate(val.begin(), val.end(), T{0}, dec_step); + case TokenType::OctalLit: + return std::accumulate(val.begin() + 1, val.end(), T{0}, oct_step); + case TokenType::BinaryLit: + return std::accumulate(val.begin() + 2, val.end(), T{0}, bin_step); + case TokenType::GPR: + case TokenType::FPR: + return std::accumulate(val.begin() + 1, val.end(), T{0}, dec_step); + case TokenType::CRField: + return std::accumulate(val.begin() + 2, val.end(), T{0}, dec_step); + case TokenType::SPR: + return static_cast(*sprg_map.Find(val)); + case TokenType::Lt: + return T{0}; + case TokenType::Gt: + return T{1}; + case TokenType::Eq: + return T{2}; + case TokenType::So: + return T{3}; + default: + return std::nullopt; + } +} +} // namespace + +void ConvertStringLiteral(std::string_view literal, std::vector* out_vec) +{ + ConvertStringLiteral(literal, std::back_inserter(*out_vec)); +} + +std::string_view TokenTypeToStr(TokenType tp) +{ + switch (tp) + { + case TokenType::GPR: + return "GPR"; + case TokenType::FPR: + return "FPR"; + case TokenType::SPR: + return "SPR"; + case TokenType::CRField: + return "CR Field"; + case TokenType::Lt: + case TokenType::Gt: + case TokenType::Eq: + case TokenType::So: + return "CR Bit"; + case TokenType::Identifier: + return "Identifier"; + case TokenType::StringLit: + return "String Literal"; + case TokenType::DecimalLit: + return "Decimal Literal"; + case TokenType::BinaryLit: + return "Binary Literal"; + case TokenType::HexadecimalLit: + return "Hexadecimal Literal"; + case TokenType::OctalLit: + return "Octal Literal"; + case TokenType::FloatLit: + return "Float Literal"; + case TokenType::Invalid: + return "Invalid"; + case TokenType::Lsh: + return "<<"; + case TokenType::Rsh: + return ">>"; + case TokenType::Comma: + return ","; + case TokenType::Lparen: + return "("; + case TokenType::Rparen: + return ")"; + case TokenType::Pipe: + return "|"; + case TokenType::Caret: + return "^"; + case TokenType::Ampersand: + return "&"; + case TokenType::Plus: + return "+"; + case TokenType::Minus: + return "-"; + case TokenType::Star: + return "*"; + case TokenType::Slash: + return "/"; + case TokenType::Tilde: + return "~"; + case TokenType::At: + return "@"; + case TokenType::Colon: + return ":"; + case TokenType::Grave: + return "`"; + case TokenType::Dot: + return "."; + case TokenType::Eof: + return "End of File"; + case TokenType::Eol: + return "End of Line"; + default: + return ""; + } +} + +std::string_view AssemblerToken::TypeStr() const +{ + return TokenTypeToStr(token_type); +} + +std::string_view AssemblerToken::ValStr() const +{ + switch (token_type) + { + case TokenType::Eol: + return ""; + case TokenType::Eof: + return ""; + default: + return token_val; + } +} + +template <> +std::optional AssemblerToken::EvalToken() const +{ + if (token_type == TokenType::FloatLit) + { + return std::stof(std::string(token_val)); + } + return std::nullopt; +} + +template <> +std::optional AssemblerToken::EvalToken() const +{ + if (token_type == TokenType::FloatLit) + { + return std::stod(std::string(token_val)); + } + return std::nullopt; +} + +template <> +std::optional AssemblerToken::EvalToken() const +{ + return EvalIntegral(token_type, token_val); +} + +template <> +std::optional AssemblerToken::EvalToken() const +{ + return EvalIntegral(token_type, token_val); +} + +template <> +std::optional AssemblerToken::EvalToken() const +{ + return EvalIntegral(token_type, token_val); +} + +template <> +std::optional AssemblerToken::EvalToken() const +{ + return EvalIntegral(token_type, token_val); +} + +size_t Lexer::LineNumber() const +{ + return m_lexed_tokens.empty() ? m_pos.line : TagOf(m_lexed_tokens.front()).line; +} + +size_t Lexer::ColNumber() const +{ + return m_lexed_tokens.empty() ? m_pos.col : TagOf(m_lexed_tokens.front()).col; +} + +std::string_view Lexer::CurrentLine() const +{ + const size_t line_index = + m_lexed_tokens.empty() ? m_pos.index : TagOf(m_lexed_tokens.front()).index; + size_t begin_index = line_index == 0 ? 0 : line_index - 1; + for (; begin_index > 0; begin_index--) + { + if (m_lex_string[begin_index] == '\n') + { + begin_index++; + break; + } + } + size_t end_index = begin_index; + for (; end_index < m_lex_string.size(); end_index++) + { + if (m_lex_string[end_index] == '\n') + { + end_index++; + break; + } + } + return m_lex_string.substr(begin_index, end_index - begin_index); +} + +void Lexer::SetIdentifierMatchRule(IdentifierMatchRule set) +{ + FeedbackTokens(); + m_match_rule = set; +} + +const Tagged& Lexer::LookaheadTagRef(size_t num_fwd) const +{ + while (m_lexed_tokens.size() < num_fwd) + { + LookaheadRef(); + } + return m_lexed_tokens[num_fwd]; +} + +AssemblerToken Lexer::Lookahead() const +{ + if (m_lexed_tokens.empty()) + { + CursorPosition pos_pre = m_pos; + m_lexed_tokens.emplace_back(pos_pre, LexSingle()); + } + return ValueOf(m_lexed_tokens.front()); +} + +const AssemblerToken& Lexer::LookaheadRef() const +{ + if (m_lexed_tokens.empty()) + { + CursorPosition pos_pre = m_pos; + m_lexed_tokens.emplace_back(pos_pre, LexSingle()); + } + return ValueOf(m_lexed_tokens.front()); +} + +TokenType Lexer::LookaheadType() const +{ + return LookaheadRef().token_type; +} + +AssemblerToken Lexer::LookaheadFloat() const +{ + FeedbackTokens(); + SkipWs(); + + CursorPosition pos_pre = m_pos; + ScanStart(); + + std::optional failure_reason = RunDfa(float_dfa); + + // Special case: lex at least a single char for no matches for errors to make sense + if (m_scan_pos.index == pos_pre.index) + { + Step(); + } + + std::string_view tok_str = ScanFinishOut(); + AssemblerToken tok; + if (!failure_reason) + { + tok = AssemblerToken{ + TokenType::FloatLit, + tok_str, + "", + Interval{0, 0}, + }; + } + else + { + tok = AssemblerToken{ + TokenType::Invalid, + tok_str, + *failure_reason, + Interval{0, tok_str.length()}, + }; + } + + m_lexed_tokens.emplace_back(pos_pre, tok); + return tok; +} + +void Lexer::Eat() +{ + if (m_lexed_tokens.empty()) + { + LexSingle(); + } + else + { + m_lexed_tokens.pop_front(); + } +} + +void Lexer::EatAndReset() +{ + Eat(); + SetIdentifierMatchRule(IdentifierMatchRule::Typical); +} + +std::optional Lexer::RunDfa(const std::vector& dfa) const +{ + size_t dfa_index = 0; + bool transition_found; + do + { + transition_found = false; + if (Peek() == '\0') + { + break; + } + + const DfaNode& n = dfa[dfa_index]; + for (auto&& edge : n.edges) + { + if (edge.first(Peek())) + { + transition_found = true; + dfa_index = edge.second; + break; + } + } + + if (transition_found) + { + Step(); + } + } while (transition_found); + + return dfa[dfa_index].match_failure_reason; +} + +void Lexer::SkipWs() const +{ + ScanStart(); + for (char c = Peek(); std::isspace(c) && c != '\n'; c = Step().Peek()) + { + } + if (Peek() == '#') + { + while (Peek() != '\n' && Peek() != '\0') + { + Step(); + } + } + ScanFinish(); +} + +void Lexer::FeedbackTokens() const +{ + if (m_lexed_tokens.empty()) + { + return; + } + m_pos = m_scan_pos = TagOf(m_lexed_tokens.front()); + m_lexed_tokens.clear(); +} + +bool Lexer::IdentifierHeadExtra(char h) const +{ + switch (m_match_rule) + { + case IdentifierMatchRule::Typical: + case IdentifierMatchRule::Mnemonic: + return false; + case IdentifierMatchRule::Directive: + return std::isdigit(h); + } + return false; +} + +bool Lexer::IdentifierExtra(char c) const +{ + switch (m_match_rule) + { + case IdentifierMatchRule::Typical: + case IdentifierMatchRule::Directive: + return false; + case IdentifierMatchRule::Mnemonic: + return c == '+' || c == '-' || c == '.'; + } + return false; +} + +void Lexer::ScanStart() const +{ + m_scan_pos = m_pos; +} + +void Lexer::ScanFinish() const +{ + m_pos = m_scan_pos; +} + +std::string_view Lexer::ScanFinishOut() const +{ + const size_t start = m_pos.index; + m_pos = m_scan_pos; + return m_lex_string.substr(start, m_scan_pos.index - start); +} + +char Lexer::Peek() const +{ + if (m_scan_pos.index >= m_lex_string.length()) + { + return 0; + } + return m_lex_string[m_scan_pos.index]; +} + +const Lexer& Lexer::Step() const +{ + if (m_scan_pos.index >= m_lex_string.length()) + { + return *this; + } + + if (Peek() == '\n') + { + m_scan_pos.line++; + m_scan_pos.col = 0; + } + else + { + m_scan_pos.col++; + } + m_scan_pos.index++; + return *this; +} + +TokenType Lexer::LexStringLit(std::string_view& invalid_reason, Interval& invalid_region) const +{ + // The open quote has alread been matched + const size_t string_start = m_scan_pos.index - 1; + TokenType token_type = TokenType::StringLit; + + std::optional failure_reason = RunDfa(string_dfa); + + if (failure_reason) + { + token_type = TokenType::Invalid; + invalid_reason = *failure_reason; + invalid_region = Interval{0, m_scan_pos.index - string_start}; + } + + return token_type; +} + +TokenType Lexer::ClassifyAlnum() const +{ + const std::string_view alnum = m_lex_string.substr(m_pos.index, m_scan_pos.index - m_pos.index); + constexpr auto valid_regnum = [](std::string_view rn) { + if (rn.length() == 1 && std::isdigit(rn[0])) + { + return true; + } + else if (rn.length() == 2 && std::isdigit(rn[0]) && std::isdigit(rn[1])) + { + if (rn[0] == '1' || rn[0] == '2') + { + return true; + } + + if (rn[0] == '3') + { + return rn[1] <= '2'; + } + } + + return false; + }; + constexpr auto eq_nocase = [](std::string_view str, std::string_view lwr) { + auto it_l = str.cbegin(), it_r = lwr.cbegin(); + for (; it_l != str.cend() && it_r != lwr.cend(); it_l++, it_r++) + { + if (std::tolower(*it_l) != *it_r) + { + return false; + } + } + return it_l == str.end() && it_r == lwr.end(); + }; + + if (std::tolower(alnum[0]) == 'r' && valid_regnum(alnum.substr(1))) + { + return TokenType::GPR; + } + else if (std::tolower(alnum[0]) == 'f' && valid_regnum(alnum.substr(1))) + { + return TokenType::FPR; + } + else if (alnum.length() == 3 && eq_nocase(alnum.substr(0, 2), "cr") && alnum[2] >= '0' && + alnum[2] <= '7') + { + return TokenType::CRField; + } + else if (eq_nocase(alnum, "lt")) + { + return TokenType::Lt; + } + else if (eq_nocase(alnum, "gt")) + { + return TokenType::Gt; + } + else if (eq_nocase(alnum, "eq")) + { + return TokenType::Eq; + } + else if (eq_nocase(alnum, "so")) + { + return TokenType::So; + } + else if (sprg_map.Find(alnum) != nullptr) + { + return TokenType::SPR; + } + return TokenType::Identifier; +} + +AssemblerToken Lexer::LexSingle() const +{ + SkipWs(); + + ScanStart(); + const char h = Peek(); + + TokenType token_type; + std::string_view invalid_reason = ""; + Interval invalid_region = Interval{0, 0}; + + Step(); + + if (std::isalpha(h) || h == '_' || IdentifierHeadExtra(h)) + { + for (char c = Peek(); std::isalnum(c) || c == '_' || IdentifierExtra(c); c = Step().Peek()) + { + } + + token_type = ClassifyAlnum(); + } + else if (h == '"') + { + token_type = LexStringLit(invalid_reason, invalid_region); + } + else if (h == '0') + { + const char imm_type = Peek(); + + if (imm_type == 'x') + { + token_type = TokenType::HexadecimalLit; + Step(); + for (char c = Peek(); std::isxdigit(c); c = Step().Peek()) + { + } + } + else if (imm_type == 'b') + { + token_type = TokenType::BinaryLit; + Step(); + for (char c = Peek(); IsBinary(c); c = Step().Peek()) + { + } + } + else if (IsOctal(imm_type)) + { + token_type = TokenType::OctalLit; + for (char c = Peek(); IsOctal(c); c = Step().Peek()) + { + } + } + else + { + token_type = TokenType::DecimalLit; + } + } + else if (std::isdigit(h)) + { + for (char c = Peek(); std::isdigit(c); c = Step().Peek()) + { + } + token_type = TokenType::DecimalLit; + } + else if (h == '<' || h == '>') + { + // Special case for two-character operators + const char second_ch = Peek(); + if (second_ch == h) + { + Step(); + token_type = second_ch == '<' ? TokenType::Lsh : TokenType::Rsh; + } + else + { + token_type = TokenType::Invalid; + invalid_reason = "Unrecognized character"; + invalid_region = Interval{0, 1}; + } + } + else + { + token_type = SingleCharToken(h); + if (token_type == TokenType::Invalid) + { + invalid_reason = "Unrecognized character"; + invalid_region = Interval{0, 1}; + } + } + + AssemblerToken new_tok = {token_type, ScanFinishOut(), invalid_reason, invalid_region}; + SkipWs(); + return new_tok; +} +} // namespace Common::GekkoAssembler::detail diff --git a/Source/Core/Common/Assembler/GekkoLexer.h b/Source/Core/Common/Assembler/GekkoLexer.h new file mode 100644 index 0000000000..9ff78c04d0 --- /dev/null +++ b/Source/Core/Common/Assembler/GekkoLexer.h @@ -0,0 +1,188 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Common/Assembler/AssemblerShared.h" +#include "Common/Assembler/AssemblerTables.h" +#include "Common/CommonTypes.h" + +namespace Common::GekkoAssembler::detail +{ +void ConvertStringLiteral(std::string_view literal, std::vector* out_vec); + +enum class TokenType +{ + Invalid, + Identifier, + StringLit, + HexadecimalLit, + DecimalLit, + OctalLit, + BinaryLit, + FloatLit, + GPR, + FPR, + CRField, + SPR, + Lt, + Gt, + Eq, + So, + // EOL signifies boundaries between instructions, a la ';' + Eol, + Eof, + + Dot, + Colon, + Comma, + Lparen, + Rparen, + Pipe, + Caret, + Ampersand, + Lsh, + Rsh, + Plus, + Minus, + Star, + Slash, + Tilde, + Grave, + At, + + OperatorBegin = Dot, + LastToken = At, +}; + +std::string_view TokenTypeToStr(TokenType); + +struct AssemblerToken +{ + TokenType token_type; + std::string_view token_val; + std::string_view invalid_reason; + // Within an invalid token, specifies the erroneous region + Interval invalid_region; + + std::string_view TypeStr() const; + std::string_view ValStr() const; + + // Supported Templates: + // u8, u16, u32, u64, float, double + template + std::optional EvalToken() const; +}; + +struct CursorPosition +{ + size_t index = 0; + size_t line = 0; + size_t col = 0; +}; + +class Lexer +{ +public: + enum class IdentifierMatchRule + { + Typical, + Mnemonic, // Mnemonics can contain +, -, or . to specify branch prediction rules and link bit + Directive, // Directives can start with a digit + }; + +public: + explicit Lexer(std::string_view str) + : m_lex_string(str), m_match_rule(IdentifierMatchRule::Typical) + { + } + + size_t LineNumber() const; + size_t ColNumber() const; + std::string_view CurrentLine() const; + + // Since there's only one place floats get lexed, it's 'okay' to have an explicit + // "lex a float token" function + void SetIdentifierMatchRule(IdentifierMatchRule set); + const Tagged& LookaheadTagRef(size_t num_fwd) const; + AssemblerToken Lookahead() const; + const AssemblerToken& LookaheadRef() const; + TokenType LookaheadType() const; + // Since there's only one place floats get lexed, it's 'okay' to have an explicit + // "lex a float token" function + AssemblerToken LookaheadFloat() const; + void Eat(); + void EatAndReset(); + + template + void LookaheadTaggedN(std::array, N>* tokens_out) const + { + const size_t filled_amt = std::min(m_lexed_tokens.size(), N); + + std::copy_n(m_lexed_tokens.begin(), filled_amt, tokens_out->begin()); + + std::generate_n(tokens_out->begin() + filled_amt, N - filled_amt, [this] { + CursorPosition p = m_pos; + return m_lexed_tokens.emplace_back(p, LexSingle()); + }); + } + + template + void LookaheadN(std::array* tokens_out) const + { + const size_t filled_amt = std::min(m_lexed_tokens.size(), N); + + auto _it = m_lexed_tokens.begin(); + std::generate_n(tokens_out->begin(), filled_amt, [&_it] { return ValueOf(*_it++); }); + + std::generate_n(tokens_out->begin() + filled_amt, N - filled_amt, [this] { + CursorPosition p = m_pos; + return ValueOf(m_lexed_tokens.emplace_back(p, LexSingle())); + }); + } + + template + void EatN() + { + size_t consumed = 0; + while (m_lexed_tokens.size() > 0 && consumed < N) + { + m_lexed_tokens.pop_front(); + consumed++; + } + for (size_t i = consumed; i < N; i++) + { + LexSingle(); + } + } + +private: + std::optional RunDfa(const std::vector& dfa) const; + void SkipWs() const; + void FeedbackTokens() const; + bool IdentifierHeadExtra(char h) const; + bool IdentifierExtra(char c) const; + void ScanStart() const; + void ScanFinish() const; + std::string_view ScanFinishOut() const; + char Peek() const; + const Lexer& Step() const; + TokenType LexStringLit(std::string_view& invalid_reason, Interval& invalid_region) const; + TokenType ClassifyAlnum() const; + AssemblerToken LexSingle() const; + + std::string_view m_lex_string; + mutable CursorPosition m_pos; + mutable CursorPosition m_scan_pos; + mutable std::deque> m_lexed_tokens; + IdentifierMatchRule m_match_rule; +}; +} // namespace Common::GekkoAssembler::detail diff --git a/Source/Core/Common/Assembler/GekkoParser.cpp b/Source/Core/Common/Assembler/GekkoParser.cpp new file mode 100644 index 0000000000..26b5bb1082 --- /dev/null +++ b/Source/Core/Common/Assembler/GekkoParser.cpp @@ -0,0 +1,885 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Common/Assembler/GekkoParser.h" + +#include +#include +#include + +#include + +#include "Common/Assembler/AssemblerShared.h" +#include "Common/Assembler/AssemblerTables.h" +#include "Common/Assembler/GekkoLexer.h" +#include "Common/Assert.h" + +namespace Common::GekkoAssembler::detail +{ +namespace +{ +bool MatchOperandFirst(const AssemblerToken& tok) +{ + switch (tok.token_type) + { + case TokenType::Minus: + case TokenType::Tilde: + case TokenType::Lparen: + case TokenType::Grave: + case TokenType::Identifier: + case TokenType::DecimalLit: + case TokenType::OctalLit: + case TokenType::HexadecimalLit: + case TokenType::BinaryLit: + case TokenType::Dot: + return true; + default: + return false; + } +} + +void ParseImm(ParseState* state) +{ + AssemblerToken tok = state->lexer.Lookahead(); + switch (tok.token_type) + { + case TokenType::HexadecimalLit: + state->plugin.OnTerminal(Terminal::Hex, tok); + break; + case TokenType::DecimalLit: + state->plugin.OnTerminal(Terminal::Dec, tok); + break; + case TokenType::OctalLit: + state->plugin.OnTerminal(Terminal::Oct, tok); + break; + case TokenType::BinaryLit: + state->plugin.OnTerminal(Terminal::Bin, tok); + break; + default: + state->EmitErrorHere(fmt::format("Invalid {} with value '{}'", tok.TypeStr(), tok.ValStr())); + return; + } + if (state->error) + { + return; + } + state->lexer.Eat(); +} + +void ParseId(ParseState* state) +{ + AssemblerToken tok = state->lexer.Lookahead(); + if (tok.token_type == TokenType::Identifier) + { + state->plugin.OnTerminal(Terminal::Id, tok); + if (state->error) + { + return; + } + state->lexer.Eat(); + } + else + { + state->EmitErrorHere(fmt::format("Expected an identifier, but found '{}'", tok.ValStr())); + } +} + +void ParseIdLocation(ParseState* state) +{ + std::array toks; + state->lexer.LookaheadN(&toks); + + if (toks[1].token_type == TokenType::At) + { + if (toks[2].token_val == "ha") + { + state->plugin.OnHiaddr(toks[0].token_val); + if (state->error) + { + return; + } + state->lexer.EatN<3>(); + return; + } + else if (toks[2].token_val == "l") + { + state->plugin.OnLoaddr(toks[0].token_val); + if (state->error) + { + return; + } + state->lexer.EatN<3>(); + return; + } + } + + ParseId(state); +} + +void ParsePpcBuiltin(ParseState* state) +{ + AssemblerToken tok = state->lexer.Lookahead(); + switch (tok.token_type) + { + case TokenType::GPR: + state->plugin.OnTerminal(Terminal::GPR, tok); + break; + case TokenType::FPR: + state->plugin.OnTerminal(Terminal::FPR, tok); + break; + case TokenType::SPR: + state->plugin.OnTerminal(Terminal::SPR, tok); + break; + case TokenType::CRField: + state->plugin.OnTerminal(Terminal::CRField, tok); + break; + case TokenType::Lt: + state->plugin.OnTerminal(Terminal::Lt, tok); + break; + case TokenType::Gt: + state->plugin.OnTerminal(Terminal::Gt, tok); + break; + case TokenType::Eq: + state->plugin.OnTerminal(Terminal::Eq, tok); + break; + case TokenType::So: + state->plugin.OnTerminal(Terminal::So, tok); + break; + default: + state->EmitErrorHere( + fmt::format("Unexpected token '{}' in ppc builtin", state->lexer.LookaheadRef().ValStr())); + break; + } + if (state->error) + { + return; + } + state->lexer.Eat(); +} + +void ParseBaseexpr(ParseState* state) +{ + TokenType tok = state->lexer.LookaheadType(); + switch (tok) + { + case TokenType::HexadecimalLit: + case TokenType::DecimalLit: + case TokenType::OctalLit: + case TokenType::BinaryLit: + ParseImm(state); + break; + + case TokenType::Identifier: + ParseIdLocation(state); + break; + + case TokenType::GPR: + case TokenType::FPR: + case TokenType::SPR: + case TokenType::CRField: + case TokenType::Lt: + case TokenType::Gt: + case TokenType::Eq: + case TokenType::So: + ParsePpcBuiltin(state); + break; + + case TokenType::Dot: + state->plugin.OnTerminal(Terminal::Dot, state->lexer.Lookahead()); + if (state->error) + { + return; + } + state->lexer.Eat(); + break; + + default: + state->EmitErrorHere( + fmt::format("Unexpected token '{}' in expression", state->lexer.LookaheadRef().ValStr())); + break; + } +} + +void ParseBitor(ParseState* state); +void ParseParen(ParseState* state) +{ + if (state->HasToken(TokenType::Lparen)) + { + state->plugin.OnOpenParen(ParenType::Normal); + if (state->error) + { + return; + } + + state->lexer.Eat(); + ParseBitor(state); + if (state->error) + { + return; + } + + if (state->HasToken(TokenType::Rparen)) + { + state->plugin.OnCloseParen(ParenType::Normal); + } + state->ParseToken(TokenType::Rparen); + } + else if (state->HasToken(TokenType::Grave)) + { + state->plugin.OnOpenParen(ParenType::RelConv); + + state->lexer.Eat(); + ParseBitor(state); + if (state->error) + { + return; + } + + if (state->HasToken(TokenType::Grave)) + { + state->plugin.OnCloseParen(ParenType::RelConv); + } + state->ParseToken(TokenType::Grave); + } + else + { + ParseBaseexpr(state); + } +} + +void ParseUnary(ParseState* state) +{ + TokenType tok = state->lexer.LookaheadType(); + if (tok == TokenType::Minus || tok == TokenType::Tilde) + { + state->lexer.Eat(); + ParseUnary(state); + if (state->error) + { + return; + } + + if (tok == TokenType::Minus) + { + state->plugin.OnOperator(AsmOp::Neg); + } + else + { + state->plugin.OnOperator(AsmOp::Not); + } + } + else + { + ParseParen(state); + } +} + +void ParseMultiplication(ParseState* state) +{ + ParseUnary(state); + if (state->error) + { + return; + } + + TokenType tok = state->lexer.LookaheadType(); + while (tok == TokenType::Star || tok == TokenType::Slash) + { + state->lexer.Eat(); + ParseUnary(state); + if (state->error) + { + return; + } + + if (tok == TokenType::Star) + { + state->plugin.OnOperator(AsmOp::Mul); + } + else + { + state->plugin.OnOperator(AsmOp::Div); + } + tok = state->lexer.LookaheadType(); + } +} + +void ParseAddition(ParseState* state) +{ + ParseMultiplication(state); + if (state->error) + { + return; + } + + TokenType tok = state->lexer.LookaheadType(); + while (tok == TokenType::Plus || tok == TokenType::Minus) + { + state->lexer.Eat(); + ParseMultiplication(state); + if (state->error) + { + return; + } + + if (tok == TokenType::Plus) + { + state->plugin.OnOperator(AsmOp::Add); + } + else + { + state->plugin.OnOperator(AsmOp::Sub); + } + tok = state->lexer.LookaheadType(); + } +} + +void ParseShift(ParseState* state) +{ + ParseAddition(state); + if (state->error) + { + return; + } + + TokenType tok = state->lexer.LookaheadType(); + while (tok == TokenType::Lsh || tok == TokenType::Rsh) + { + state->lexer.Eat(); + ParseAddition(state); + if (state->error) + { + return; + } + + if (tok == TokenType::Lsh) + { + state->plugin.OnOperator(AsmOp::Lsh); + } + else + { + state->plugin.OnOperator(AsmOp::Rsh); + } + tok = state->lexer.LookaheadType(); + } +} + +void ParseBitand(ParseState* state) +{ + ParseShift(state); + if (state->error) + { + return; + } + + while (state->HasToken(TokenType::Ampersand)) + { + state->lexer.Eat(); + ParseShift(state); + if (state->error) + { + return; + } + + state->plugin.OnOperator(AsmOp::And); + } +} + +void ParseBitxor(ParseState* state) +{ + ParseBitand(state); + if (state->error) + { + return; + } + + while (state->HasToken(TokenType::Caret)) + { + state->lexer.Eat(); + ParseBitand(state); + if (state->error) + { + return; + } + + state->plugin.OnOperator(AsmOp::Xor); + } +} + +void ParseBitor(ParseState* state) +{ + ParseBitxor(state); + if (state->error) + { + return; + } + + while (state->HasToken(TokenType::Pipe)) + { + state->lexer.Eat(); + ParseBitxor(state); + if (state->error) + { + return; + } + + state->plugin.OnOperator(AsmOp::Or); + } +} + +void ParseOperand(ParseState* state) +{ + state->plugin.OnOperandPre(); + ParseBitor(state); + if (state->error) + { + return; + } + state->plugin.OnOperandPost(); +} + +void ParseOperandList(ParseState* state, ParseAlg alg) +{ + if (alg == ParseAlg::None) + { + return; + } + if (alg == ParseAlg::NoneOrOp1) + { + if (MatchOperandFirst(state->lexer.Lookahead())) + { + ParseOperand(state); + } + return; + } + + enum ParseStep + { + _Operand, + _Comma, + _Lparen, + _Rparen, + _OptComma + }; + std::vector steps; + + switch (alg) + { + case ParseAlg::Op1: + steps = {_Operand}; + break; + case ParseAlg::Op1Or2: + steps = {_Operand, _OptComma, _Operand}; + break; + case ParseAlg::Op2Or3: + steps = {_Operand, _Comma, _Operand, _OptComma, _Operand}; + break; + case ParseAlg::Op1Off1: + steps = {_Operand, _Comma, _Operand, _Lparen, _Operand, _Rparen}; + break; + case ParseAlg::Op2: + steps = {_Operand, _Comma, _Operand}; + break; + case ParseAlg::Op3: + steps = {_Operand, _Comma, _Operand, _Comma, _Operand}; + break; + case ParseAlg::Op4: + steps = {_Operand, _Comma, _Operand, _Comma, _Operand, _Comma, _Operand}; + break; + case ParseAlg::Op5: + steps = {_Operand, _Comma, _Operand, _Comma, _Operand, _Comma, _Operand, _Comma, _Operand}; + break; + case ParseAlg::Op1Off1Op2: + steps = {_Operand, _Comma, _Operand, _Lparen, _Operand, + _Rparen, _Comma, _Operand, _Comma, _Operand}; + break; + default: + ASSERT(false); + return; + } + + for (ParseStep step : steps) + { + bool stop_parse = false; + switch (step) + { + case _Operand: + ParseOperand(state); + break; + case _Comma: + state->ParseToken(TokenType::Comma); + break; + case _Lparen: + state->ParseToken(TokenType::Lparen); + break; + case _Rparen: + state->ParseToken(TokenType::Rparen); + break; + case _OptComma: + if (state->HasToken(TokenType::Comma)) + { + state->ParseToken(TokenType::Comma); + } + else + { + stop_parse = true; + } + break; + } + if (state->error) + { + return; + } + if (stop_parse) + { + break; + } + } +} + +void ParseInstruction(ParseState* state) +{ + state->lexer.SetIdentifierMatchRule(Lexer::IdentifierMatchRule::Mnemonic); + + AssemblerToken mnemonic_token = state->lexer.Lookahead(); + if (mnemonic_token.token_type != TokenType::Identifier) + { + state->lexer.SetIdentifierMatchRule(Lexer::IdentifierMatchRule::Typical); + return; + } + + ParseInfo const* parse_info = mnemonic_tokens.Find(mnemonic_token.token_val); + bool is_extended = false; + if (parse_info == nullptr) + { + parse_info = extended_mnemonic_tokens.Find(mnemonic_token.token_val); + if (parse_info == nullptr) + { + state->EmitErrorHere( + fmt::format("Unknown or unsupported mnemonic '{}'", mnemonic_token.ValStr())); + return; + } + is_extended = true; + } + + state->plugin.OnInstructionPre(*parse_info, is_extended); + + state->lexer.EatAndReset(); + + ParseOperandList(state, parse_info->parse_algorithm); + if (state->error) + { + return; + } + + state->plugin.OnInstructionPost(*parse_info, is_extended); +} + +void ParseLabel(ParseState* state) +{ + std::array tokens; + state->lexer.LookaheadN(&tokens); + + if (tokens[0].token_type == TokenType::Identifier && tokens[1].token_type == TokenType::Colon) + { + state->plugin.OnLabelDecl(tokens[0].token_val); + if (state->error) + { + return; + } + state->lexer.EatN<2>(); + } +} + +void ParseResolvedExpr(ParseState* state) +{ + state->plugin.OnResolvedExprPre(); + ParseBitor(state); + if (state->error) + { + return; + } + state->plugin.OnResolvedExprPost(); +} + +void ParseExpressionList(ParseState* state) +{ + ParseResolvedExpr(state); + if (state->error) + { + return; + } + + while (state->HasToken(TokenType::Comma)) + { + state->lexer.Eat(); + ParseResolvedExpr(state); + if (state->error) + { + return; + } + } +} + +void ParseFloat(ParseState* state) +{ + AssemblerToken flt_token = state->lexer.LookaheadFloat(); + if (flt_token.token_type != TokenType::FloatLit) + { + state->EmitErrorHere("Invalid floating point literal"); + return; + } + state->plugin.OnTerminal(Terminal::Flt, flt_token); + state->lexer.Eat(); +} + +void ParseFloatList(ParseState* state) +{ + ParseFloat(state); + if (state->error) + { + return; + } + + while (state->HasToken(TokenType::Comma)) + { + state->lexer.Eat(); + ParseFloat(state); + if (state->error) + { + return; + } + } +} + +void ParseDefvar(ParseState* state) +{ + AssemblerToken tok = state->lexer.Lookahead(); + if (tok.token_type == TokenType::Identifier) + { + state->plugin.OnVarDecl(tok.token_val); + if (state->error) + { + return; + } + state->lexer.Eat(); + + state->ParseToken(TokenType::Comma); + if (state->error) + { + return; + } + + ParseResolvedExpr(state); + } + else + { + state->EmitErrorHere(fmt::format("Expected an identifier, but found '{}'", tok.ValStr())); + } +} + +void ParseString(ParseState* state) +{ + AssemblerToken tok = state->lexer.Lookahead(); + if (tok.token_type == TokenType::StringLit) + { + state->plugin.OnTerminal(Terminal::Str, tok); + state->lexer.Eat(); + } + else + { + state->EmitErrorHere(fmt::format("Expected a string literal, but found '{}'", tok.ValStr())); + } +} + +void ParseDirective(ParseState* state) +{ + // TODO: test directives + state->lexer.SetIdentifierMatchRule(Lexer::IdentifierMatchRule::Directive); + AssemblerToken tok = state->lexer.Lookahead(); + if (tok.token_type != TokenType::Identifier) + { + state->EmitErrorHere(fmt::format("Unexpected token '{}' in directive type", tok.ValStr())); + return; + } + + GekkoDirective const* directive_enum = directives_map.Find(tok.token_val); + if (directive_enum == nullptr) + { + state->EmitErrorHere(fmt::format("Unknown assembler directive '{}'", tok.ValStr())); + return; + } + + state->plugin.OnDirectivePre(*directive_enum); + + state->lexer.EatAndReset(); + switch (*directive_enum) + { + case GekkoDirective::Byte: + case GekkoDirective::_2byte: + case GekkoDirective::_4byte: + case GekkoDirective::_8byte: + ParseExpressionList(state); + break; + + case GekkoDirective::Float: + case GekkoDirective::Double: + ParseFloatList(state); + break; + + case GekkoDirective::Locate: + case GekkoDirective::Zeros: + case GekkoDirective::Skip: + ParseResolvedExpr(state); + break; + + case GekkoDirective::PadAlign: + case GekkoDirective::Align: + ParseImm(state); + break; + + case GekkoDirective::DefVar: + ParseDefvar(state); + break; + + case GekkoDirective::Ascii: + case GekkoDirective::Asciz: + ParseString(state); + break; + } + + if (state->error) + { + return; + } + + state->plugin.OnDirectivePost(*directive_enum); +} + +void ParseLine(ParseState* state) +{ + if (state->HasToken(TokenType::Dot)) + { + state->ParseToken(TokenType::Dot); + ParseDirective(state); + } + else + { + ParseInstruction(state); + } +} + +void ParseProgram(ParseState* state) +{ + AssemblerToken tok = state->lexer.Lookahead(); + if (tok.token_type == TokenType::Eof) + { + state->eof = true; + return; + } + ParseLabel(state); + if (state->error) + { + return; + } + ParseLine(state); + if (state->error) + { + return; + } + + while (!state->eof && !state->error) + { + tok = state->lexer.Lookahead(); + if (tok.token_type == TokenType::Eof) + { + state->eof = true; + } + else if (tok.token_type == TokenType::Eol) + { + state->lexer.Eat(); + ParseLabel(state); + if (state->error) + { + return; + } + ParseLine(state); + } + else + { + state->EmitErrorHere( + fmt::format("Unexpected token '{}' where line should have ended", tok.ValStr())); + } + } +} +} // namespace + +ParseState::ParseState(std::string_view input_str, ParsePlugin& p) + : lexer(input_str), plugin(p), eof(false) +{ +} + +bool ParseState::HasToken(TokenType tp) const +{ + return lexer.LookaheadType() == tp; +} + +void ParseState::ParseToken(TokenType tp) +{ + AssemblerToken tok = lexer.LookaheadRef(); + if (tok.token_type == tp) + { + lexer.Eat(); + } + else + { + EmitErrorHere(fmt::format("Expected '{}' but found '{}'", TokenTypeToStr(tp), tok.ValStr())); + } +} + +void ParseState::EmitErrorHere(std::string&& message) +{ + AssemblerToken cur_token = lexer.Lookahead(); + if (cur_token.token_type == TokenType::Invalid) + { + error = AssemblerError{ + std::string(cur_token.invalid_reason), + lexer.CurrentLine(), + lexer.LineNumber(), + lexer.ColNumber() + cur_token.invalid_region.begin, + cur_token.invalid_region.len, + }; + } + else + { + error = AssemblerError{ + std::move(message), lexer.CurrentLine(), lexer.LineNumber(), + lexer.ColNumber(), cur_token.token_val.size(), + }; + } +} + +void ParseWithPlugin(ParsePlugin* plugin, std::string_view input) +{ + ParseState parse_state = ParseState(input, *plugin); + plugin->SetOwner(&parse_state); + ParseProgram(&parse_state); + + if (parse_state.error) + { + plugin->OnError(); + plugin->ForwardError(std::move(*parse_state.error)); + } + else + { + plugin->PostParseAction(); + if (parse_state.error) + { + plugin->OnError(); + plugin->ForwardError(std::move(*parse_state.error)); + } + } + + plugin->SetOwner(nullptr); +} +} // namespace Common::GekkoAssembler::detail diff --git a/Source/Core/Common/Assembler/GekkoParser.h b/Source/Core/Common/Assembler/GekkoParser.h new file mode 100644 index 0000000000..4258b5d582 --- /dev/null +++ b/Source/Core/Common/Assembler/GekkoParser.h @@ -0,0 +1,124 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "Common/Assembler/AssemblerShared.h" +#include "Common/Assembler/GekkoLexer.h" +#include "Common/CommonTypes.h" + +namespace Common::GekkoAssembler::detail +{ +class ParsePlugin; + +struct ParseState +{ + ParseState(std::string_view input_str, ParsePlugin& plugin); + + bool HasToken(TokenType tp) const; + void ParseToken(TokenType tp); + void EmitErrorHere(std::string&& message); + + Lexer lexer; + ParsePlugin& plugin; + + std::optional error; + bool eof; +}; + +enum class AsmOp +{ + Or, + Xor, + And, + Lsh, + Rsh, + Add, + Sub, + Mul, + Div, + Neg, + Not +}; + +enum class Terminal +{ + Hex, + Dec, + Oct, + Bin, + Flt, + Str, + Id, + GPR, + FPR, + SPR, + CRField, + Lt, + Gt, + Eq, + So, + Dot, +}; + +enum class ParenType +{ + Normal, + RelConv, +}; + +// Overridable plugin class supporting a series of skeleton functions which get called when +// the parser parses a given point of interest +class ParsePlugin +{ +public: + ParsePlugin() : m_owner(nullptr) {} + virtual ~ParsePlugin() = default; + + void SetOwner(ParseState* o) { m_owner = o; } + void ForwardError(AssemblerError&& err) { m_owner_error = std::move(err); } + std::optional& Error() { return m_owner_error; } + + virtual void PostParseAction() {} + + // Nonterminal callouts + // Pre occurs prior to the head nonterminal being parsed + // Post occurs after the nonterminal has been fully parsed + virtual void OnDirectivePre(GekkoDirective directive) {} + virtual void OnDirectivePost(GekkoDirective directive) {} + virtual void OnInstructionPre(const ParseInfo& mnemonic_info, bool extended) {} + virtual void OnInstructionPost(const ParseInfo& mnemonic_info, bool extended) {} + virtual void OnOperandPre() {} + virtual void OnOperandPost() {} + virtual void OnResolvedExprPre() {} + virtual void OnResolvedExprPost() {} + + // Operator callouts + // All occur after the relevant operands have been parsed + virtual void OnOperator(AsmOp operation) {} + + // Individual token callouts + // All occur prior to the token being parsed + // Due to ambiguity of some tokens, an explicit operation is provided + virtual void OnTerminal(Terminal type, const AssemblerToken& val) {} + virtual void OnHiaddr(std::string_view id) {} + virtual void OnLoaddr(std::string_view id) {} + virtual void OnOpenParen(ParenType type) {} + virtual void OnCloseParen(ParenType type) {} + virtual void OnError() {} + virtual void OnLabelDecl(std::string_view name) {} + virtual void OnVarDecl(std::string_view name) {} + +protected: + ParseState* m_owner; + std::optional m_owner_error; +}; + +// Parse the provided input with a plugin to handle what to do with certain points of interest +// e.g. Convert to an IR for generating final machine code, picking up syntactical information +void ParseWithPlugin(ParsePlugin* plugin, std::string_view input); +} // namespace Common::GekkoAssembler::detail diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index d3e635925e..1f81242592 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -1,6 +1,18 @@ add_library(common Analytics.cpp Analytics.h + Assembler/AssemblerShared.cpp + Assembler/AssemblerShared.h + Assembler/AssemblerTables.cpp + Assembler/AssemblerTables.h + Assembler/GekkoAssembler.cpp + Assembler/GekkoAssembler.h + Assembler/GekkoIRGen.cpp + Assembler/GekkoIRGen.h + Assembler/GekkoLexer.cpp + Assembler/GekkoLexer.h + Assembler/GekkoParser.cpp + Assembler/GekkoParser.h Assert.h BitField.h BitSet.h diff --git a/Source/Core/Common/CommonPaths.h b/Source/Core/Common/CommonPaths.h index 44ada6dd19..bcdbcc3be6 100644 --- a/Source/Core/Common/CommonPaths.h +++ b/Source/Core/Common/CommonPaths.h @@ -94,6 +94,7 @@ #define DYNAMICINPUT_DIR "DynamicInputTextures" #define GRAPHICSMOD_DIR "GraphicMods" #define WIISDSYNC_DIR "WiiSDSync" +#define ASSEMBLY_DIR "SavedAssembly" // This one is only used to remove it if it was present #define SHADERCACHE_LEGACY_DIR "ShaderCache" diff --git a/Source/Core/Common/FileUtil.cpp b/Source/Core/Common/FileUtil.cpp index eb880c51ad..868125431e 100644 --- a/Source/Core/Common/FileUtil.cpp +++ b/Source/Core/Common/FileUtil.cpp @@ -897,6 +897,8 @@ static void RebuildUserDirectories(unsigned int dir_index) s_user_paths[D_GBASAVES_IDX] = s_user_paths[D_GBAUSER_IDX] + GBASAVES_DIR DIR_SEP; s_user_paths[F_GBABIOS_IDX] = s_user_paths[D_GBAUSER_IDX] + GBA_BIOS; + s_user_paths[D_ASM_ROOT_IDX] = s_user_paths[D_USER_IDX] + ASSEMBLY_DIR DIR_SEP; + // The shader cache has moved to the cache directory, so remove the old one. // TODO: remove that someday. File::DeleteDirRecursively(s_user_paths[D_USER_IDX] + SHADERCACHE_LEGACY_DIR DIR_SEP); diff --git a/Source/Core/Common/FileUtil.h b/Source/Core/Common/FileUtil.h index 8d5f312d65..39ed26ad13 100644 --- a/Source/Core/Common/FileUtil.h +++ b/Source/Core/Common/FileUtil.h @@ -71,6 +71,7 @@ enum D_GPU_DRIVERS_TMP, D_GPU_DRIVERS_HOOKS, D_GPU_DRIVERS_FILE_REDIRECT, + D_ASM_ROOT_IDX, FIRST_FILE_USER_PATH_IDX, F_DOLPHINCONFIG_IDX = FIRST_FILE_USER_PATH_IDX, F_GCPADCONFIG_IDX, diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index 391b1f1afb..927c125bcb 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -198,6 +198,12 @@ add_executable(dolphin-emu Config/WiimoteControllersWidget.h ConvertDialog.cpp ConvertDialog.h + Debugger/AssembleInstructionDialog.cpp + Debugger/AssembleInstructionDialog.h + Debugger/AssemblerWidget.cpp + Debugger/AssemblerWidget.h + Debugger/AssemblyEditor.cpp + Debugger/AssemblyEditor.h Debugger/BreakpointDialog.cpp Debugger/BreakpointDialog.h Debugger/BreakpointWidget.cpp @@ -208,6 +214,8 @@ add_executable(dolphin-emu Debugger/CodeViewWidget.h Debugger/CodeWidget.cpp Debugger/CodeWidget.h + Debugger/GekkoSyntaxHighlight.cpp + Debugger/GekkoSyntaxHighlight.h Debugger/JITWidget.cpp Debugger/JITWidget.h Debugger/MemoryViewWidget.cpp diff --git a/Source/Core/DolphinQt/Debugger/AssembleInstructionDialog.cpp b/Source/Core/DolphinQt/Debugger/AssembleInstructionDialog.cpp new file mode 100644 index 0000000000..e498dc06e9 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/AssembleInstructionDialog.cpp @@ -0,0 +1,129 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Debugger/AssembleInstructionDialog.h" + +#include +#include +#include +#include +#include +#include + +#include "Common/Assembler/GekkoAssembler.h" +#include "Common/StringUtil.h" + +namespace +{ +QString HtmlFormatErrorLoc(const Common::GekkoAssembler::AssemblerError& err) +{ + return QObject::tr("Error on line %1 col %2") + .arg(err.line + 1) + .arg(err.col + 1); +} + +QString HtmlFormatErrorLine(const Common::GekkoAssembler::AssemblerError& err) +{ + const QString line_pre_error = + QString::fromStdString(std::string(err.error_line.substr(0, err.col))).toHtmlEscaped(); + const QString line_error = + QString::fromStdString(std::string(err.error_line.substr(err.col, err.len))).toHtmlEscaped(); + const QString line_post_error = + QString::fromStdString(std::string(err.error_line.substr(err.col + err.len))).toHtmlEscaped(); + + return QObject::tr("%1%2%3") + .arg(line_pre_error) + .arg(line_error) + .arg(line_post_error); +} +} // namespace + +AssembleInstructionDialog::AssembleInstructionDialog(QWidget* parent, u32 address, u32 value) + : QDialog(parent), m_code(value), m_address(address) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setWindowModality(Qt::WindowModal); + setWindowTitle(tr("Instruction")); + + CreateWidgets(); + ConnectWidgets(); +} + +void AssembleInstructionDialog::CreateWidgets() +{ + auto* layout = new QVBoxLayout; + + m_input_edit = new QLineEdit; + m_error_loc_label = new QLabel; + m_error_line_label = new QLabel; + m_msg_label = new QLabel(tr("No input")); + m_button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + m_error_line_label->setFont(QFont(QFontDatabase::systemFont(QFontDatabase::FixedFont).family())); + m_input_edit->setFont(QFont(QFontDatabase::systemFont(QFontDatabase::FixedFont).family())); + layout->addWidget(new QLabel(tr("Inline Assembler"))); + layout->addWidget(m_error_loc_label); + layout->addWidget(m_input_edit); + layout->addWidget(m_error_line_label); + layout->addWidget(m_msg_label); + layout->addWidget(m_button_box); + m_input_edit->setText(QStringLiteral(".4byte 0x%1").arg(m_code, 8, 16, QLatin1Char('0'))); + + setLayout(layout); + OnEditChanged(); +} + +void AssembleInstructionDialog::ConnectWidgets() +{ + connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + connect(m_input_edit, &QLineEdit::textChanged, this, &AssembleInstructionDialog::OnEditChanged); +} + +void AssembleInstructionDialog::OnEditChanged() +{ + using namespace Common::GekkoAssembler; + std::string line = m_input_edit->text().toStdString(); + Common::ToLower(&line); + + FailureOr> asm_result = Assemble(line, m_address); + + if (IsFailure(asm_result)) + { + m_button_box->button(QDialogButtonBox::Ok)->setEnabled(false); + + const AssemblerError& failure = GetFailure(asm_result); + m_error_loc_label->setText(HtmlFormatErrorLoc(failure)); + m_error_line_label->setText(HtmlFormatErrorLine(failure)); + m_msg_label->setText(QString::fromStdString(failure.message).toHtmlEscaped()); + } + else if (GetT(asm_result).empty() || GetT(asm_result)[0].instructions.empty()) + { + m_button_box->button(QDialogButtonBox::Ok)->setEnabled(false); + + m_error_loc_label->setText(tr("Error")); + m_error_line_label->clear(); + m_msg_label->setText(tr("No input")); + } + else + { + m_button_box->button(QDialogButtonBox::Ok)->setEnabled(true); + m_code = 0; + + const std::vector& block_bytes = GetT(asm_result)[0].instructions; + for (size_t i = 0; i < 4 && i < block_bytes.size(); i++) + { + m_code = (m_code << 8) | block_bytes[i]; + } + + m_error_loc_label->setText(tr("Ok")); + m_error_line_label->clear(); + m_msg_label->setText(tr("Instruction: %1").arg(m_code, 8, 16, QLatin1Char('0'))); + } +} + +u32 AssembleInstructionDialog::GetCode() const +{ + return m_code; +} diff --git a/Source/Core/DolphinQt/Debugger/AssembleInstructionDialog.h b/Source/Core/DolphinQt/Debugger/AssembleInstructionDialog.h new file mode 100644 index 0000000000..de2191e2c7 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/AssembleInstructionDialog.h @@ -0,0 +1,36 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "Common/CommonTypes.h" + +class QDialogButtonBox; +class QLabel; +class QLineEdit; + +class AssembleInstructionDialog : public QDialog +{ + Q_OBJECT +public: + explicit AssembleInstructionDialog(QWidget* parent, u32 address, u32 value); + + u32 GetCode() const; + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void OnEditChanged(); + + u32 m_code; + u32 m_address; + + QLineEdit* m_input_edit; + QLabel* m_error_loc_label; + QLabel* m_error_line_label; + QLabel* m_msg_label; + QDialogButtonBox* m_button_box; +}; diff --git a/Source/Core/DolphinQt/Debugger/AssemblerWidget.cpp b/Source/Core/DolphinQt/Debugger/AssemblerWidget.cpp new file mode 100644 index 0000000000..bf47a36001 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/AssemblerWidget.cpp @@ -0,0 +1,957 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Debugger/AssemblerWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Common/Assert.h" +#include "Common/FileUtil.h" + +#include "Core/Core.h" +#include "Core/PowerPC/MMU.h" +#include "Core/PowerPC/PowerPC.h" +#include "Core/System.h" + +#include "DolphinQt/Debugger/AssemblyEditor.h" +#include "DolphinQt/QtUtils/DolphinFileDialog.h" +#include "DolphinQt/QtUtils/ModalMessageBox.h" +#include "DolphinQt/Resources.h" +#include "DolphinQt/Settings.h" + +namespace +{ +using namespace Common::GekkoAssembler; + +QString HtmlFormatErrorLoc(const AssemblerError& err) +{ + return QObject::tr("Error on line %1 col %2") + .arg(err.line + 1) + .arg(err.col + 1); +} + +QString HtmlFormatErrorLine(const AssemblerError& err) +{ + const QString line_pre_error = + QString::fromStdString(std::string(err.error_line.substr(0, err.col))).toHtmlEscaped(); + const QString line_error = + QString::fromStdString(std::string(err.error_line.substr(err.col, err.len))).toHtmlEscaped(); + const QString line_post_error = + QString::fromStdString(std::string(err.error_line.substr(err.col + err.len))).toHtmlEscaped(); + + return QObject::tr("" + "
%1%2%3
" + "
") + .arg(line_pre_error) + .arg(line_error) + .arg(line_post_error); +} + +QString HtmlFormatMessage(const AssemblerError& err) +{ + return QObject::tr("%1").arg(QString::fromStdString(err.message).toHtmlEscaped()); +} + +void DeserializeBlock(const CodeBlock& blk, std::ostringstream& out_str, bool pad4) +{ + size_t i = 0; + for (; i < blk.instructions.size(); i++) + { + out_str << fmt::format("{:02x}", blk.instructions[i]); + if (i % 8 == 7) + { + out_str << '\n'; + } + else if (i % 4 == 3) + { + out_str << ' '; + } + } + if (pad4) + { + bool did_pad = false; + for (; i % 4 != 0; i++) + { + out_str << "00"; + did_pad = true; + } + + if (did_pad) + { + out_str << (i % 8 == 0 ? '\n' : ' '); + } + } + else if (i % 8 != 7) + { + out_str << '\n'; + } +} + +void DeserializeToRaw(const std::vector& blocks, std::ostringstream& out_str) +{ + for (const auto& blk : blocks) + { + if (blk.instructions.empty()) + { + continue; + } + + out_str << fmt::format("# Block {:08x}\n", blk.block_address); + DeserializeBlock(blk, out_str, false); + } +} + +void DeserializeToAr(const std::vector& blocks, std::ostringstream& out_str) +{ + for (const auto& blk : blocks) + { + if (blk.instructions.empty()) + { + continue; + } + + size_t i = 0; + for (; i < blk.instructions.size() - 3; i += 4) + { + // type=NormalCode, subtype=SUB_RAM_WRITE, size=32bit + const u32 ar_addr = ((blk.block_address + i) & 0x1ffffff) | 0x04000000; + out_str << fmt::format("{:08x} {:02x}{:02x}{:02x}{:02x}\n", ar_addr, blk.instructions[i], + blk.instructions[i + 1], blk.instructions[i + 2], + blk.instructions[i + 3]); + } + + for (; i < blk.instructions.size(); i++) + { + // type=NormalCode, subtype=SUB_RAM_WRITE, size=8bit + const u32 ar_addr = ((blk.block_address + i) & 0x1ffffff); + out_str << fmt::format("{:08x} 000000{:02x}\n", ar_addr, blk.instructions[i]); + } + } +} + +void DeserializeToGecko(const std::vector& blocks, std::ostringstream& out_str) +{ + DeserializeToAr(blocks, out_str); +} + +void DeserializeToGeckoExec(const std::vector& blocks, std::ostringstream& out_str) +{ + for (const auto& blk : blocks) + { + if (blk.instructions.empty()) + { + continue; + } + + u32 nlines = 1 + static_cast((blk.instructions.size() - 1) / 8); + bool ret_on_newline = false; + if (blk.instructions.size() % 8 == 0 || blk.instructions.size() % 8 > 4) + { + // Append extra line for blr + nlines++; + ret_on_newline = true; + } + + out_str << fmt::format("c0000000 {:08x}\n", nlines); + DeserializeBlock(blk, out_str, true); + if (ret_on_newline) + { + out_str << "4e800020 00000000\n"; + } + else + { + out_str << "4e800020\n"; + } + } +} + +void DeserializeToGeckoTramp(const std::vector& blocks, std::ostringstream& out_str) +{ + for (const auto& blk : blocks) + { + if (blk.instructions.empty()) + { + continue; + } + + const u32 inject_addr = (blk.block_address & 0x1ffffff) | 0x02000000; + u32 nlines = 1 + static_cast((blk.instructions.size() - 1) / 8); + bool padding_on_newline = false; + if (blk.instructions.size() % 8 == 0 || blk.instructions.size() % 8 > 4) + { + // Append extra line for nop+branchback + nlines++; + padding_on_newline = true; + } + + out_str << fmt::format("c{:07x} {:08x}\n", inject_addr, nlines); + DeserializeBlock(blk, out_str, true); + if (padding_on_newline) + { + out_str << "60000000 00000000\n"; + } + else + { + out_str << "00000000\n"; + } + } +} +} // namespace + +AssemblerWidget::AssemblerWidget(QWidget* parent) + : QDockWidget(parent), m_system(Core::System::GetInstance()), m_unnamed_editor_count(0), + m_net_zoom_delta(0) +{ + { + QPalette base_palette; + m_dark_scheme = base_palette.color(QPalette::WindowText).value() > + base_palette.color(QPalette::Window).value(); + } + + setWindowTitle(tr("Assembler")); + setObjectName(QStringLiteral("assemblerwidget")); + + setHidden(!Settings::Instance().IsAssemblerVisible() || + !Settings::Instance().IsDebugModeEnabled()); + + this->setVisible(true); + CreateWidgets(); + + restoreGeometry( + Settings::GetQSettings().value(QStringLiteral("assemblerwidget/geometry")).toByteArray()); + setFloating(Settings::GetQSettings().value(QStringLiteral("assemblerwidget/floating")).toBool()); + + connect(&Settings::Instance(), &Settings::AssemblerVisibilityChanged, this, + [this](bool visible) { setHidden(!visible); }); + + connect(&Settings::Instance(), &Settings::DebugModeToggled, this, [this](bool enabled) { + setHidden(!enabled || !Settings::Instance().IsAssemblerVisible()); + }); + + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, + &AssemblerWidget::OnEmulationStateChanged); + connect(&Settings::Instance(), &Settings::ThemeChanged, this, &AssemblerWidget::UpdateIcons); + connect(m_asm_tabs, &QTabWidget::tabCloseRequested, this, &AssemblerWidget::OnTabClose); + + auto* save_shortcut = new QShortcut(QKeySequence::Save, this); + // Save should only activate if the active tab is in focus + save_shortcut->connect(save_shortcut, &QShortcut::activated, this, [this] { + if (m_asm_tabs->currentIndex() != -1 && m_asm_tabs->currentWidget()->hasFocus()) + { + OnSave(); + } + }); + + auto* zoom_in_shortcut = new QShortcut(QKeySequence::ZoomIn, this); + zoom_in_shortcut->setContext(Qt::WidgetWithChildrenShortcut); + connect(zoom_in_shortcut, &QShortcut::activated, this, &AssemblerWidget::OnZoomIn); + auto* zoom_out_shortcut = new QShortcut(QKeySequence::ZoomOut, this); + zoom_out_shortcut->setContext(Qt::WidgetWithChildrenShortcut); + connect(zoom_out_shortcut, &QShortcut::activated, this, &AssemblerWidget::OnZoomOut); + + auto* zoom_in_alternate = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Equal), this); + zoom_in_alternate->setContext(Qt::WidgetWithChildrenShortcut); + connect(zoom_in_alternate, &QShortcut::activated, this, &AssemblerWidget::OnZoomIn); + auto* zoom_out_alternate = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Underscore), this); + zoom_out_alternate->setContext(Qt::WidgetWithChildrenShortcut); + connect(zoom_out_alternate, &QShortcut::activated, this, &AssemblerWidget::OnZoomOut); + + auto* zoom_reset = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_0), this); + zoom_reset->setContext(Qt::WidgetWithChildrenShortcut); + connect(zoom_reset, &QShortcut::activated, this, &AssemblerWidget::OnZoomReset); + + ConnectWidgets(); + UpdateIcons(); +} + +void AssemblerWidget::closeEvent(QCloseEvent*) +{ + Settings::Instance().SetAssemblerVisible(false); +} + +bool AssemblerWidget::ApplicationCloseRequest() +{ + int num_unsaved = 0; + for (int i = 0; i < m_asm_tabs->count(); i++) + { + if (GetEditor(i)->IsDirty()) + { + num_unsaved++; + } + } + + if (num_unsaved > 0) + { + const int result = ModalMessageBox::question( + this, tr("Unsaved Changes"), + tr("You have %1 unsaved assembly tabs open\n\n" + "Do you want to save all and exit?") + .arg(num_unsaved), + QMessageBox::YesToAll | QMessageBox::NoToAll | QMessageBox::Cancel, QMessageBox::Cancel); + switch (result) + { + case QMessageBox::YesToAll: + for (int i = 0; i < m_asm_tabs->count(); i++) + { + AsmEditor* editor = GetEditor(i); + if (editor->IsDirty()) + { + if (!SaveEditor(editor)) + { + return false; + } + } + } + return true; + case QMessageBox::NoToAll: + return true; + case QMessageBox::Cancel: + return false; + } + } + + return true; +} + +AssemblerWidget::~AssemblerWidget() +{ + auto& settings = Settings::GetQSettings(); + + settings.setValue(QStringLiteral("assemblerwidget/geometry"), saveGeometry()); + settings.setValue(QStringLiteral("assemblerwidget/floating"), isFloating()); +} + +void AssemblerWidget::CreateWidgets() +{ + m_asm_tabs = new QTabWidget; + m_toolbar = new QToolBar; + m_output_type = new QComboBox; + m_output_box = new QPlainTextEdit; + m_error_box = new QTextEdit; + m_address_line = new QLineEdit; + m_copy_output_button = new QPushButton; + + m_asm_tabs->setTabsClosable(true); + + // Initialize toolbar and actions + // m_toolbar->setIconSize(QSize(32, 32)); + m_toolbar->setContentsMargins(0, 0, 0, 0); + m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + + m_open = m_toolbar->addAction(tr("Open"), this, &AssemblerWidget::OnOpen); + m_new = m_toolbar->addAction(tr("New"), this, &AssemblerWidget::OnNew); + m_assemble = m_toolbar->addAction(tr("Assemble"), this, [this] { + std::vector unused; + OnAssemble(&unused); + }); + m_inject = m_toolbar->addAction(tr("Inject"), this, &AssemblerWidget::OnInject); + m_save = m_toolbar->addAction(tr("Save"), this, &AssemblerWidget::OnSave); + + m_inject->setEnabled(false); + m_save->setEnabled(false); + m_assemble->setEnabled(false); + + // Initialize input, output, error text areas + auto palette = m_output_box->palette(); + if (m_dark_scheme) + { + palette.setColor(QPalette::Base, QColor::fromRgb(76, 76, 76)); + } + else + { + palette.setColor(QPalette::Base, QColor::fromRgb(180, 180, 180)); + } + m_output_box->setPalette(palette); + m_error_box->setPalette(palette); + + QFont mono_font(QFontDatabase::systemFont(QFontDatabase::FixedFont).family()); + QFont error_font(QFontDatabase::systemFont(QFontDatabase::GeneralFont).family()); + mono_font.setPointSize(12); + error_font.setPointSize(12); + QFontMetrics mono_metrics(mono_font); + QFontMetrics err_metrics(mono_font); + + m_output_box->setFont(mono_font); + m_error_box->setFont(error_font); + m_output_box->setReadOnly(true); + m_error_box->setReadOnly(true); + + const int output_area_width = mono_metrics.horizontalAdvance(QLatin1Char('0')) * OUTPUT_BOX_WIDTH; + m_error_box->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + m_error_box->setFixedHeight(err_metrics.height() * 3 + mono_metrics.height()); + m_output_box->setFixedWidth(output_area_width); + m_error_box->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + + // Initialize output format selection box + m_output_type->addItem(tr("Raw")); + m_output_type->addItem(tr("AR Code")); + m_output_type->addItem(tr("Gecko (04)")); + m_output_type->addItem(tr("Gecko (C0)")); + m_output_type->addItem(tr("Gecko (C2)")); + + // Setup layouts + auto* addr_input_layout = new QHBoxLayout; + addr_input_layout->addWidget(new QLabel(tr("Base Address"))); + addr_input_layout->addWidget(m_address_line); + + auto* output_extra_layout = new QHBoxLayout; + output_extra_layout->addWidget(m_output_type); + output_extra_layout->addWidget(m_copy_output_button); + + QWidget* address_input_box = new QWidget(); + address_input_box->setLayout(addr_input_layout); + addr_input_layout->setContentsMargins(0, 0, 0, 0); + + QWidget* output_extra_box = new QWidget(); + output_extra_box->setFixedWidth(output_area_width); + output_extra_box->setLayout(output_extra_layout); + output_extra_layout->setContentsMargins(0, 0, 0, 0); + + auto* assembler_layout = new QGridLayout; + assembler_layout->setSpacing(0); + assembler_layout->setContentsMargins(5, 0, 5, 5); + assembler_layout->addWidget(m_toolbar, 0, 0, 1, 2); + { + auto* input_group = new QGroupBox(tr("Input")); + auto* layout = new QVBoxLayout; + input_group->setLayout(layout); + layout->addWidget(m_asm_tabs); + layout->addWidget(address_input_box); + assembler_layout->addWidget(input_group, 1, 0, 1, 1); + } + { + auto* output_group = new QGroupBox(tr("Output")); + auto* layout = new QGridLayout; + output_group->setLayout(layout); + layout->addWidget(m_output_box, 0, 0); + layout->addWidget(output_extra_box, 1, 0); + assembler_layout->addWidget(output_group, 1, 1, 1, 1); + output_group->setSizePolicy( + QSizePolicy(QSizePolicy::Policy::Fixed, QSizePolicy::Policy::Expanding)); + } + { + auto* error_group = new QGroupBox(tr("Error Log")); + auto* layout = new QHBoxLayout; + error_group->setLayout(layout); + layout->addWidget(m_error_box); + assembler_layout->addWidget(error_group, 2, 0, 1, 2); + error_group->setSizePolicy( + QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Fixed)); + } + + QWidget* widget = new QWidget; + widget->setLayout(assembler_layout); + setWidget(widget); +} + +void AssemblerWidget::ConnectWidgets() +{ + m_output_box->connect(m_output_box, &QPlainTextEdit::updateRequest, this, [this] { + if (m_output_box->verticalScrollBar()->isVisible()) + { + m_output_box->setFixedWidth(m_output_box->fontMetrics().horizontalAdvance(QLatin1Char('0')) * + OUTPUT_BOX_WIDTH + + m_output_box->style()->pixelMetric(QStyle::PM_ScrollBarExtent)); + } + else + { + m_output_box->setFixedWidth(m_output_box->fontMetrics().horizontalAdvance(QLatin1Char('0')) * + OUTPUT_BOX_WIDTH); + } + }); + m_copy_output_button->connect(m_copy_output_button, &QPushButton::released, this, + &AssemblerWidget::OnCopyOutput); + m_address_line->connect(m_address_line, &QLineEdit::textChanged, this, + &AssemblerWidget::OnBaseAddressChanged); + m_asm_tabs->connect(m_asm_tabs, &QTabWidget::currentChanged, this, &AssemblerWidget::OnTabChange); +} + +void AssemblerWidget::OnAssemble(std::vector* asm_out) +{ + if (m_asm_tabs->currentIndex() == -1) + { + return; + } + AsmEditor* active_editor = GetEditor(m_asm_tabs->currentIndex()); + + AsmKind kind = AsmKind::Raw; + m_error_box->clear(); + m_output_box->clear(); + switch (m_output_type->currentIndex()) + { + case 0: + kind = AsmKind::Raw; + break; + case 1: + kind = AsmKind::ActionReplay; + break; + case 2: + kind = AsmKind::Gecko; + break; + case 3: + kind = AsmKind::GeckoExec; + break; + case 4: + kind = AsmKind::GeckoTrampoline; + break; + } + + bool good; + u32 base_address = m_address_line->text().toUInt(&good, 16); + if (!good) + { + base_address = 0; + m_error_box->append( + tr("Warning invalid base address, defaulting to 0")); + } + + const std::string contents = active_editor->toPlainText().toStdString(); + auto result = Assemble(contents, base_address); + if (IsFailure(result)) + { + m_error_box->clear(); + asm_out->clear(); + + const AssemblerError& error = GetFailure(result); + m_error_box->append(HtmlFormatErrorLoc(error)); + m_error_box->append(HtmlFormatErrorLine(error)); + m_error_box->append(HtmlFormatMessage(error)); + asm_out->clear(); + return; + } + + auto& blocks = GetT(result); + std::ostringstream str_contents; + switch (kind) + { + case AsmKind::Raw: + DeserializeToRaw(blocks, str_contents); + break; + case AsmKind::ActionReplay: + DeserializeToAr(blocks, str_contents); + break; + case AsmKind::Gecko: + DeserializeToGecko(blocks, str_contents); + break; + case AsmKind::GeckoExec: + DeserializeToGeckoExec(blocks, str_contents); + break; + case AsmKind::GeckoTrampoline: + DeserializeToGeckoTramp(blocks, str_contents); + break; + } + + m_output_box->appendPlainText(QString::fromStdString(str_contents.str())); + m_output_box->moveCursor(QTextCursor::MoveOperation::Start); + m_output_box->ensureCursorVisible(); + + *asm_out = std::move(GetT(result)); +} + +void AssemblerWidget::OnCopyOutput() +{ + QApplication::clipboard()->setText(m_output_box->toPlainText()); +} + +void AssemblerWidget::OnOpen() +{ + const std::string default_dir = File::GetUserPath(D_ASM_ROOT_IDX); + const QStringList paths = DolphinFileDialog::getOpenFileNames( + this, tr("Select a File"), QString::fromStdString(default_dir), + QStringLiteral("%1 (*.s *.S *.asm);;%2 (*)") + .arg(tr("All Assembly files")) + .arg(tr("All Files"))); + if (paths.isEmpty()) + { + return; + } + + std::optional show_index; + for (auto path : paths) + { + show_index = std::nullopt; + for (int i = 0; i < m_asm_tabs->count(); i++) + { + AsmEditor* editor = GetEditor(i); + if (editor->PathsMatch(path)) + { + show_index = i; + break; + } + } + + if (!show_index) + { + NewEditor(path); + } + } + + if (show_index) + { + m_asm_tabs->setCurrentIndex(*show_index); + } +} + +void AssemblerWidget::OnNew() +{ + NewEditor(); +} + +void AssemblerWidget::OnInject() +{ + Core::CPUThreadGuard guard(m_system); + + std::vector asm_result; + OnAssemble(&asm_result); + for (const auto& blk : asm_result) + { + if (!PowerPC::MMU::HostIsRAMAddress(guard, blk.block_address) || blk.instructions.empty()) + { + continue; + } + + m_system.GetPowerPC().GetDebugInterface().SetPatch(guard, blk.block_address, blk.instructions); + } +} + +void AssemblerWidget::OnSave() +{ + if (m_asm_tabs->currentIndex() == -1) + { + return; + } + AsmEditor* active_editor = GetEditor(m_asm_tabs->currentIndex()); + + SaveEditor(active_editor); +} + +void AssemblerWidget::OnZoomIn() +{ + if (m_asm_tabs->currentIndex() != -1) + { + ZoomAllEditors(2); + } +} + +void AssemblerWidget::OnZoomOut() +{ + if (m_asm_tabs->currentIndex() != -1) + { + ZoomAllEditors(-2); + } +} + +void AssemblerWidget::OnZoomReset() +{ + if (m_asm_tabs->currentIndex() != -1) + { + ZoomAllEditors(-m_net_zoom_delta); + } +} + +void AssemblerWidget::OnBaseAddressChanged() +{ + if (m_asm_tabs->currentIndex() == -1) + { + return; + } + AsmEditor* active_editor = GetEditor(m_asm_tabs->currentIndex()); + + active_editor->SetBaseAddress(m_address_line->text()); +} + +void AssemblerWidget::OnTabChange(int index) +{ + if (index == -1) + { + m_address_line->clear(); + return; + } + AsmEditor* active_editor = GetEditor(index); + + m_address_line->setText(active_editor->BaseAddress()); +} + +QString AssemblerWidget::TabTextForEditor(AsmEditor* editor, bool with_dirty) +{ + ASSERT(editor != nullptr); + QString dirtyFlag = QStringLiteral(); + if (editor->IsDirty() && with_dirty) + { + dirtyFlag = QStringLiteral(" *"); + } + + if (editor->Path().isEmpty()) + { + if (editor->EditorNum() == 0) + { + return tr("New File%1").arg(dirtyFlag); + } + return tr("New File (%1)%2").arg(editor->EditorNum() + 1).arg(dirtyFlag); + } + return tr("%1%2").arg(editor->EditorTitle()).arg(dirtyFlag); +} + +AsmEditor* AssemblerWidget::GetEditor(int idx) +{ + return qobject_cast(m_asm_tabs->widget(idx)); +} + +void AssemblerWidget::NewEditor(const QString& path) +{ + AsmEditor* new_editor = + new AsmEditor(path, path.isEmpty() ? AllocateTabNum() : INVALID_EDITOR_NUM, m_dark_scheme); + if (!path.isEmpty() && !new_editor->LoadFromPath()) + { + ModalMessageBox::warning(this, tr("Failed to open file"), + tr("Failed to read the contents of file\n\n" + "\"%1\"") + .arg(path)); + delete new_editor; + return; + } + + const int tab_idx = m_asm_tabs->addTab(new_editor, QStringLiteral()); + new_editor->connect(new_editor, &AsmEditor::PathChanged, this, [this] { + AsmEditor* updated_tab = qobject_cast(sender()); + DisambiguateTabTitles(updated_tab); + UpdateTabText(updated_tab); + }); + new_editor->connect(new_editor, &AsmEditor::DirtyChanged, this, + [this] { UpdateTabText(qobject_cast(sender())); }); + new_editor->connect(new_editor, &AsmEditor::ZoomRequested, this, + &AssemblerWidget::ZoomAllEditors); + new_editor->Zoom(m_net_zoom_delta); + + DisambiguateTabTitles(new_editor); + + m_asm_tabs->setTabText(tab_idx, TabTextForEditor(new_editor, true)); + + if (m_save && m_assemble) + { + m_save->setEnabled(true); + m_assemble->setEnabled(true); + } + + m_asm_tabs->setCurrentIndex(tab_idx); +} + +bool AssemblerWidget::SaveEditor(AsmEditor* editor) +{ + QString save_path = editor->Path(); + if (save_path.isEmpty()) + { + const std::string default_dir = File::GetUserPath(D_ASM_ROOT_IDX); + const QString asm_filter = QStringLiteral("%1 (*.S)").arg(tr("Assembly File")); + const QString all_filter = QStringLiteral("%2 (*)").arg(tr("All Files")); + + QString selected_filter; + save_path = DolphinFileDialog::getSaveFileName( + this, tr("Save File to"), QString::fromStdString(default_dir), + QStringLiteral("%1;;%2").arg(asm_filter).arg(all_filter), &selected_filter); + + if (save_path.isEmpty()) + { + return false; + } + + if (selected_filter == asm_filter && + std::filesystem::path(save_path.toStdString()).extension().empty()) + { + save_path.append(QStringLiteral(".S")); + } + } + + editor->SaveFile(save_path); + return true; +} + +void AssemblerWidget::OnEmulationStateChanged(Core::State state) +{ + m_inject->setEnabled(state != Core::State::Uninitialized); +} + +void AssemblerWidget::OnTabClose(int index) +{ + ASSERT(index < m_asm_tabs->count()); + AsmEditor* editor = GetEditor(index); + + if (editor->IsDirty()) + { + const int result = ModalMessageBox::question( + this, tr("Unsaved Changes"), + tr("There are unsaved changes in \"%1\".\n\n" + "Do you want to save before closing?") + .arg(TabTextForEditor(editor, false)), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel); + switch (result) + { + case QMessageBox::Yes: + if (editor->IsDirty()) + { + if (!SaveEditor(editor)) + { + return; + } + } + break; + case QMessageBox::No: + break; + case QMessageBox::Cancel: + return; + } + } + + CloseTab(index, editor); +} + +void AssemblerWidget::CloseTab(int index, AsmEditor* editor) +{ + FreeTabNum(editor->EditorNum()); + + m_asm_tabs->removeTab(index); + editor->deleteLater(); + + DisambiguateTabTitles(nullptr); + + if (m_asm_tabs->count() == 0 && m_save && m_assemble) + { + m_save->setEnabled(false); + m_assemble->setEnabled(false); + } +} + +int AssemblerWidget::AllocateTabNum() +{ + auto min_it = std::min_element(m_free_editor_nums.begin(), m_free_editor_nums.end()); + if (min_it == m_free_editor_nums.end()) + { + return m_unnamed_editor_count++; + } + + const int min = *min_it; + m_free_editor_nums.erase(min_it); + return min; +} + +void AssemblerWidget::FreeTabNum(int num) +{ + if (num != INVALID_EDITOR_NUM) + { + m_free_editor_nums.push_back(num); + } +} + +void AssemblerWidget::UpdateTabText(AsmEditor* editor) +{ + int tab_idx = 0; + for (; tab_idx < m_asm_tabs->count(); tab_idx++) + { + if (m_asm_tabs->widget(tab_idx) == editor) + { + break; + } + } + ASSERT(tab_idx < m_asm_tabs->count()); + + m_asm_tabs->setTabText(tab_idx, TabTextForEditor(editor, true)); +} + +void AssemblerWidget::DisambiguateTabTitles(AsmEditor* new_tab) +{ + for (int i = 0; i < m_asm_tabs->count(); i++) + { + AsmEditor* check = GetEditor(i); + if (check->IsAmbiguous()) + { + // Could group all editors with matching titles in a linked list + // but tracking that nicely without dangling pointers feels messy + bool still_ambiguous = false; + for (int j = 0; j < m_asm_tabs->count(); j++) + { + AsmEditor* against = GetEditor(j); + if (j != i && check->FileName() == against->FileName()) + { + if (!against->IsAmbiguous()) + { + against->SetAmbiguous(true); + UpdateTabText(against); + } + still_ambiguous = true; + } + } + + if (!still_ambiguous) + { + check->SetAmbiguous(false); + UpdateTabText(check); + } + } + } + + if (new_tab != nullptr) + { + bool is_ambiguous = false; + for (int i = 0; i < m_asm_tabs->count(); i++) + { + AsmEditor* against = GetEditor(i); + if (new_tab != against && against->FileName() == new_tab->FileName()) + { + against->SetAmbiguous(true); + UpdateTabText(against); + is_ambiguous = true; + } + } + + if (is_ambiguous) + { + new_tab->SetAmbiguous(true); + UpdateTabText(new_tab); + } + } +} + +void AssemblerWidget::UpdateIcons() +{ + m_new->setIcon(Resources::GetThemeIcon("assembler_new")); + m_open->setIcon(Resources::GetThemeIcon("assembler_openasm")); + m_save->setIcon(Resources::GetThemeIcon("assembler_save")); + m_assemble->setIcon(Resources::GetThemeIcon("assembler_assemble")); + m_inject->setIcon(Resources::GetThemeIcon("assembler_inject")); + m_copy_output_button->setIcon(Resources::GetThemeIcon("assembler_clipboard")); +} + +void AssemblerWidget::ZoomAllEditors(int amount) +{ + if (amount != 0) + { + m_net_zoom_delta += amount; + for (int i = 0; i < m_asm_tabs->count(); i++) + { + GetEditor(i)->Zoom(amount); + } + } +} diff --git a/Source/Core/DolphinQt/Debugger/AssemblerWidget.h b/Source/Core/DolphinQt/Debugger/AssemblerWidget.h new file mode 100644 index 0000000000..d130d01701 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/AssemblerWidget.h @@ -0,0 +1,100 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "Common/Assembler/GekkoAssembler.h" +#include "Core/Core.h" + +class QTabWidget; +class AsmEditor; +class QAction; +class QComboBox; +class QLineEdit; +class QPlainTextEdit; +class QPushButton; +class QTextEdit; +class QToolBar; + +namespace Core +{ +class System; +} // namespace Core + +class AssemblerWidget : public QDockWidget +{ + Q_OBJECT +public: + explicit AssemblerWidget(QWidget* parent); + + bool ApplicationCloseRequest(); + + ~AssemblerWidget(); + +protected: + void closeEvent(QCloseEvent*); + +private: + enum class AsmKind + { + Raw, + ActionReplay, + Gecko, + GeckoExec, + GeckoTrampoline + }; + static constexpr int OUTPUT_BOX_WIDTH = 18; + void CreateWidgets(); + void ConnectWidgets(); + + void OnEditChanged(); + + void OnAssemble(std::vector* asm_out); + void OnCopyOutput(); + void OnOpen(); + void OnNew(); + void OnInject(); + void OnSave(); + void OnZoomIn(); + void OnZoomOut(); + void OnZoomReset(); + void OnBaseAddressChanged(); + void OnTabChange(int index); + QString TabTextForEditor(AsmEditor* editor, bool with_dirty); + AsmEditor* GetEditor(int idx); + void NewEditor(const QString& path = QStringLiteral()); + bool SaveEditor(AsmEditor* editor); + void OnEmulationStateChanged(Core::State state); + void OnTabClose(int index); + void CloseTab(int index, AsmEditor* editor); + int AllocateTabNum(); + void FreeTabNum(int num); + void UpdateTabText(AsmEditor* editor); + void DisambiguateTabTitles(AsmEditor* editor); + void UpdateIcons(); + void ZoomAllEditors(int amount); + + static constexpr int INVALID_EDITOR_NUM = -1; + + Core::System& m_system; + + QTabWidget* m_asm_tabs; + QPlainTextEdit* m_output_box; + QComboBox* m_output_type; + QPushButton* m_copy_output_button; + QTextEdit* m_error_box; + QLineEdit* m_address_line; + QToolBar* m_toolbar; + QAction* m_open; + QAction* m_new; + QAction* m_assemble; + QAction* m_inject; + QAction* m_save; + + std::list m_free_editor_nums; + int m_unnamed_editor_count; + int m_net_zoom_delta; + bool m_dark_scheme; +}; diff --git a/Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp b/Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp new file mode 100644 index 0000000000..113c59fa07 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp @@ -0,0 +1,369 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Debugger/AssemblyEditor.h" + +#include +#include +#include +#include + +#include + +#include "Common/Assembler/GekkoParser.h" +#include "Common/StringUtil.h" +#include "DolphinQt/Debugger/GekkoSyntaxHighlight.h" + +QSize AsmEditor::LineNumberArea::sizeHint() const +{ + return QSize(asm_editor->LineNumberAreaWidth(), 0); +} + +void AsmEditor::LineNumberArea::paintEvent(QPaintEvent* event) +{ + asm_editor->LineNumberAreaPaintEvent(event); +} + +AsmEditor::AsmEditor(const QString& path, int editor_num, bool dark_scheme, QWidget* parent) + : QPlainTextEdit(parent), m_path(path), m_base_address(QStringLiteral("0")), + m_editor_num(editor_num), m_dirty(false), m_dark_scheme(dark_scheme) +{ + if (!m_path.isEmpty()) + { + m_filename = + QString::fromStdString(std::filesystem::path(m_path.toStdString()).filename().string()); + } + + m_line_number_area = new LineNumberArea(this); + m_highlighter = new GekkoSyntaxHighlight(document(), currentCharFormat(), dark_scheme); + m_last_block = textCursor().block(); + + QFont mono_font(QFontDatabase::systemFont(QFontDatabase::FixedFont).family()); + mono_font.setPointSize(12); + setFont(mono_font); + m_line_number_area->setFont(mono_font); + + UpdateLineNumberAreaWidth(0); + HighlightCurrentLine(); + setMouseTracking(true); + + connect(this, &AsmEditor::blockCountChanged, this, &AsmEditor::UpdateLineNumberAreaWidth); + connect(this, &AsmEditor::updateRequest, this, &AsmEditor::UpdateLineNumberArea); + connect(this, &AsmEditor::cursorPositionChanged, this, &AsmEditor::HighlightCurrentLine); + connect(this, &AsmEditor::textChanged, this, [this] { + m_dirty = true; + emit DirtyChanged(); + }); +} + +int AsmEditor::LineNumberAreaWidth() +{ + int num_digits = 1; + for (int max = qMax(1, blockCount()); max >= 10; max /= 10, ++num_digits) + { + } + + return 3 + CharWidth() * qMax(2, num_digits); +} + +void AsmEditor::SetBaseAddress(const QString& ba) +{ + if (ba != m_base_address) + { + m_base_address = ba; + m_dirty = true; + emit DirtyChanged(); + } +} + +bool AsmEditor::LoadFromPath() +{ + QFile file(m_path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + return false; + } + + const std::string base_addr_line = file.readLine().toStdString(); + std::string base_address = ""; + for (size_t i = 0; i < base_addr_line.length(); i++) + { + if (std::isspace(base_addr_line[i])) + { + continue; + } + else if (base_addr_line[i] == '#') + { + base_address = base_addr_line.substr(i + 1); + break; + } + else + { + break; + } + } + + if (base_address.empty()) + { + file.seek(0); + } + else + { + StringPopBackIf(&base_address, '\n'); + if (base_address.empty()) + { + base_address = "0"; + } + m_base_address = QString::fromStdString(base_address); + } + + const bool old_block = blockSignals(true); + setPlainText(QString::fromStdString(file.readAll().toStdString())); + blockSignals(old_block); + return true; +} + +bool AsmEditor::PathsMatch(const QString& path) const +{ + if (m_path.isEmpty() || path.isEmpty()) + { + return false; + } + return std::filesystem::path(m_path.toStdString()) == std::filesystem::path(path.toStdString()); +} + +void AsmEditor::Zoom(int amount) +{ + if (amount > 0) + { + zoomIn(amount); + } + else + { + zoomOut(-amount); + } + m_line_number_area->setFont(font()); +} + +bool AsmEditor::SaveFile(const QString& save_path) +{ + QFile file(save_path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + { + return false; + } + + if (m_path != save_path) + { + m_path = save_path; + m_filename = + QString::fromStdString(std::filesystem::path(m_path.toStdString()).filename().string()); + emit PathChanged(); + } + + if (file.write(QStringLiteral("#%1\n").arg(m_base_address).toUtf8()) == -1) + { + return false; + } + + if (file.write(toPlainText().toUtf8()) == -1) + { + return false; + } + + m_dirty = false; + emit DirtyChanged(); + return true; +} + +void AsmEditor::UpdateLineNumberAreaWidth(int) +{ + setViewportMargins(LineNumberAreaWidth(), 0, 0, 0); +} + +void AsmEditor::UpdateLineNumberArea(const QRect& rect, int dy) +{ + if (dy != 0) + { + m_line_number_area->scroll(0, dy); + } + else + { + m_line_number_area->update(0, rect.y(), m_line_number_area->width(), rect.height()); + } + + if (rect.contains(viewport()->rect())) + { + UpdateLineNumberAreaWidth(0); + } +} + +int AsmEditor::CharWidth() const +{ + return fontMetrics().horizontalAdvance(QLatin1Char(' ')); +} + +void AsmEditor::resizeEvent(QResizeEvent* e) +{ + QPlainTextEdit::resizeEvent(e); + + const QRect cr = contentsRect(); + m_line_number_area->setGeometry(QRect(cr.left(), cr.top(), LineNumberAreaWidth(), cr.height())); +} + +void AsmEditor::paintEvent(QPaintEvent* event) +{ + QPlainTextEdit::paintEvent(event); + + QPainter painter(viewport()); + QTextCursor tc(document()); + + QPen p = QPen(Qt::red); + p.setStyle(Qt::PenStyle::SolidLine); + p.setWidth(1); + painter.setPen(p); + const int width = CharWidth(); + + for (QTextBlock blk = firstVisibleBlock(); blk.isVisible() && blk.isValid(); blk = blk.next()) + { + if (blk.userData() == nullptr) + { + continue; + } + + BlockInfo* info = static_cast(blk.userData()); + if (info->error_at_eol) + { + tc.setPosition(blk.position() + blk.length() - 1); + tc.clearSelection(); + const QRect qr = cursorRect(tc); + painter.drawLine(qr.x(), qr.y() + qr.height(), qr.x() + width, qr.y() + qr.height()); + } + } +} + +bool AsmEditor::event(QEvent* e) +{ + if (e->type() == QEvent::ToolTip) + { + QHelpEvent* he = static_cast(e); + QTextCursor hover_cursor = cursorForPosition(he->pos()); + QTextBlock hover_block = hover_cursor.block(); + + BlockInfo* info = static_cast(hover_block.userData()); + if (info == nullptr || !info->error) + { + QToolTip::hideText(); + return true; + } + + QRect check_rect; + if (info->error_at_eol) + { + hover_cursor.setPosition(hover_block.position() + + static_cast(info->error->col + info->error->len)); + const QRect cursor_left = cursorRect(hover_cursor); + const int area_width = CharWidth(); + check_rect = QRect(cursor_left.x() + LineNumberAreaWidth(), cursor_left.y(), + cursor_left.x() + area_width, cursor_left.height()); + } + else + { + hover_cursor.setPosition(hover_block.position() + static_cast(info->error->col)); + const QRect cursor_left = cursorRect(hover_cursor); + hover_cursor.setPosition(hover_block.position() + + static_cast(info->error->col + info->error->len)); + const QRect cursor_right = cursorRect(hover_cursor); + check_rect = QRect(cursor_left.x() + LineNumberAreaWidth(), cursor_left.y(), + cursor_right.x() - cursor_left.x(), cursor_left.height()); + } + if (check_rect.contains(he->pos())) + { + QToolTip::showText(he->globalPos(), QString::fromStdString(info->error->message)); + } + else + { + QToolTip::hideText(); + } + return true; + } + return QPlainTextEdit::event(e); +} + +void AsmEditor::keyPressEvent(QKeyEvent* event) +{ + // HACK: Change shift+enter to enter to keep lines as blocks + if (event->modifiers() & Qt::ShiftModifier && + (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) + { + event->setModifiers(event->modifiers() & ~Qt::ShiftModifier); + } + QPlainTextEdit::keyPressEvent(event); +} + +void AsmEditor::wheelEvent(QWheelEvent* event) +{ + QPlainTextEdit::wheelEvent(event); + + if (event->modifiers() & Qt::ControlModifier) + { + auto delta = static_cast(std::round((event->angleDelta().y() / 120.0))); + if (delta != 0) + { + emit ZoomRequested(delta); + } + } +} + +void AsmEditor::HighlightCurrentLine() +{ + const bool old_state = blockSignals(true); + + if (m_last_block.blockNumber() != textCursor().blockNumber()) + { + m_highlighter->SetMode(2); + m_highlighter->rehighlightBlock(m_last_block); + + m_last_block = textCursor().block(); + } + + m_highlighter->SetCursorLoc(textCursor().positionInBlock()); + m_highlighter->SetMode(1); + m_highlighter->rehighlightBlock(textCursor().block()); + m_highlighter->SetMode(0); + + blockSignals(old_state); +} + +void AsmEditor::LineNumberAreaPaintEvent(QPaintEvent* event) +{ + QPainter painter(m_line_number_area); + if (m_dark_scheme) + { + painter.fillRect(event->rect(), QColor::fromRgb(76, 76, 76)); + } + else + { + painter.fillRect(event->rect(), QColor::fromRgb(180, 180, 180)); + } + + QTextBlock block = firstVisibleBlock(); + int block_num = block.blockNumber(); + int top = qRound(blockBoundingGeometry(block).translated(contentOffset()).top()); + int bottom = top + qRound(blockBoundingRect(block).height()); + + while (block.isValid() && top <= event->rect().bottom()) + { + if (block.isVisible() && bottom >= event->rect().top()) + { + const QString num = QString::number(block_num + 1); + painter.drawText(0, top, m_line_number_area->width(), fontMetrics().height(), Qt::AlignRight, + num); + } + + block = block.next(); + top = bottom; + bottom = top + qRound(blockBoundingRect(block).height()); + ++block_num; + } +} diff --git a/Source/Core/DolphinQt/Debugger/AssemblyEditor.h b/Source/Core/DolphinQt/Debugger/AssemblyEditor.h new file mode 100644 index 0000000000..c850817b82 --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/AssemblyEditor.h @@ -0,0 +1,81 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +class QWidget; +class QPaintEvent; +class QResizeEvent; +class QRect; +class QWheelEvent; +class GekkoSyntaxHighlight; + +class AsmEditor : public QPlainTextEdit +{ + Q_OBJECT; + +public: + AsmEditor(const QString& file_path, int editor_num, bool dark_scheme, QWidget* parent = nullptr); + void LineNumberAreaPaintEvent(QPaintEvent* event); + int LineNumberAreaWidth(); + const QString& Path() const { return m_path; } + const QString& FileName() const { return m_filename; } + const QString& EditorTitle() const { return m_title_ambiguous ? Path() : FileName(); } + const QString& BaseAddress() const { return m_base_address; } + void SetBaseAddress(const QString& ba); + void SetAmbiguous(bool b) { m_title_ambiguous = b; } + int EditorNum() const { return m_editor_num; } + bool LoadFromPath(); + bool IsDirty() const { return m_dirty; } + bool IsAmbiguous() const { return m_title_ambiguous; } + bool PathsMatch(const QString& path) const; + void Zoom(int amount); + +public slots: + bool SaveFile(const QString& save_path); + +signals: + void PathChanged(); + void DirtyChanged(); + void ZoomRequested(int amount); + +protected: + void resizeEvent(QResizeEvent* event) override; + void paintEvent(QPaintEvent* event) override; + bool event(QEvent* e) override; + void keyPressEvent(QKeyEvent* event) override; + void wheelEvent(QWheelEvent* event) override; + +private: + void UpdateLineNumberAreaWidth(int new_block_count); + void HighlightCurrentLine(); + void UpdateLineNumberArea(const QRect& rect, int dy); + int CharWidth() const; + + class LineNumberArea : public QWidget + { + public: + LineNumberArea(AsmEditor* editor) : QWidget(editor), asm_editor(editor) {} + QSize sizeHint() const override; + + protected: + void paintEvent(QPaintEvent* event) override; + + private: + AsmEditor* asm_editor; + }; + + QWidget* m_line_number_area; + GekkoSyntaxHighlight* m_highlighter; + QString m_path; + QString m_filename; + QString m_base_address; + const int m_editor_num; + bool m_dirty; + QTextBlock m_last_block; + bool m_title_ambiguous; + bool m_dark_scheme; +}; diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index 5507875997..df6ccc6a5b 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -35,6 +35,7 @@ #include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" +#include "DolphinQt/Debugger/AssembleInstructionDialog.h" #include "DolphinQt/Debugger/PatchInstructionDialog.h" #include "DolphinQt/Host.h" #include "DolphinQt/QtUtils/SetWindowDecorations.h" @@ -597,6 +598,8 @@ void CodeViewWidget::OnContextMenu() auto* insert_nop_action = menu->addAction(tr("Insert &nop"), this, &CodeViewWidget::OnInsertNOP); auto* replace_action = menu->addAction(tr("Re&place instruction"), this, &CodeViewWidget::OnReplaceInstruction); + auto* assemble_action = + menu->addAction(tr("Assemble instruction"), this, &CodeViewWidget::OnAssembleInstruction); auto* restore_action = menu->addAction(tr("Restore instruction"), this, &CodeViewWidget::OnRestoreInstruction); @@ -637,8 +640,9 @@ void CodeViewWidget::OnContextMenu() run_until_menu->setEnabled(!target.isEmpty()); follow_branch_action->setEnabled(follow_branch_enabled); - for (auto* action : {copy_address_action, copy_line_action, copy_hex_action, function_action, - ppc_action, insert_blr_action, insert_nop_action, replace_action}) + for (auto* action : + {copy_address_action, copy_line_action, copy_hex_action, function_action, ppc_action, + insert_blr_action, insert_nop_action, replace_action, assemble_action}) { action->setEnabled(running); } @@ -997,8 +1001,17 @@ void CodeViewWidget::OnSetSymbolEndAddress() void CodeViewWidget::OnReplaceInstruction() { - Core::CPUThreadGuard guard(m_system); + DoPatchInstruction(false); +} +void CodeViewWidget::OnAssembleInstruction() +{ + DoPatchInstruction(true); +} + +void CodeViewWidget::DoPatchInstruction(bool assemble) +{ + Core::CPUThreadGuard guard(m_system); const u32 addr = GetContextAddress(); if (!PowerPC::MMU::HostIsInstructionRAMAddress(guard, addr)) @@ -1010,13 +1023,26 @@ void CodeViewWidget::OnReplaceInstruction() return; auto& debug_interface = m_system.GetPowerPC().GetDebugInterface(); - PatchInstructionDialog dialog(this, addr, debug_interface.ReadInstruction(guard, addr)); - SetQWidgetWindowDecorations(&dialog); - if (dialog.exec() == QDialog::Accepted) + if (assemble) { - debug_interface.SetPatch(guard, addr, dialog.GetCode()); - Update(&guard); + AssembleInstructionDialog dialog(this, addr, debug_interface.ReadInstruction(guard, addr)); + SetQWidgetWindowDecorations(&dialog); + if (dialog.exec() == QDialog::Accepted) + { + debug_interface.SetPatch(guard, addr, dialog.GetCode()); + Update(&guard); + } + } + else + { + PatchInstructionDialog dialog(this, addr, debug_interface.ReadInstruction(guard, addr)); + SetQWidgetWindowDecorations(&dialog); + if (dialog.exec() == QDialog::Accepted) + { + debug_interface.SetPatch(guard, addr, dialog.GetCode()); + Update(&guard); + } } } diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h index 142a56d7a3..5e63eb96a4 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.h @@ -95,6 +95,8 @@ private: void OnInsertBLR(); void OnInsertNOP(); void OnReplaceInstruction(); + void OnAssembleInstruction(); + void DoPatchInstruction(bool assemble); void OnRestoreInstruction(); void CalculateBranchIndentation(); diff --git a/Source/Core/DolphinQt/Debugger/GekkoSyntaxHighlight.cpp b/Source/Core/DolphinQt/Debugger/GekkoSyntaxHighlight.cpp new file mode 100644 index 0000000000..cdbbcf550d --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/GekkoSyntaxHighlight.cpp @@ -0,0 +1,261 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/Debugger/GekkoSyntaxHighlight.h" + +#include "Common/Assembler/GekkoParser.h" + +#include +#include + +namespace +{ +using namespace Common::GekkoAssembler; +using namespace Common::GekkoAssembler::detail; + +class HighlightParsePlugin : public ParsePlugin +{ +public: + virtual ~HighlightParsePlugin() = default; + + std::vector>&& MoveParens() { return std::move(m_matched_parens); } + std::vector>&& MoveFormatting() + { + return std::move(m_formatting); + } + + void OnDirectivePre(GekkoDirective) override { HighlightCurToken(HighlightFormat::Directive); } + + void OnInstructionPre(const ParseInfo&, bool) override + { + HighlightCurToken(HighlightFormat::Mnemonic); + } + + void OnTerminal(Terminal type, const AssemblerToken& val) override + { + switch (type) + { + case Terminal::Id: + HighlightCurToken(HighlightFormat::Symbol); + break; + + case Terminal::Hex: + case Terminal::Dec: + case Terminal::Oct: + case Terminal::Bin: + case Terminal::Flt: + HighlightCurToken(HighlightFormat::Immediate); + break; + + case Terminal::GPR: + HighlightCurToken(HighlightFormat::GPR); + break; + + case Terminal::FPR: + HighlightCurToken(HighlightFormat::GPR); + break; + + case Terminal::SPR: + HighlightCurToken(HighlightFormat::SPR); + break; + + case Terminal::CRField: + HighlightCurToken(HighlightFormat::CRField); + break; + + case Terminal::Lt: + case Terminal::Gt: + case Terminal::Eq: + case Terminal::So: + HighlightCurToken(HighlightFormat::CRFlag); + break; + + case Terminal::Str: + HighlightCurToken(HighlightFormat::Str); + break; + + default: + break; + } + } + + void OnHiaddr(std::string_view) override + { + HighlightCurToken(HighlightFormat::Symbol); + auto&& [ha_pos, ha_tok] = m_owner->lexer.LookaheadTagRef(2); + m_formatting.emplace_back(static_cast(ha_pos.col), + static_cast(ha_tok.token_val.length()), HighlightFormat::HaLa); + } + + void OnLoaddr(std::string_view id) override { OnHiaddr(id); } + + void OnOpenParen(ParenType type) override + { + m_paren_stack.push_back(static_cast(m_owner->lexer.ColNumber())); + } + + void OnCloseParen(ParenType type) override + { + if (m_paren_stack.empty()) + { + return; + } + + m_matched_parens.emplace_back(m_paren_stack.back(), + static_cast(m_owner->lexer.ColNumber())); + m_paren_stack.pop_back(); + } + + void OnError() override + { + m_formatting.emplace_back(static_cast(m_owner->error->col), + static_cast(m_owner->error->len), HighlightFormat::Error); + } + + void OnLabelDecl(std::string_view name) override + { + const int len = static_cast(m_owner->lexer.LookaheadRef().token_val.length()); + const int off = static_cast(m_owner->lexer.ColNumber()); + m_formatting.emplace_back(len, off, HighlightFormat::Symbol); + } + + void OnVarDecl(std::string_view name) override { OnLabelDecl(name); } + +private: + std::vector m_paren_stack; + std::vector> m_matched_parens; + std::vector> m_formatting; + + void HighlightCurToken(HighlightFormat format) + { + const int len = static_cast(m_owner->lexer.LookaheadRef().token_val.length()); + const int off = static_cast(m_owner->lexer.ColNumber()); + m_formatting.emplace_back(off, len, format); + } +}; +} // namespace + +void GekkoSyntaxHighlight::highlightBlock(const QString& text) +{ + BlockInfo* info = static_cast(currentBlockUserData()); + if (info == nullptr) + { + info = new BlockInfo; + setCurrentBlockUserData(info); + } + + qsizetype comment_idx = text.indexOf(QLatin1Char('#')); + if (comment_idx != -1) + { + HighlightSubstr(comment_idx, text.length() - comment_idx, HighlightFormat::Comment); + } + + if (m_mode == 0) + { + HighlightParsePlugin plugin; + ParseWithPlugin(&plugin, text.toStdString()); + + info->block_format = plugin.MoveFormatting(); + info->parens = plugin.MoveParens(); + info->error = std::move(plugin.Error()); + info->error_at_eol = info->error && info->error->len == 0; + } + else if (m_mode == 1) + { + auto paren_it = std::find_if(info->parens.begin(), info->parens.end(), + [this](const std::pair& p) { + return p.first == m_cursor_loc || p.second == m_cursor_loc; + }); + if (paren_it != info->parens.end()) + { + HighlightSubstr(paren_it->first, 1, HighlightFormat::Paren); + HighlightSubstr(paren_it->second, 1, HighlightFormat::Paren); + } + } + + for (auto&& [off, len, format] : info->block_format) + { + HighlightSubstr(off, len, format); + } +} + +GekkoSyntaxHighlight::GekkoSyntaxHighlight(QTextDocument* document, QTextCharFormat base_format, + bool dark_scheme) + : QSyntaxHighlighter(document), m_base_format(base_format) +{ + QPalette base_scheme; + m_theme_idx = dark_scheme ? 1 : 0; +} + +void GekkoSyntaxHighlight::HighlightSubstr(int start, int len, HighlightFormat format) +{ + QTextCharFormat hl_format = m_base_format; + const QColor DIRECTIVE_COLOR[2] = {QColor(0x9d, 0x00, 0x06), + QColor(0xfb, 0x49, 0x34)}; // Gruvbox darkred + const QColor MNEMONIC_COLOR[2] = {QColor(0x79, 0x74, 0x0e), + QColor(0xb8, 0xbb, 0x26)}; // Gruvbox darkgreen + const QColor IMM_COLOR[2] = {QColor(0xb5, 0x76, 0x14), + QColor(0xfa, 0xbd, 0x2f)}; // Gruvbox darkyellow + const QColor BUILTIN_COLOR[2] = {QColor(0x07, 0x66, 0x78), + QColor(0x83, 0xa5, 0x98)}; // Gruvbox darkblue + const QColor HA_LA_COLOR[2] = {QColor(0xaf, 0x3a, 0x03), + QColor(0xfe, 0x80, 0x19)}; // Gruvbox darkorange + const QColor HOVER_BG_COLOR[2] = {QColor(0xd5, 0xc4, 0xa1), + QColor(0x50, 0x49, 0x45)}; // Gruvbox bg2 + const QColor STRING_COLOR[2] = {QColor(0x98, 0x97, 0x1a), + QColor(0x98, 0x97, 0x1a)}; // Gruvbox green + const QColor COMMENT_COLOR[2] = {QColor(0x68, 0x9d, 0x6a), + QColor(0x68, 0x9d, 0x6a)}; // Gruvbox aqua + + switch (format) + { + case HighlightFormat::Directive: + hl_format.setForeground(DIRECTIVE_COLOR[m_theme_idx]); + break; + case HighlightFormat::Mnemonic: + hl_format.setForeground(MNEMONIC_COLOR[m_theme_idx]); + break; + case HighlightFormat::Symbol: + break; + case HighlightFormat::Immediate: + hl_format.setForeground(IMM_COLOR[m_theme_idx]); + break; + case HighlightFormat::GPR: + hl_format.setForeground(BUILTIN_COLOR[m_theme_idx]); + break; + case HighlightFormat::FPR: + hl_format.setForeground(BUILTIN_COLOR[m_theme_idx]); + break; + case HighlightFormat::SPR: + hl_format.setForeground(BUILTIN_COLOR[m_theme_idx]); + break; + case HighlightFormat::CRField: + hl_format.setForeground(BUILTIN_COLOR[m_theme_idx]); + break; + case HighlightFormat::CRFlag: + hl_format.setForeground(BUILTIN_COLOR[m_theme_idx]); + break; + case HighlightFormat::Str: + hl_format.setForeground(STRING_COLOR[m_theme_idx]); + break; + case HighlightFormat::HaLa: + hl_format.setForeground(HA_LA_COLOR[m_theme_idx]); + break; + case HighlightFormat::Paren: + hl_format.setBackground(HOVER_BG_COLOR[m_theme_idx]); + break; + case HighlightFormat::Default: + hl_format.clearForeground(); + hl_format.clearBackground(); + break; + case HighlightFormat::Comment: + hl_format.setForeground(COMMENT_COLOR[m_theme_idx]); + break; + case HighlightFormat::Error: + hl_format.setUnderlineColor(Qt::red); + hl_format.setUnderlineStyle(QTextCharFormat::WaveUnderline); + break; + } + + setFormat(start, len, hl_format); +} diff --git a/Source/Core/DolphinQt/Debugger/GekkoSyntaxHighlight.h b/Source/Core/DolphinQt/Debugger/GekkoSyntaxHighlight.h new file mode 100644 index 0000000000..777f3c45ff --- /dev/null +++ b/Source/Core/DolphinQt/Debugger/GekkoSyntaxHighlight.h @@ -0,0 +1,60 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include + +#include "Common/Assembler/AssemblerShared.h" + +enum class HighlightFormat +{ + Directive, + Mnemonic, + Symbol, + Immediate, + GPR, + FPR, + SPR, + CRField, + CRFlag, + Str, + HaLa, + Paren, + Default, + Comment, + Error, +}; + +struct BlockInfo : public QTextBlockUserData +{ + std::vector> block_format; + std::vector> parens; + std::optional error; + bool error_at_eol = false; +}; + +class GekkoSyntaxHighlight : public QSyntaxHighlighter +{ + Q_OBJECT; + +public: + explicit GekkoSyntaxHighlight(QTextDocument* document, QTextCharFormat base_format, + bool dark_scheme); + + void HighlightSubstr(int start, int len, HighlightFormat format); + void SetMode(int mode) { m_mode = mode; } + void SetCursorLoc(int loc) { m_cursor_loc = loc; } + +protected: + void highlightBlock(const QString& line) override; + +private: + int m_mode = 0; + int m_cursor_loc = 0; + QTextCharFormat m_base_format; + int m_theme_idx = 0; +}; diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index 242466af35..e7829759dd 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -123,8 +123,10 @@ GameList::GameList(QWidget* parent) : QStackedWidget(parent), m_model(this) m_prefer_list = Settings::Instance().GetPreferredView(); ConsiderViewChange(); - const auto* zoom_in = new QShortcut(QKeySequence::ZoomIn, this); - const auto* zoom_out = new QShortcut(QKeySequence::ZoomOut, this); + auto* zoom_in = new QShortcut(QKeySequence::ZoomIn, this); + auto* zoom_out = new QShortcut(QKeySequence::ZoomOut, this); + zoom_in->setContext(Qt::WidgetWithChildrenShortcut); + zoom_out->setContext(Qt::WidgetWithChildrenShortcut); connect(zoom_in, &QShortcut::activated, this, &GameList::ZoomIn); connect(zoom_out, &QShortcut::activated, this, &GameList::ZoomOut); diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 7df52cdebf..62a56bd091 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -82,6 +82,7 @@ #include "DolphinQt/Config/LogWidget.h" #include "DolphinQt/Config/Mapping/MappingWindow.h" #include "DolphinQt/Config/SettingsWindow.h" +#include "DolphinQt/Debugger/AssemblerWidget.h" #include "DolphinQt/Debugger/BreakpointWidget.h" #include "DolphinQt/Debugger/CodeViewWidget.h" #include "DolphinQt/Debugger/CodeWidget.h" @@ -449,6 +450,7 @@ void MainWindow::CreateComponents() m_breakpoint_widget = new BreakpointWidget(this); m_code_widget = new CodeWidget(this); m_cheats_manager = new CheatsManager(this); + m_assembler_widget = new AssemblerWidget(this); const auto request_watch = [this](QString name, u32 addr) { m_watch_widget->AddWatch(name, addr); @@ -740,6 +742,7 @@ void MainWindow::ConnectStack() addDockWidget(Qt::LeftDockWidgetArea, m_memory_widget); addDockWidget(Qt::LeftDockWidgetArea, m_network_widget); addDockWidget(Qt::LeftDockWidgetArea, m_jit_widget); + addDockWidget(Qt::LeftDockWidgetArea, m_assembler_widget); tabifyDockWidget(m_log_widget, m_log_config_widget); tabifyDockWidget(m_log_widget, m_code_widget); @@ -750,6 +753,7 @@ void MainWindow::ConnectStack() tabifyDockWidget(m_log_widget, m_memory_widget); tabifyDockWidget(m_log_widget, m_network_widget); tabifyDockWidget(m_log_widget, m_jit_widget); + tabifyDockWidget(m_log_widget, m_assembler_widget); } void MainWindow::RefreshGameList() @@ -872,7 +876,16 @@ void MainWindow::OnStopComplete() SetFullScreenResolution(false); if (m_exit_requested || Settings::Instance().IsBatchModeEnabled()) - QGuiApplication::exit(0); + { + if (m_assembler_widget->ApplicationCloseRequest()) + { + QGuiApplication::exit(0); + } + else + { + m_exit_requested = false; + } + } // If the current emulation prevented the booting of another, do that now if (m_pending_boot != nullptr) diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index ce6beb1578..68b3e6cd2d 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -18,6 +18,7 @@ class QStackedWidget; class QString; class AchievementsWindow; +class AssemblerWidget; class BreakpointWidget; struct BootParameters; class CheatsManager; @@ -259,6 +260,7 @@ private: AchievementsWindow* m_achievements_window = nullptr; #endif // USE_RETRO_ACHIEVEMENTS + AssemblerWidget* m_assembler_widget; BreakpointWidget* m_breakpoint_widget; CodeWidget* m_code_widget; JITWidget* m_jit_widget; diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 2b68cf640d..71f69cc3e7 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -494,6 +494,14 @@ void MenuBar::AddViewMenu() connect(m_show_jit, &QAction::toggled, &Settings::Instance(), &Settings::SetJITVisible); connect(&Settings::Instance(), &Settings::JITVisibilityChanged, m_show_jit, &QAction::setChecked); + m_show_assembler = view_menu->addAction(tr("&Assembler")); + m_show_assembler->setCheckable(true); + m_show_assembler->setChecked(Settings::Instance().IsAssemblerVisible()); + connect(m_show_assembler, &QAction::toggled, &Settings::Instance(), + &Settings::SetAssemblerVisible); + connect(&Settings::Instance(), &Settings::AssemblerVisibilityChanged, m_show_assembler, + &QAction::setChecked); + view_menu->addSeparator(); AddGameListTypeSection(view_menu); diff --git a/Source/Core/DolphinQt/MenuBar.h b/Source/Core/DolphinQt/MenuBar.h index e505160afa..0f3481e5f9 100644 --- a/Source/Core/DolphinQt/MenuBar.h +++ b/Source/Core/DolphinQt/MenuBar.h @@ -253,6 +253,7 @@ private: QAction* m_show_memory; QAction* m_show_network; QAction* m_show_jit; + QAction* m_show_assembler; QMenu* m_cols_menu; // JIT diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index 2e21835b6f..7e9a612239 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -693,6 +693,20 @@ bool Settings::IsJITVisible() const return QSettings().value(QStringLiteral("debugger/showjit")).toBool(); } +void Settings::SetAssemblerVisible(bool enabled) +{ + if (IsAssemblerVisible() == enabled) + return; + QSettings().setValue(QStringLiteral("debugger/showassembler"), enabled); + + emit AssemblerVisibilityChanged(enabled); +} + +bool Settings::IsAssemblerVisible() const +{ + return QSettings().value(QStringLiteral("debugger/showassembler")).toBool(); +} + void Settings::RefreshWidgetVisibility() { emit DebugModeToggled(IsDebugModeEnabled()); diff --git a/Source/Core/DolphinQt/Settings.h b/Source/Core/DolphinQt/Settings.h index fdc9f0a940..5a7516d378 100644 --- a/Source/Core/DolphinQt/Settings.h +++ b/Source/Core/DolphinQt/Settings.h @@ -166,6 +166,8 @@ public: bool IsNetworkVisible() const; void SetJITVisible(bool enabled); bool IsJITVisible() const; + void SetAssemblerVisible(bool enabled); + bool IsAssemblerVisible() const; QFont GetDebugFont() const; void SetDebugFont(QFont font); @@ -213,6 +215,7 @@ signals: void MemoryVisibilityChanged(bool visible); void NetworkVisibilityChanged(bool visible); void JITVisibilityChanged(bool visible); + void AssemblerVisibilityChanged(bool visible); void DebugModeToggled(bool enabled); void DebugFontChanged(QFont font); void AutoUpdateTrackChanged(const QString& mode); diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index 9b31ad5bf9..4c9a7c95ae 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -266,6 +266,7 @@ void CreateDirectories() File::CreateFullPath(File::GetUserPath(D_SHADERS_IDX)); File::CreateFullPath(File::GetUserPath(D_SHADERS_IDX) + ANAGLYPH_DIR DIR_SEP); File::CreateFullPath(File::GetUserPath(D_STATESAVES_IDX)); + File::CreateFullPath(File::GetUserPath(D_ASM_ROOT_IDX)); #ifndef ANDROID File::CreateFullPath(File::GetUserPath(D_THEMES_IDX)); File::CreateFullPath(File::GetUserPath(D_STYLES_IDX));