summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/Common/Assembler/AssemblerShared.cpp26
-rw-r--r--Source/Core/Common/Assembler/AssemblerShared.h545
-rw-r--r--Source/Core/Common/Assembler/AssemblerTables.cpp1482
-rw-r--r--Source/Core/Common/Assembler/AssemblerTables.h152
-rw-r--r--Source/Core/Common/Assembler/CaseInsensitiveDict.h126
-rw-r--r--Source/Core/Common/Assembler/GekkoAssembler.cpp189
-rw-r--r--Source/Core/Common/Assembler/GekkoAssembler.h29
-rw-r--r--Source/Core/Common/Assembler/GekkoIRGen.cpp832
-rw-r--r--Source/Core/Common/Assembler/GekkoIRGen.h50
-rw-r--r--Source/Core/Common/Assembler/GekkoLexer.cpp794
-rw-r--r--Source/Core/Common/Assembler/GekkoLexer.h188
-rw-r--r--Source/Core/Common/Assembler/GekkoParser.cpp885
-rw-r--r--Source/Core/Common/Assembler/GekkoParser.h124
-rw-r--r--Source/Core/Common/CMakeLists.txt12
-rw-r--r--Source/Core/Common/CommonPaths.h1
-rw-r--r--Source/Core/Common/FileUtil.cpp2
-rw-r--r--Source/Core/Common/FileUtil.h1
-rw-r--r--Source/Core/DolphinLib.props13
-rw-r--r--Source/Core/DolphinQt/CMakeLists.txt8
-rw-r--r--Source/Core/DolphinQt/Debugger/AssembleInstructionDialog.cpp131
-rw-r--r--Source/Core/DolphinQt/Debugger/AssembleInstructionDialog.h36
-rw-r--r--Source/Core/DolphinQt/Debugger/AssemblerWidget.cpp960
-rw-r--r--Source/Core/DolphinQt/Debugger/AssemblerWidget.h100
-rw-r--r--Source/Core/DolphinQt/Debugger/AssemblyEditor.cpp369
-rw-r--r--Source/Core/DolphinQt/Debugger/AssemblyEditor.h81
-rw-r--r--Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp42
-rw-r--r--Source/Core/DolphinQt/Debugger/CodeViewWidget.h2
-rw-r--r--Source/Core/DolphinQt/Debugger/GekkoSyntaxHighlight.cpp261
-rw-r--r--Source/Core/DolphinQt/Debugger/GekkoSyntaxHighlight.h60
-rw-r--r--Source/Core/DolphinQt/DolphinQt.vcxproj8
-rw-r--r--Source/Core/DolphinQt/GameList/GameList.cpp6
-rw-r--r--Source/Core/DolphinQt/MainWindow.cpp15
-rw-r--r--Source/Core/DolphinQt/MainWindow.h2
-rw-r--r--Source/Core/DolphinQt/MenuBar.cpp8
-rw-r--r--Source/Core/DolphinQt/MenuBar.h1
-rw-r--r--Source/Core/DolphinQt/Settings.cpp14
-rw-r--r--Source/Core/DolphinQt/Settings.h3
-rw-r--r--Source/Core/UICommon/UICommon.cpp1
-rw-r--r--Source/UnitTests/Common/AssemblerTest.cpp2225
-rw-r--r--Source/UnitTests/Common/CMakeLists.txt1
40 files changed, 9774 insertions, 11 deletions
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 <fmt/format.h>
+
+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 <cstddef>
+#include <cstdint>
+#include <string>
+#include <string_view>
+#include <variant>
+
+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 <typename Tag, typename T>
+using Tagged = std::pair<Tag, T>;
+template <typename Tag, typename T>
+constexpr const Tag& TagOf(const Tagged<Tag, T>& val)
+{
+ return std::get<0>(val);
+}
+template <typename Tag, typename T>
+constexpr Tag& TagOf(Tagged<Tag, T>& val)
+{
+ return std::get<0>(val);
+}
+template <typename Tag, typename T>
+constexpr const T& ValueOf(const Tagged<Tag, T>& val)
+{
+ return std::get<1>(val);
+}
+template <typename Tag, typename T>
+constexpr T& ValueOf(Tagged<Tag, T>& val)
+{
+ return std::get<1>(val);
+}
+
+template <typename T>
+using FailureOr = std::variant<AssemblerError, T>;
+template <typename T>
+constexpr bool IsFailure(const FailureOr<T>& var)
+{
+ return std::holds_alternative<AssemblerError>(var);
+}
+template <typename T>
+constexpr AssemblerError& GetFailure(FailureOr<T>& var)
+{
+ return std::get<AssemblerError>(var);
+}
+template <typename T>
+constexpr const AssemblerError& GetFailure(const FailureOr<T>& var)
+{
+ return std::get<AssemblerError>(var);
+}
+template <typename T>
+constexpr const T& GetT(const FailureOr<T>& var)
+{
+ return std::get<T>(var);
+}
+template <typename T>
+constexpr T& GetT(FailureOr<T>& var)
+{
+ return std::get<T>(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<u32>(((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<Interval, u32>({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<u32, '_'> 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<GekkoDirective> 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<size_t>(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<ParseInfo, '.', '_'> 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<size_t>(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<ParseInfo, '.', '_', '+', '-'> 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<u32>(std::initializer_list<OperandDesc>{__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<MnemonicDesc, NUM_MNEMONICS* VARIANT_PERMUTATIONS> 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<u32>(-static_cast<s32>(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 <u32 BO, u32 BI>
+void FillBOBI(OperandList& operands)
+{
+ operands.Insert(0, BO);
+ operands.Insert(1, BI);
+}
+
+template <size_t Idx>
+void BitswapIdx(OperandList& operands)
+{
+ operands[Idx] = SprBitswap(operands[Idx]);
+}
+
+template <u32 BO, u32 Cond, u32 ParamCount>
+void FillBOBICond(OperandList& operands)
+{
+ if (operands.count < ParamCount)
+ {
+ operands.Insert(0, 0);
+ }
+ operands[0] = (operands[0] << 2) | Cond;
+ operands.Insert(0, BO);
+}
+
+template <u32 BO>
+void FillBO(OperandList& operands)
+{
+ operands.Insert(0, BO);
+}
+
+template <u32 TO>
+void TrapSetTO(OperandList& operands)
+{
+ operands.Insert(0, TO);
+}
+
+template <u32 SPRG>
+void FillMtspr(OperandList& operands)
+{
+ operands.Insert(0, SPRG);
+}
+
+template <u32 SPRG>
+void FillMfspr(OperandList& operands)
+{
+ operands.Insert(1, SPRG);
+}
+
+template <u32 SPRG>
+void FillMtsprBatAndBitswap(OperandList& operands)
+{
+ operands[0] = SprBitswap(2 * operands[0] + SPRG);
+}
+
+template <u32 SPRG>
+void FillMfsprBatAndBitswap(OperandList& operands)
+{
+ operands[1] = SprBitswap(2 * operands[1] + SPRG);
+}
+} // namespace
+
+#define PSEUDO(base, variant_bits, cb) \
+ ExtendedMnemonicDesc { static_cast<size_t>(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<ExtendedMnemonicDesc, NUM_EXT_MNEMONICS* VARIANT_PERMUTATIONS>
+ 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<SprBitswap(1)>), // mtxer
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(1)>), // mfxer
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(8)>), // mtlr
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(8)>), // mflr
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(9)>), // mtctr
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(9)>), // mfctr
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(18)>), // mtdsisr
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(18)>), // mfdsisr
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(19)>), // mtdar
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(19)>), // mfdar
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(22)>), // mtdec
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(22)>), // mfdec
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(25)>), // mtsdr1
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(25)>), // mfsdr1
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(26)>), // mtsrr0
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(26)>), // mfsrr0
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(27)>), // mtsrr1
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(27)>), // mfsrr1
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(280)>), // mtasr
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(280)>), // mfasr
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(282)>), // mtear
+ PLAIN_PSEUDO(GekkoMnemonic::Mfspr_nobitswap, FillMfspr<SprBitswap(282)>), // mfear
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(284)>), // mttbl
+ PLAIN_PSEUDO(GekkoMnemonic::Mftb_nobitswap, FillMfspr<SprBitswap(268)>), // mftbl
+ PLAIN_PSEUDO(GekkoMnemonic::Mtspr_nobitswap, FillMtspr<SprBitswap(285)>), // mttbu
+ PLAIN_PSEUDO(GekkoMnemonic::Mftb_nobitswap, FillMfspr<SprBitswap(269)>), // 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<DfaNode> 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<DfaNode> 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 <array>
+#include <cstddef>
+#include <optional>
+#include <string_view>
+#include <vector>
+
+#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<u32, '_'> sprg_map;
+// Mapping of directive names to an enumeration
+extern const CaseInsensitiveDict<GekkoDirective> directives_map;
+// Mapping of normal Gekko mnemonics to their index and argument form
+extern const CaseInsensitiveDict<ParseInfo, '.', '_'> mnemonic_tokens;
+// Mapping of extended Gekko mnemonics to their index and argument form
+extern const CaseInsensitiveDict<ParseInfo, '.', '_', '+', '-'> extended_mnemonic_tokens;
+
+//////////////////////
+// ASSEMBLER TABLES //
+//////////////////////
+constexpr size_t MAX_OPERANDS = 5;
+
+struct OperandList
+{
+ std::array<Tagged<Interval, u32>, 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 <typename It>
+ 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<OperandDesc, MAX_OPERANDS> 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<size_t>(GekkoMnemonic::LastMnemonic) + 1;
+static constexpr size_t NUM_EXT_MNEMONICS =
+ static_cast<size_t>(ExtendedGekkoMnemonic::LastMnemonic) + 1;
+static constexpr size_t VARIANT_PERMUTATIONS = 4;
+
+// Table for mapping mnemonic+variants to their descriptors
+extern const std::array<MnemonicDesc, NUM_MNEMONICS * VARIANT_PERMUTATIONS> mnemonics;
+// Table for mapping extended mnemonic+variants to their descriptors
+extern const std::array<ExtendedMnemonicDesc, NUM_EXT_MNEMONICS * VARIANT_PERMUTATIONS>
+ 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<TransitionF, size_t>;
+struct DfaNode
+{
+ std::vector<DfaEdge> edges;
+ // If nullopt: this is a final node
+ // If string: invalid reason
+ std::optional<std::string_view> 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<DfaNode> float_dfa;
+// C-style strings
+// regex: "([^\\\n]|\\([0-7]{1,3}|x[0-9a-fA-F]+|[^x0-7\n]))*"
+extern const std::vector<DfaNode> 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 <array>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+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 <typename V, char... ExtraMatches>
+class CaseInsensitiveDict
+{
+public:
+ CaseInsensitiveDict(const std::initializer_list<std::pair<std::string_view, V>>& il)
+ {
+ for (auto&& [k, v] : il)
+ {
+ Add(k, v);
+ }
+ }
+
+ template <typename T>
+ 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<uint32_t>(-1);
+
+private:
+ struct TrieEntry
+ {
+ std::array<uint32_t, 36 + sizeof...(ExtraMatches)> _conns;
+ std::optional<V> _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 <typename T>
+ auto TryFind(const T& key) const -> std::pair<TrieEntry const*, decltype(key.cbegin())>
+ {
+ std::pair<TrieEntry const*, decltype(key.cbegin())> 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 <typename T>
+ auto TryFind(const T& key) -> std::pair<TrieEntry*, decltype(key.cbegin())>
+ {
+ auto&& [e_const, it] =
+ const_cast<CaseInsensitiveDict<V, ExtraMatches...> const*>(this)->TryFind(key);
+ return {const_cast<TrieEntry*>(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<uint32_t>(m_entry_pool.size());
+ last_e = &m_entry_pool.emplace_back();
+ }
+ }
+ last_e->_val = val;
+ }
+
+ TrieEntry m_root_entry;
+ std::vector<TrieEntry> 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 <algorithm>
+#include <array>
+#include <string>
+#include <vector>
+
+#include <fmt/format.h>
+
+#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<u32> 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<s32>(operands[i]),
+ static_cast<s32>(desc.operand_masks[i].MinVal()),
+ static_cast<s32>(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<s32>(operands[i]),
+ static_cast<s32>(desc.operand_masks[i].MinVal()),
+ static_cast<s32>(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<std::vector<CodeBlock>> Assemble(std::string_view instruction,
+ u32 current_instruction_address)
+{
+ FailureOr<detail::GekkoIR> 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<CodeBlock> 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<detail::InstChunk>(chunk))
+ {
+ for (const detail::GekkoInstruction& parsed_inst : std::get<detail::InstChunk>(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<GekkoMnemonic>(idx >> 2), adjusted_ops);
+
+ FailureOr<u32> 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<detail::ByteChunk>(chunk))
+ {
+ detail::ByteChunk byte_arr = std::get<detail::ByteChunk>(chunk);
+ new_block.instructions.insert(new_block.instructions.end(), byte_arr.begin(),
+ byte_arr.end());
+ }
+ else if (std::holds_alternative<detail::PadChunk>(chunk))
+ {
+ detail::PadChunk pad_len = std::get<detail::PadChunk>(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 <string_view>
+#include <vector>
+
+#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<u8> 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<std::vector<CodeBlock>> 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 <functional>
+#include <map>
+#include <numeric>
+#include <set>
+#include <stack>
+#include <variant>
+#include <vector>
+
+#include <fmt/format.h>
+
+#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<u64> LookupVar(std::string_view lab);
+ std::optional<u32> LookupLabel(std::string_view lab);
+
+ template <typename T>
+ T& GetChunk();
+
+ template <typename T>
+ 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<std::string, u32, std::less<>> m_labels;
+ std::map<std::string, u64, std::less<>> m_constants;
+ std::set<std::string> m_symset;
+
+ EvalMode m_evaluation_mode;
+
+ // For operand parsing
+ std::stack<std::function<u32()>> m_fixup_stack;
+ std::vector<std::function<u32()>> m_operand_fixups;
+ size_t m_operand_str_start;
+
+ // For directive parsing
+ std::vector<u64> m_eval_stack;
+ std::variant<std::vector<float>, std::vector<double>> 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<u64>{};
+
+ switch (directive)
+ {
+ case GekkoDirective::Float:
+ m_floats_list = std::vector<float>{};
+ break;
+ case GekkoDirective::Double:
+ m_floats_list = std::vector<double>{};
+ 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<u32>(m_eval_stack.back()));
+ break;
+
+ case GekkoDirective::Zeros:
+ PadSpace(static_cast<u32>(m_eval_stack.back()));
+ break;
+
+ case GekkoDirective::Skip:
+ {
+ const u32 skip_len = static_cast<u32>(m_eval_stack.back());
+ if (skip_len > 0)
+ {
+ StartBlock(CurrentAddress() + skip_len);
+ }
+ break;
+ }
+
+ case GekkoDirective::PadAlign:
+ PadAlign(static_cast<u32>(m_eval_stack.back()));
+ break;
+
+ case GekkoDirective::Align:
+ StartBlockAlign(static_cast<u32>(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<u8>(static_cast<u8>(m_eval_stack.back()));
+ break;
+ case GekkoDirective::_2byte:
+ AddBytes<u16>(static_cast<u16>(m_eval_stack.back()));
+ break;
+ case GekkoDirective::_4byte:
+ AddBytes<u32>(static_cast<u32>(m_eval_stack.back()));
+ break;
+ case GekkoDirective::_8byte:
+ AddBytes<u64>(static_cast<u64>(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<u64> 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<u32> 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<ByteChunk>();
+ ConvertStringLiteral(str, &bytes);
+ if (null_term)
+ {
+ bytes.push_back('\0');
+ }
+}
+
+template <typename T>
+T& GekkoIRPlugin::GetChunk()
+{
+ if (!m_active_block->chunks.empty() && std::holds_alternative<T>(m_active_block->chunks.back()))
+ {
+ return std::get<T>(m_active_block->chunks.back());
+ }
+
+ return std::get<T>(m_active_block->chunks.emplace_back(T{}));
+}
+
+template <typename T>
+void GekkoIRPlugin::AddBytes(T val)
+{
+ if constexpr (std::is_integral_v<T>)
+ {
+ ByteChunk& bytes = GetChunk<ByteChunk>();
+ 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<T, float>)
+ {
+ static_assert(sizeof(double) == sizeof(u64));
+ AddBytes(BitCast<u32>(val));
+ }
+ else
+ {
+ // std::is_same_v<T, double>
+ static_assert(sizeof(double) == sizeof(u64));
+ AddBytes(BitCast<u64>(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<PadChunk>();
+ current_pad += (1 << bits) - (current_addr & align_mask);
+ }
+}
+
+void GekkoIRPlugin::PadSpace(size_t space)
+{
+ GetChunk<PadChunk>() += 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<u32()> rhs = std::move(m_fixup_stack.top());
+ m_fixup_stack.pop();
+ std::function<u32()> 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<u32()> 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<u32()> 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<u32>(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<InstChunk>().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<u32>(-static_cast<s32>(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<u32>(-static_cast<s32>(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<u32> val = tok.EvalToken<u32>();
+ 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<u64> val = tok.EvalToken<u64>();
+ ASSERT(val.has_value());
+ m_eval_stack.push_back(*val);
+ break;
+ }
+
+ case Terminal::Flt:
+ {
+ std::visit(
+ [&tok](auto&& vec) {
+ auto opt = tok.EvalToken<typename std::decay_t<decltype(vec)>::value_type>();
+ ASSERT(opt.has_value());
+ vec.push_back(*opt);
+ },
+ m_floats_list);
+ break;
+ }
+
+ case Terminal::Dot:
+ m_eval_stack.push_back(static_cast<u64>(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<InstChunk>(chunk))
+ {
+ size = std::get<InstChunk>(chunk).size() * 4;
+ }
+ else if (std::holds_alternative<ByteChunk>(chunk))
+ {
+ size = std::get<ByteChunk>(chunk).size();
+ }
+ else if (std::holds_alternative<PadChunk>(chunk))
+ {
+ size = std::get<PadChunk>(chunk);
+ }
+ else
+ {
+ ASSERT(false);
+ size = 0;
+ }
+
+ return acc + static_cast<u32>(size);
+ });
+}
+
+FailureOr<GekkoIR> 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<GekkoIR>(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 <string_view>
+#include <vector>
+
+#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:
+ // (<GekkoMnemonic> << 2) | (<variant bits>)
+ 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<GekkoInstruction>;
+using ByteChunk = std::vector<u8>;
+using PadChunk = size_t;
+using ChunkVariant = std::variant<InstChunk, ByteChunk, PadChunk>;
+
+struct IRBlock
+{
+ explicit IRBlock(u32 address) : block_address(address) {}
+
+ u32 BlockEndAddress() const;
+
+ std::vector<ChunkVariant> chunks;
+ u32 block_address;
+};
+
+struct GekkoIR
+{
+ std::vector<IRBlock> blocks;
+ std::vector<Tagged<Interval, u32>> operand_pool;
+};
+
+FailureOr<GekkoIR> 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 <iterator>
+#include <numeric>
+
+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 <typename T>
+constexpr T ConvertNib(char c)
+{
+ if (c >= 'a' && c <= 'f')
+ {
+ return static_cast<T>(c - 'a' + 10);
+ }
+ if (c >= 'A' && c <= 'F')
+ {
+ return static_cast<T>(c - 'A' + 10);
+ }
+ return static_cast<T>(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 <typename Cont>
+void ConvertStringLiteral(std::string_view literal, std::back_insert_iterator<Cont> 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<u8>(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<char>(c);
+ }
+ out_it = static_cast<u8>(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<u8>(simple_escape);
+ ++i;
+ }
+ }
+ else
+ {
+ out_it = static_cast<u8>(literal[i]);
+ ++i;
+ }
+ }
+}
+
+template <typename T>
+std::optional<T> EvalIntegral(TokenType tp, std::string_view val)
+{
+ constexpr auto hex_step = [](T acc, char c) { return acc << 4 | ConvertNib<T>(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<T>(*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<u8>* 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 "<EOL>";
+ case TokenType::Eof:
+ return "<EOF>";
+ default:
+ return token_val;
+ }
+}
+
+template <>
+std::optional<float> AssemblerToken::EvalToken() const
+{
+ if (token_type == TokenType::FloatLit)
+ {
+ return std::stof(std::string(token_val));
+ }
+ return std::nullopt;
+}
+
+template <>
+std::optional<double> AssemblerToken::EvalToken() const
+{
+ if (token_type == TokenType::FloatLit)
+ {
+ return std::stod(std::string(token_val));
+ }
+ return std::nullopt;
+}
+
+template <>
+std::optional<u8> AssemblerToken::EvalToken() const
+{
+ return EvalIntegral<u8>(token_type, token_val);
+}
+
+template <>
+std::optional<u16> AssemblerToken::EvalToken() const
+{
+ return EvalIntegral<u16>(token_type, token_val);
+}
+
+template <>
+std::optional<u32> AssemblerToken::EvalToken() const
+{
+ return EvalIntegral<u32>(token_type, token_val);
+}
+
+template <>
+std::optional<u64> AssemblerToken::EvalToken() const
+{
+ return EvalIntegral<u64>(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<CursorPosition, AssemblerToken>& 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<std::string_view> 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<std::string_view> Lexer::RunDfa(const std::vector<DfaNode>& 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<std::string_view> 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 <algorithm>
+#include <array>
+#include <deque>
+#include <optional>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+#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<u8>* 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 <typename T>
+ std::optional<T> 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<CursorPosition, AssemblerToken>& 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 <size_t N>
+ void LookaheadTaggedN(std::array<Tagged<CursorPosition, AssemblerToken>, 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 <size_t N>
+ void LookaheadN(std::array<AssemblerToken, N>* 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 <size_t N>
+ 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<std::string_view> RunDfa(const std::vector<DfaNode>& 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<Tagged<CursorPosition, AssemblerToken>> 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 <algorithm>
+#include <array>
+#include <functional>
+
+#include <fmt/format.h>
+
+#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<AssemblerToken, 3> 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<ParseStep> 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<AssemblerToken, 2> 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 <optional>
+#include <string>
+#include <string_view>
+
+#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<AssemblerError> 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<AssemblerError>& 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<AssemblerError> 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 2c4ec7a6f1..a4a761276b 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 4510842089..bf30fb98a9 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 4b211fec5e..562e66ff56 100644
--- a/Source/Core/Common/FileUtil.cpp
+++ b/Source/Core/Common/FileUtil.cpp
@@ -896,6 +896,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 276dd6caa8..94491cecfd 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/DolphinLib.props b/Source/Core/DolphinLib.props
index cc564bbafd..06b1a88eee 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -16,6 +16,13 @@
<ClInclude Include="Common\Align.h" />
<ClInclude Include="Common\Analytics.h" />
<ClInclude Include="Common\Assert.h" />
+ <ClInclude Include="Common\Assembler\AssemblerShared.h" />
+ <ClInclude Include="Common\Assembler\AssemblerTables.h" />
+ <ClInclude Include="Common\Assembler\CaseInsensitiveDict.h" />
+ <ClInclude Include="Common\Assembler\GekkoAssembler.h" />
+ <ClInclude Include="Common\Assembler\GekkoIRGen.h" />
+ <ClInclude Include="Common\Assembler\GekkoLexer.h" />
+ <ClInclude Include="Common\Assembler\GekkoParser.h" />
<ClInclude Include="Common\BitField.h" />
<ClInclude Include="Common\BitSet.h" />
<ClInclude Include="Common\BitUtils.h" />
@@ -763,6 +770,12 @@
<ClCompile Include="AudioCommon\WASAPIStream.cpp" />
<ClCompile Include="AudioCommon\WaveFile.cpp" />
<ClCompile Include="Common\Analytics.cpp" />
+ <ClCompile Include="Common\Assembler\AssemblerShared.cpp" />
+ <ClCompile Include="Common\Assembler\AssemblerTables.cpp" />
+ <ClCompile Include="Common\Assembler\GekkoAssembler.cpp" />
+ <ClCompile Include="Common\Assembler\GekkoIRGen.cpp" />
+ <ClCompile Include="Common\Assembler\GekkoLexer.cpp" />
+ <ClCompile Include="Common\Assembler\GekkoParser.cpp" />
<ClCompile Include="Common\ColorUtil.cpp" />
<ClCompile Include="Common\CommonFuncs.cpp" />
<ClCompile Include="Common\CompatPatches.cpp" />
diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt
index a7eac856d0..8285c17d4a 100644
--- a/Source/Core/DolphinQt/CMakeLists.txt
+++ b/Source/Core/DolphinQt/CMakeLists.txt
@@ -200,6 +200,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
@@ -210,6 +216,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..01d3c91d5d
--- /dev/null
+++ b/Source/Core/DolphinQt/Debugger/AssembleInstructionDialog.cpp
@@ -0,0 +1,131 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "DolphinQt/Debugger/AssembleInstructionDialog.h"
+
+#include <QDialogButtonBox>
+#include <QFontDatabase>
+#include <QLabel>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QVBoxLayout>
+
+#include "Common/Assembler/GekkoAssembler.h"
+#include "Common/StringUtil.h"
+
+namespace
+{
+QString HtmlFormatErrorLoc(const Common::GekkoAssembler::AssemblerError& err)
+{
+ const QString error = QStringLiteral("<span style=\"color: red; font-weight: bold\">%1</span>")
+ .arg(QObject::tr("Error"));
+ // i18n: '%1' is the translation of 'Error'
+ return QObject::tr("%1 in column %2").arg(error).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 QStringLiteral("%1<u><span style=\"color:red; font-weight:bold\">%2</span></u>%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(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<std::vector<CodeBlock>> 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(
+ QStringLiteral("<span style=\"color: red; font-weight: bold\">%1</span>").arg(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<u8>& 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(
+ QStringLiteral("<span style=\"color: green; font-weight: bold\">%1</span>").arg(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 <QDialog>
+
+#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..a769df82e6
--- /dev/null
+++ b/Source/Core/DolphinQt/Debugger/AssemblerWidget.cpp
@@ -0,0 +1,960 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "DolphinQt/Debugger/AssemblerWidget.h"
+
+#include <QAction>
+#include <QApplication>
+#include <QClipboard>
+#include <QComboBox>
+#include <QFont>
+#include <QFontDatabase>
+#include <QGridLayout>
+#include <QGroupBox>
+#include <QLabel>
+#include <QLineEdit>
+#include <QMenu>
+#include <QPlainTextEdit>
+#include <QPushButton>
+#include <QScrollBar>
+#include <QShortcut>
+#include <QStyle>
+#include <QTabWidget>
+#include <QTextBlock>
+#include <QTextEdit>
+#include <QToolBar>
+#include <QToolButton>
+
+#include <filesystem>
+#include <fmt/format.h>
+
+#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)
+{
+ const QString error = QStringLiteral("<span style=\"color: red; font-weight: bold\">%1</span>")
+ .arg(QObject::tr("Error"));
+ // i18n: '%1' is the translation of 'Error'
+ return QObject::tr("%1 on line %1 column %2").arg(error).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 QStringLiteral("<span style=\"font-family:'monospace';font-size:16px\">"
+ "<pre>%1<u><span style=\"color:red;font-weight:bold\">%2</span></u>%3</pre>"
+ "</span>")
+ .arg(line_pre_error)
+ .arg(line_error)
+ .arg(line_post_error);
+}
+
+QString HtmlFormatMessage(const AssemblerError& err)
+{
+ return QStringLiteral("<span>%1</span>").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<CodeBlock>& 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<CodeBlock>& 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<CodeBlock>& blocks, std::ostringstream& out_str)
+{
+ DeserializeToAr(blocks, out_str);
+}
+
+void DeserializeToGeckoExec(const std::vector<CodeBlock>& blocks, std::ostringstream& out_str)
+{
+ for (const auto& blk : blocks)
+ {
+ if (blk.instructions.empty())
+ {
+ continue;
+ }
+
+ u32 nlines = 1 + static_cast<u32>((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<CodeBlock>& 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<u32>((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<CodeBlock> 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<CodeBlock>* 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;
+ const QString warning =
+ QStringLiteral("<span style=\"color:#ffcc00\">%1</span>").arg(tr("Warning"));
+ // i18n: '%1' is the translation of 'Warning'
+ m_error_box->append(tr("%1 invalid base address, defaulting to 0").arg(warning));
+ }
+
+ 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<int> 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<CodeBlock> 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<AsmEditor*>(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<AsmEditor*>(sender());
+ DisambiguateTabTitles(updated_tab);
+ UpdateTabText(updated_tab);
+ });
+ new_editor->connect(new_editor, &AsmEditor::DirtyChanged, this,
+ [this] { UpdateTabText(qobject_cast<AsmEditor*>(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 <QDockWidget>
+
+#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<Common::GekkoAssembler::CodeBlock>* 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<int> 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 <QFile>
+#include <QPainter>
+#include <QTextBlock>
+#include <QToolTip>
+
+#include <filesystem>
+
+#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<BlockInfo*>(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<QHelpEvent*>(e);
+ QTextCursor hover_cursor = cursorForPosition(he->pos());
+ QTextBlock hover_block = hover_cursor.block();
+
+ BlockInfo* info = static_cast<BlockInfo*>(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<int>(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<int>(info->error->col));
+ const QRect cursor_left = cursorRect(hover_cursor);
+ hover_cursor.setPosition(hover_block.position() +
+ static_cast<int>(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<int>(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 <QPlainTextEdit>
+#include <QTextBlock>
+
+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 <QLabel>
+#include <QPalette>
+
+namespace
+{
+using namespace Common::GekkoAssembler;
+using namespace Common::GekkoAssembler::detail;
+
+class HighlightParsePlugin : public ParsePlugin
+{
+public:
+ virtual ~HighlightParsePlugin() = default;
+
+ std::vector<std::pair<int, int>>&& MoveParens() { return std::move(m_matched_parens); }
+ std::vector<std::tuple<int, int, HighlightFormat>>&& 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<int>(ha_pos.col),
+ static_cast<int>(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<int>(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<int>(m_owner->lexer.ColNumber()));
+ m_paren_stack.pop_back();
+ }
+
+ void OnError() override
+ {
+ m_formatting.emplace_back(static_cast<int>(m_owner->error->col),
+ static_cast<int>(m_owner->error->len), HighlightFormat::Error);
+ }
+
+ void OnLabelDecl(std::string_view name) override
+ {
+ const int len = static_cast<int>(m_owner->lexer.LookaheadRef().token_val.length());
+ const int off = static_cast<int>(m_owner->lexer.ColNumber());
+ m_formatting.emplace_back(len, off, HighlightFormat::Symbol);
+ }
+
+ void OnVarDecl(std::string_view name) override { OnLabelDecl(name); }
+
+private:
+ std::vector<int> m_paren_stack;
+ std::vector<std::pair<int, int>> m_matched_parens;
+ std::vector<std::tuple<int, int, HighlightFormat>> m_formatting;
+
+ void HighlightCurToken(HighlightFormat format)
+ {
+ const int len = static_cast<int>(m_owner->lexer.LookaheadRef().token_val.length());
+ const int off = static_cast<int>(m_owner->lexer.ColNumber());
+ m_formatting.emplace_back(off, len, format);
+ }
+};
+} // namespace
+
+void GekkoSyntaxHighlight::highlightBlock(const QString& text)
+{
+ BlockInfo* info = static_cast<BlockInfo*>(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<int, int>& 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 <QSyntaxHighlighter>
+#include <QTextCharFormat>
+
+#include <optional>
+
+#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<std::tuple<int, int, HighlightFormat>> block_format;
+ std::vector<std::pair<int, int>> parens;
+ std::optional<Common::GekkoAssembler::AssemblerError> 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/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj
index 9ad07befec..063a048880 100644
--- a/Source/Core/DolphinQt/DolphinQt.vcxproj
+++ b/Source/Core/DolphinQt/DolphinQt.vcxproj
@@ -134,11 +134,15 @@
<ClCompile Include="Config\VerifyWidget.cpp" />
<ClCompile Include="Config\WiimoteControllersWidget.cpp" />
<ClCompile Include="ConvertDialog.cpp" />
+ <ClCompile Include="Debugger\AssembleInstructionDialog.cpp" />
+ <ClCompile Include="Debugger\AssemblerWidget.cpp" />
+ <ClCompile Include="Debugger\AssemblyEditor.cpp" />
<ClCompile Include="Debugger\BreakpointDialog.cpp" />
<ClCompile Include="Debugger\BreakpointWidget.cpp" />
<ClCompile Include="Debugger\CodeDiffDialog.cpp" />
<ClCompile Include="Debugger\CodeViewWidget.cpp" />
<ClCompile Include="Debugger\CodeWidget.cpp" />
+ <ClCompile Include="Debugger\GekkoSyntaxHighlight.cpp" />
<ClCompile Include="Debugger\JITWidget.cpp" />
<ClCompile Include="Debugger\MemoryViewWidget.cpp" />
<ClCompile Include="Debugger\MemoryWidget.cpp" />
@@ -342,11 +346,15 @@
<QtMoc Include="Config\VerifyWidget.h" />
<QtMoc Include="Config\WiimoteControllersWidget.h" />
<QtMoc Include="ConvertDialog.h" />
+ <QtMoc Include="Debugger\AssembleInstructionDialog.h" />
+ <QtMoc Include="Debugger\AssemblerWidget.h" />
+ <QtMoc Include="Debugger\AssemblyEditor.h" />
<QtMoc Include="Debugger\BreakpointDialog.h" />
<QtMoc Include="Debugger\BreakpointWidget.h" />
<QtMoc Include="Debugger\CodeDiffDialog.h" />
<QtMoc Include="Debugger\CodeViewWidget.h" />
<QtMoc Include="Debugger\CodeWidget.h" />
+ <QtMoc Include="Debugger\GekkoSyntaxHighlight.h" />
<QtMoc Include="Debugger\JITWidget.h" />
<QtMoc Include="Debugger\MemoryViewWidget.h" />
<QtMoc Include="Debugger\MemoryWidget.h" />
diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp
index 1b621222b2..34ffe08259 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 9189e82b11..ef1bbe7642 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"
@@ -456,6 +457,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);
@@ -747,6 +749,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);
@@ -757,6 +760,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()
@@ -879,7 +883,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 1f53d5e1ff..ede2380125 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;
@@ -260,6 +261,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 802a135bb8..1fa1a7d562 100644
--- a/Source/Core/DolphinQt/MenuBar.cpp
+++ b/Source/Core/DolphinQt/MenuBar.cpp
@@ -510,6 +510,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 322d33541c..6ddf7150cf 100644
--- a/Source/Core/DolphinQt/MenuBar.h
+++ b/Source/Core/DolphinQt/MenuBar.h
@@ -255,6 +255,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 e3bb288c7b..dae5e64b38 100644
--- a/Source/Core/DolphinQt/Settings.cpp
+++ b/Source/Core/DolphinQt/Settings.cpp
@@ -698,6 +698,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));
diff --git a/Source/UnitTests/Common/AssemblerTest.cpp b/Source/UnitTests/Common/AssemblerTest.cpp
new file mode 100644
index 0000000000..965c645cd5
--- /dev/null
+++ b/Source/UnitTests/Common/AssemblerTest.cpp
@@ -0,0 +1,2225 @@
+// Copyright 2023 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <gtest/gtest.h>
+
+#include <string_view>
+#include <vector>
+
+#include "Common/Assembler/GekkoAssembler.h"
+
+using namespace Common::GekkoAssembler;
+
+constexpr char instructions[] = "add r3, r4, r5\n"
+ "add. r3, r4, r5\n"
+ "addo r3, r4, r5\n"
+ "addo. r3, r4, r5\n"
+ "addc r3, r4, r5\n"
+ "addc. r3, r4, r5\n"
+ "addco r3, r4, r5\n"
+ "addco. r3, r4, r5\n"
+ "adde r3, r4, r5\n"
+ "adde. r3, r4, r5\n"
+ "addeo r3, r4, r5\n"
+ "addeo. r3, r4, r5\n"
+ "addme r3, r4\n"
+ "addme. r3, r4\n"
+ "addmeo r3, r4\n"
+ "addmeo. r3, r4\n"
+ "addze r3, r4\n"
+ "addze. r3, r4\n"
+ "addzeo r3, r4\n"
+ "addzeo. r3, r4\n"
+ "divw r3, r4, r5\n"
+ "divw. r3, r4, r5\n"
+ "divwo r3, r4, r5\n"
+ "divwo. r3, r4, r5\n"
+ "divwu r3, r4, r5\n"
+ "divwu. r3, r4, r5\n"
+ "divwuo r3, r4, r5\n"
+ "divwuo. r3, r4, r5\n"
+ "mullw r3, r4, r5\n"
+ "mullw. r3, r4, r5\n"
+ "mullwo r3, r4, r5\n"
+ "mullwo. r3, r4, r5\n"
+ "neg r3, r4\n"
+ "neg. r3, r4\n"
+ "nego r3, r4\n"
+ "nego. r3, r4\n"
+ "subf r3, r4, r5\n"
+ "subf. r3, r4, r5\n"
+ "subfo r3, r4, r5\n"
+ "subfo. r3, r4, r5\n"
+ "subfc r3, r4, r5\n"
+ "subfc. r3, r4, r5\n"
+ "subfco r3, r4, r5\n"
+ "subfco. r3, r4, r5\n"
+ "subfe r3, r4, r5\n"
+ "subfe. r3, r4, r5\n"
+ "subfeo r3, r4, r5\n"
+ "subfeo. r3, r4, r5\n"
+ "subfme r3, r4\n"
+ "subfme. r3, r4\n"
+ "subfmeo r3, r4\n"
+ "subfmeo. r3, r4\n"
+ "subfze r3, r4\n"
+ "subfze. r3, r4\n"
+ "subfzeo r3, r4\n"
+ "subfzeo. r3, r4\n"
+ "addi r3, r4, 1000\n"
+ "addic r3, r4, 1000\n"
+ "addic. r3, r4, 1000\n"
+ "addis r3, r4, 1000\n"
+ "mulli r3, r4, 1000\n"
+ "subfic r3, r4, 1000\n"
+ "cmpi cr1, 0, r4, 1000\n"
+ "cmpli cr1, 0, r4, 1000\n"
+ "andi. r4, r6, 1000\n"
+ "andis. r4, r6, 1000\n"
+ "ori r4, r6, 1000\n"
+ "oris r4, r6, 1000\n"
+ "xori r4, r6, 1000\n"
+ "xoris r4, r6, 1000\n"
+ "lbz r3, 100(r4)\n"
+ "lbzu r3, 100( r4)\n"
+ "lha r3, 100( r4)\n"
+ "lhau r3, 100( r4)\n"
+ "lhz r3, 100( r4)\n"
+ "lhzu r3, 100( r4)\n"
+ "lwz r3, 100( r4)\n"
+ "lwzu r3, 100( r4)\n"
+ "stb r6, 100( r4)\n"
+ "stbu r6, 100( r4)\n"
+ "sth r6, 100( r4)\n"
+ "sthu r6, 100( r4)\n"
+ "stw r6, 100( r4)\n"
+ "stwu r6, 100( r4)\n"
+ "lmw r6, 100( r4)\n"
+ "stmw r6, 100( r4)\n"
+ "lfd r3, 100( r4)\n"
+ "lfdu r3, 100( r4)\n"
+ "lfs r3, 100( r4)\n"
+ "lfsu r3, 100( r4)\n"
+ "stfd r6, 100( r4)\n"
+ "stfdu r6, 100( r4)\n"
+ "stfs r6, 100( r4)\n"
+ "stfsu r6, 100( r4)\n"
+ "twi 8, r4, 1000\n"
+ "psq_l r3, 200( r4), 0, 2\n"
+ "psq_lu r3, 200( r4), 0, 2\n"
+ "psq_st r6, 200( r4), 0, 2\n"
+ "psq_stu r6, 200( r4), 0, 2\n"
+ "mulhw r3, r4, r5\n"
+ "mulhw. r3, r4, r5\n"
+ "mulhwu r3, r4, r5\n"
+ "mulhwu. r3, r4, r5\n"
+ "and r4, r6, r5\n"
+ "and. r4, r6, r5\n"
+ "andc r4, r6, r5\n"
+ "andc. r4, r6, r5\n"
+ "cntlzw r4, r6\n"
+ "cntlzw. r4, r6\n"
+ "eqv r4, r6, r5\n"
+ "eqv. r4, r6, r5\n"
+ "extsb r4, r6\n"
+ "extsb. r4, r6\n"
+ "extsh r4, r6\n"
+ "extsh. r4, r6\n"
+ "nand r4, r6, r5\n"
+ "nand. r4, r6, r5\n"
+ "nor r4, r6, r5\n"
+ "nor. r4, r6, r5\n"
+ "or r4, r6, r5\n"
+ "or. r4, r6, r5\n"
+ "orc r4, r6, r5\n"
+ "orc. r4, r6, r5\n"
+ "xor r4, r6, r5\n"
+ "xor. r4, r6, r5\n"
+ "rlwimi r4, r6, 0, 10, 15\n"
+ "rlwimi. r4, r6, 0, 10, 15\n"
+ "rlwinm r4, r6, 0, 10, 15\n"
+ "rlwinm. r4, r6, 0, 10, 15\n"
+ "rlwnm r4, r6, r5, 10, 15\n"
+ "rlwnm. r4, r6, r5, 10, 15\n"
+ "slw r4, r6, r5\n"
+ "slw. r4, r6, r5\n"
+ "sraw r4, r6, r5\n"
+ "sraw. r4, r6, r5\n"
+ "srawi r4, r6, 0\n"
+ "srawi. r4, r6, 0\n"
+ "srw r4, r6, r5\n"
+ "srw. r4, r6, r5\n"
+ "fadd r3, r4, r5\n"
+ "fadd. r3, r4, r5\n"
+ "fadds r3, r4, r5\n"
+ "fadds. r3, r4, r5\n"
+ "fdiv r3, r4, r5\n"
+ "fdiv. r3, r4, r5\n"
+ "fdivs r3, r4, r5\n"
+ "fdivs. r3, r4, r5\n"
+ "fmul r3, r4, r7\n"
+ "fmul. r3, r4, r7\n"
+ "fmuls r3, r4, r7\n"
+ "fmuls. r3, r4, r7\n"
+ "fres r3, r5\n"
+ "fres. r3, r5\n"
+ "frsqrte r3, r5\n"
+ "frsqrte. r3, r5\n"
+ "fsub r3, r4, r5\n"
+ "fsub. r3, r4, r5\n"
+ "fsubs r3, r4, r5\n"
+ "fsubs. r3, r4, r5\n"
+ "fsel r3, r4, r7, r5\n"
+ "fsel. r3, r4, r7, r5\n"
+ "fmadd r3, r4, r7, r5\n"
+ "fmadd. r3, r4, r7, r5\n"
+ "fmadds r3, r4, r7, r5\n"
+ "fmadds. r3, r4, r7, r5\n"
+ "fmsub r3, r4, r7, r5\n"
+ "fmsub. r3, r4, r7, r5\n"
+ "fmsubs r3, r4, r7, r5\n"
+ "fmsubs. r3, r4, r7, r5\n"
+ "fnmadd r3, r4, r7, r5\n"
+ "fnmadd. r3, r4, r7, r5\n"
+ "fnmadds r3, r4, r7, r5\n"
+ "fnmadds. r3, r4, r7, r5\n"
+ "fnmsub r3, r4, r7, r5\n"
+ "fnmsub. r3, r4, r7, r5\n"
+ "fnmsubs r3, r4, r7, r5\n"
+ "fnmsubs. r3, r4, r7, r5\n"
+ "fctiw r3, r5\n"
+ "fctiw. r3, r5\n"
+ "fctiwz r3, r5\n"
+ "fctiwz. r3, r5\n"
+ "frsp r3, r5\n"
+ "frsp. r3, r5\n"
+ "mffs r3\n"
+ "mffs. r3\n"
+ "mtfsb0 21\n"
+ "mtfsb0. 21\n"
+ "mtfsb1 21\n"
+ "mtfsb1. 21\n"
+ "mtfsf 255, r5\n"
+ "mtfsf. 255, r5\n"
+ "mtfsfi cr1, 5\n"
+ "mtfsfi. cr1, 5\n"
+ "fabs r3, r5\n"
+ "fabs. r3, r5\n"
+ "fmr r3, r5\n"
+ "fmr. r3, r5\n"
+ "fnabs r3, r5\n"
+ "fnabs. r3, r5\n"
+ "fneg r3, r5\n"
+ "fneg. r3, r5\n"
+ "ps_div r3, r4, r5\n"
+ "ps_div. r3, r4, r5\n"
+ "ps_sub r3, r4, r5\n"
+ "ps_sub. r3, r4, r5\n"
+ "ps_add r3, r4, r5\n"
+ "ps_add. r3, r4, r5\n"
+ "ps_sel r3, r4, r7, r5\n"
+ "ps_sel. r3, r4, r7, r5\n"
+ "ps_res r3, r5\n"
+ "ps_res. r3, r5\n"
+ "ps_mul r3, r4, r7\n"
+ "ps_mul. r3, r4, r7\n"
+ "ps_rsqrte r3, r5\n"
+ "ps_rsqrte. r3, r5\n"
+ "ps_msub r3, r4, r7, r5\n"
+ "ps_msub. r3, r4, r7, r5\n"
+ "ps_madd r3, r4, r7, r5\n"
+ "ps_madd. r3, r4, r7, r5\n"
+ "ps_nmsub r3, r4, r7, r5\n"
+ "ps_nmsub. r3, r4, r7, r5\n"
+ "ps_nmadd r3, r4, r7, r5\n"
+ "ps_nmadd. r3, r4, r7, r5\n"
+ "ps_neg r3, r5\n"
+ "ps_neg. r3, r5\n"
+ "ps_mr r3, r5\n"
+ "ps_mr. r3, r5\n"
+ "ps_nabs r3, r5\n"
+ "ps_nabs. r3, r5\n"
+ "ps_abs r3, r5\n"
+ "ps_abs. r3, r5\n"
+ "ps_sum0 r3, r4, r7, r5\n"
+ "ps_sum0. r3, r4, r7, r5\n"
+ "ps_sum1 r3, r4, r7, r5\n"
+ "ps_sum1. r3, r4, r7, r5\n"
+ "ps_muls0 r3, r4, r7\n"
+ "ps_muls0. r3, r4, r7\n"
+ "ps_muls1 r3, r4, r7\n"
+ "ps_muls1. r3, r4, r7\n"
+ "ps_madds0 r3, r4, r7, r5\n"
+ "ps_madds0. r3, r4, r7, r5\n"
+ "ps_madds1 r3, r4, r7, r5\n"
+ "ps_madds1. r3, r4, r7, r5\n"
+ "ps_merge00 r3, r4, r5\n"
+ "ps_merge00. r3, r4, r5\n"
+ "ps_merge01 r3, r4, r5\n"
+ "ps_merge01. r3, r4, r5\n"
+ "ps_merge10 r3, r4, r5\n"
+ "ps_merge10. r3, r4, r5\n"
+ "ps_merge11 r3, r4, r5\n"
+ "ps_merge11. r3, r4, r5\n"
+ "cmp cr1, 0, r4, r5\n"
+ "cmpl cr1, 0, r4, r5\n"
+ "fcmpo cr1, r4, r5\n"
+ "fcmpu cr1, r4, r5\n"
+ "mcrfs cr1, 7\n"
+ "lbzux r3, r4, r5\n"
+ "lbzx r3, r4, r5\n"
+ "lhaux r3, r4, r5\n"
+ "lhax r3, r4, r5\n"
+ "lhzux r3, r4, r5\n"
+ "lhzx r3, r4, r5\n"
+ "lwzux r3, r4, r5\n"
+ "lwzx r3, r4, r5\n"
+ "stbux r6, r4, r5\n"
+ "stbx r6, r4, r5\n"
+ "sthux r6, r4, r5\n"
+ "sthx r6, r4, r5\n"
+ "stwux r6, r4, r5\n"
+ "stwx r6, r4, r5\n"
+ "lhbrx r3, r4, r5\n"
+ "lwbrx r3, r4, r5\n"
+ "sthbrx r6, r4, r5\n"
+ "stwbrx r6, r4, r5\n"
+ "lswi r5, r4, 1\n"
+ "lswx r3, r4, r5\n"
+ "stswi r6, r4, 1\n"
+ "stswx r6, r4, r5\n"
+ "lwarx r3, r4, r5\n"
+ "stwcx. r6, r4, r5\n"
+ "lfdux r3, r4, r5\n"
+ "lfdx r3, r4, r5\n"
+ "lfsux r3, r4, r5\n"
+ "lfsx r3, r4, r5\n"
+ "stfdux r6, r4, r5\n"
+ "stfdx r6, r4, r5\n"
+ "stfiwx r6, r4, r5\n"
+ "stfsux r6, r4, r5\n"
+ "stfsx r6, r4, r5\n"
+ "crand 21, 22, 23\n"
+ "crandc 21, 22, 23\n"
+ "creqv 21, 22, 23\n"
+ "crnand 21, 22, 23\n"
+ "crnor 21, 22, 23\n"
+ "cror 21, 22, 23\n"
+ "crorc 21, 22, 23\n"
+ "crxor 21, 22, 23\n"
+ "mcrf cr1, 7\n"
+ "tw 8, r4, r5\n"
+ "mcrxr cr1\n"
+ "mfcr r3\n"
+ "mfmsr r3\n"
+ "mfspr r3, LR\n"
+ "mftb r3, 268\n"
+ "mtcrf 255, r6\n"
+ "mtmsr r6\n"
+ "mtspr LR, r3\n"
+ "dcbf r4, r5\n"
+ "dcbi r4, r5\n"
+ "dcbst r4, r5\n"
+ "dcbt r4, r5\n"
+ "dcbtst r4, r5\n"
+ "dcbz r4, r5\n"
+ "icbi r4, r5\n"
+ "mfsr r3, 0\n"
+ "mfsrin r3, r5\n"
+ "mtsr 0, r6\n"
+ "mtsrin r6, r5\n"
+ "tlbie r5\n"
+ "eciwx r3, r4, r5\n"
+ "ecowx r6, r4, r5\n"
+ "psq_lx r3, r4, r5, 0, 2\n"
+ "psq_stx r6, r4, r5, 0, 2\n"
+ "psq_lux r3, r4, r5, 0, 2\n"
+ "psq_stux r6, r4, r5, 0, 2\n"
+ "ps_cmpu0 cr1, r4, r5\n"
+ "ps_cmpo0 cr1, r4, r5\n"
+ "ps_cmpu1 cr1, r4, r5\n"
+ "ps_cmpo1 cr1, r4, r5\n"
+ "dcbz_l r4, r5\n"
+ "b 0x1000\n"
+ "ba 0x1000\n"
+ "bl 0x1000\n"
+ "bla 0x1000\n"
+ "bc 12, 2, -0xc\n"
+ "bca 12, 2, -0xc\n"
+ "bcl 12, 2, -0xc\n"
+ "bcla 12, 2, -0xc\n"
+ "bcctr 12, 2\n"
+ "bcctrl 12, 2\n"
+ "bclr 12, 2\n"
+ "bclrl 12, 2\n";
+
+constexpr u8 expected_instructions[] = {
+ 0x7c, 0x64, 0x2a, 0x14, 0x7c, 0x64, 0x2a, 0x15, 0x7c, 0x64, 0x2e, 0x14, 0x7c, 0x64, 0x2e, 0x15,
+ 0x7c, 0x64, 0x28, 0x14, 0x7c, 0x64, 0x28, 0x15, 0x7c, 0x64, 0x2c, 0x14, 0x7c, 0x64, 0x2c, 0x15,
+ 0x7c, 0x64, 0x29, 0x14, 0x7c, 0x64, 0x29, 0x15, 0x7c, 0x64, 0x2d, 0x14, 0x7c, 0x64, 0x2d, 0x15,
+ 0x7c, 0x64, 0x01, 0xd4, 0x7c, 0x64, 0x01, 0xd5, 0x7c, 0x64, 0x05, 0xd4, 0x7c, 0x64, 0x05, 0xd5,
+ 0x7c, 0x64, 0x01, 0x94, 0x7c, 0x64, 0x01, 0x95, 0x7c, 0x64, 0x05, 0x94, 0x7c, 0x64, 0x05, 0x95,
+ 0x7c, 0x64, 0x2b, 0xd6, 0x7c, 0x64, 0x2b, 0xd7, 0x7c, 0x64, 0x2f, 0xd6, 0x7c, 0x64, 0x2f, 0xd7,
+ 0x7c, 0x64, 0x2b, 0x96, 0x7c, 0x64, 0x2b, 0x97, 0x7c, 0x64, 0x2f, 0x96, 0x7c, 0x64, 0x2f, 0x97,
+ 0x7c, 0x64, 0x29, 0xd6, 0x7c, 0x64, 0x29, 0xd7, 0x7c, 0x64, 0x2d, 0xd6, 0x7c, 0x64, 0x2d, 0xd7,
+ 0x7c, 0x64, 0x00, 0xd0, 0x7c, 0x64, 0x00, 0xd1, 0x7c, 0x64, 0x04, 0xd0, 0x7c, 0x64, 0x04, 0xd1,
+ 0x7c, 0x64, 0x28, 0x50, 0x7c, 0x64, 0x28, 0x51, 0x7c, 0x64, 0x2c, 0x50, 0x7c, 0x64, 0x2c, 0x51,
+ 0x7c, 0x64, 0x28, 0x10, 0x7c, 0x64, 0x28, 0x11, 0x7c, 0x64, 0x2c, 0x10, 0x7c, 0x64, 0x2c, 0x11,
+ 0x7c, 0x64, 0x29, 0x10, 0x7c, 0x64, 0x29, 0x11, 0x7c, 0x64, 0x2d, 0x10, 0x7c, 0x64, 0x2d, 0x11,
+ 0x7c, 0x64, 0x01, 0xd0, 0x7c, 0x64, 0x01, 0xd1, 0x7c, 0x64, 0x05, 0xd0, 0x7c, 0x64, 0x05, 0xd1,
+ 0x7c, 0x64, 0x01, 0x90, 0x7c, 0x64, 0x01, 0x91, 0x7c, 0x64, 0x05, 0x90, 0x7c, 0x64, 0x05, 0x91,
+ 0x38, 0x64, 0x03, 0xe8, 0x30, 0x64, 0x03, 0xe8, 0x34, 0x64, 0x03, 0xe8, 0x3c, 0x64, 0x03, 0xe8,
+ 0x1c, 0x64, 0x03, 0xe8, 0x20, 0x64, 0x03, 0xe8, 0x2c, 0x84, 0x03, 0xe8, 0x28, 0x84, 0x03, 0xe8,
+ 0x70, 0xc4, 0x03, 0xe8, 0x74, 0xc4, 0x03, 0xe8, 0x60, 0xc4, 0x03, 0xe8, 0x64, 0xc4, 0x03, 0xe8,
+ 0x68, 0xc4, 0x03, 0xe8, 0x6c, 0xc4, 0x03, 0xe8, 0x88, 0x64, 0x00, 0x64, 0x8c, 0x64, 0x00, 0x64,
+ 0xa8, 0x64, 0x00, 0x64, 0xac, 0x64, 0x00, 0x64, 0xa0, 0x64, 0x00, 0x64, 0xa4, 0x64, 0x00, 0x64,
+ 0x80, 0x64, 0x00, 0x64, 0x84, 0x64, 0x00, 0x64, 0x98, 0xc4, 0x00, 0x64, 0x9c, 0xc4, 0x00, 0x64,
+ 0xb0, 0xc4, 0x00, 0x64, 0xb4, 0xc4, 0x00, 0x64, 0x90, 0xc4, 0x00, 0x64, 0x94, 0xc4, 0x00, 0x64,
+ 0xb8, 0xc4, 0x00, 0x64, 0xbc, 0xc4, 0x00, 0x64, 0xc8, 0x64, 0x00, 0x64, 0xcc, 0x64, 0x00, 0x64,
+ 0xc0, 0x64, 0x00, 0x64, 0xc4, 0x64, 0x00, 0x64, 0xd8, 0xc4, 0x00, 0x64, 0xdc, 0xc4, 0x00, 0x64,
+ 0xd0, 0xc4, 0x00, 0x64, 0xd4, 0xc4, 0x00, 0x64, 0x0d, 0x04, 0x03, 0xe8, 0xe0, 0x64, 0x20, 0xc8,
+ 0xe4, 0x64, 0x20, 0xc8, 0xf0, 0xc4, 0x20, 0xc8, 0xf4, 0xc4, 0x20, 0xc8, 0x7c, 0x64, 0x28, 0x96,
+ 0x7c, 0x64, 0x28, 0x97, 0x7c, 0x64, 0x28, 0x16, 0x7c, 0x64, 0x28, 0x17, 0x7c, 0xc4, 0x28, 0x38,
+ 0x7c, 0xc4, 0x28, 0x39, 0x7c, 0xc4, 0x28, 0x78, 0x7c, 0xc4, 0x28, 0x79, 0x7c, 0xc4, 0x00, 0x34,
+ 0x7c, 0xc4, 0x00, 0x35, 0x7c, 0xc4, 0x2a, 0x38, 0x7c, 0xc4, 0x2a, 0x39, 0x7c, 0xc4, 0x07, 0x74,
+ 0x7c, 0xc4, 0x07, 0x75, 0x7c, 0xc4, 0x07, 0x34, 0x7c, 0xc4, 0x07, 0x35, 0x7c, 0xc4, 0x2b, 0xb8,
+ 0x7c, 0xc4, 0x2b, 0xb9, 0x7c, 0xc4, 0x28, 0xf8, 0x7c, 0xc4, 0x28, 0xf9, 0x7c, 0xc4, 0x2b, 0x78,
+ 0x7c, 0xc4, 0x2b, 0x79, 0x7c, 0xc4, 0x2b, 0x38, 0x7c, 0xc4, 0x2b, 0x39, 0x7c, 0xc4, 0x2a, 0x78,
+ 0x7c, 0xc4, 0x2a, 0x79, 0x50, 0xc4, 0x02, 0x9e, 0x50, 0xc4, 0x02, 0x9f, 0x54, 0xc4, 0x02, 0x9e,
+ 0x54, 0xc4, 0x02, 0x9f, 0x5c, 0xc4, 0x2a, 0x9e, 0x5c, 0xc4, 0x2a, 0x9f, 0x7c, 0xc4, 0x28, 0x30,
+ 0x7c, 0xc4, 0x28, 0x31, 0x7c, 0xc4, 0x2e, 0x30, 0x7c, 0xc4, 0x2e, 0x31, 0x7c, 0xc4, 0x06, 0x70,
+ 0x7c, 0xc4, 0x06, 0x71, 0x7c, 0xc4, 0x2c, 0x30, 0x7c, 0xc4, 0x2c, 0x31, 0xfc, 0x64, 0x28, 0x2a,
+ 0xfc, 0x64, 0x28, 0x2b, 0xec, 0x64, 0x28, 0x2a, 0xec, 0x64, 0x28, 0x2b, 0xfc, 0x64, 0x28, 0x24,
+ 0xfc, 0x64, 0x28, 0x25, 0xec, 0x64, 0x28, 0x24, 0xec, 0x64, 0x28, 0x25, 0xfc, 0x64, 0x01, 0xf2,
+ 0xfc, 0x64, 0x01, 0xf3, 0xec, 0x64, 0x01, 0xf2, 0xec, 0x64, 0x01, 0xf3, 0xec, 0x60, 0x28, 0x30,
+ 0xec, 0x60, 0x28, 0x31, 0xfc, 0x60, 0x28, 0x34, 0xfc, 0x60, 0x28, 0x35, 0xfc, 0x64, 0x28, 0x28,
+ 0xfc, 0x64, 0x28, 0x29, 0xec, 0x64, 0x28, 0x28, 0xec, 0x64, 0x28, 0x29, 0xfc, 0x64, 0x29, 0xee,
+ 0xfc, 0x64, 0x29, 0xef, 0xfc, 0x64, 0x29, 0xfa, 0xfc, 0x64, 0x29, 0xfb, 0xec, 0x64, 0x29, 0xfa,
+ 0xec, 0x64, 0x29, 0xfb, 0xfc, 0x64, 0x29, 0xf8, 0xfc, 0x64, 0x29, 0xf9, 0xec, 0x64, 0x29, 0xf8,
+ 0xec, 0x64, 0x29, 0xf9, 0xfc, 0x64, 0x29, 0xfe, 0xfc, 0x64, 0x29, 0xff, 0xec, 0x64, 0x29, 0xfe,
+ 0xec, 0x64, 0x29, 0xff, 0xfc, 0x64, 0x29, 0xfc, 0xfc, 0x64, 0x29, 0xfd, 0xec, 0x64, 0x29, 0xfc,
+ 0xec, 0x64, 0x29, 0xfd, 0xfc, 0x60, 0x28, 0x1c, 0xfc, 0x60, 0x28, 0x1d, 0xfc, 0x60, 0x28, 0x1e,
+ 0xfc, 0x60, 0x28, 0x1f, 0xfc, 0x60, 0x28, 0x18, 0xfc, 0x60, 0x28, 0x19, 0xfc, 0x60, 0x04, 0x8e,
+ 0xfc, 0x60, 0x04, 0x8f, 0xfe, 0xa0, 0x00, 0x8c, 0xfe, 0xa0, 0x00, 0x8d, 0xfe, 0xa0, 0x00, 0x4c,
+ 0xfe, 0xa0, 0x00, 0x4d, 0xfd, 0xfe, 0x2d, 0x8e, 0xfd, 0xfe, 0x2d, 0x8f, 0xfc, 0x80, 0x51, 0x0c,
+ 0xfc, 0x80, 0x51, 0x0d, 0xfc, 0x60, 0x2a, 0x10, 0xfc, 0x60, 0x2a, 0x11, 0xfc, 0x60, 0x28, 0x90,
+ 0xfc, 0x60, 0x28, 0x91, 0xfc, 0x60, 0x29, 0x10, 0xfc, 0x60, 0x29, 0x11, 0xfc, 0x60, 0x28, 0x50,
+ 0xfc, 0x60, 0x28, 0x51, 0x10, 0x64, 0x28, 0x24, 0x10, 0x64, 0x28, 0x25, 0x10, 0x64, 0x28, 0x28,
+ 0x10, 0x64, 0x28, 0x29, 0x10, 0x64, 0x28, 0x2a, 0x10, 0x64, 0x28, 0x2b, 0x10, 0x64, 0x29, 0xee,
+ 0x10, 0x64, 0x29, 0xef, 0x10, 0x60, 0x28, 0x30, 0x10, 0x60, 0x28, 0x31, 0x10, 0x64, 0x01, 0xf2,
+ 0x10, 0x64, 0x01, 0xf3, 0x10, 0x60, 0x28, 0x34, 0x10, 0x60, 0x28, 0x35, 0x10, 0x64, 0x29, 0xf8,
+ 0x10, 0x64, 0x29, 0xf9, 0x10, 0x64, 0x29, 0xfa, 0x10, 0x64, 0x29, 0xfb, 0x10, 0x64, 0x29, 0xfc,
+ 0x10, 0x64, 0x29, 0xfd, 0x10, 0x64, 0x29, 0xfe, 0x10, 0x64, 0x29, 0xff, 0x10, 0x60, 0x28, 0x50,
+ 0x10, 0x60, 0x28, 0x51, 0x10, 0x60, 0x28, 0x90, 0x10, 0x60, 0x28, 0x91, 0x10, 0x60, 0x29, 0x10,
+ 0x10, 0x60, 0x29, 0x11, 0x10, 0x60, 0x2a, 0x10, 0x10, 0x60, 0x2a, 0x11, 0x10, 0x64, 0x29, 0xd4,
+ 0x10, 0x64, 0x29, 0xd5, 0x10, 0x64, 0x29, 0xd6, 0x10, 0x64, 0x29, 0xd7, 0x10, 0x64, 0x01, 0xd8,
+ 0x10, 0x64, 0x01, 0xd9, 0x10, 0x64, 0x01, 0xda, 0x10, 0x64, 0x01, 0xdb, 0x10, 0x64, 0x29, 0xdc,
+ 0x10, 0x64, 0x29, 0xdd, 0x10, 0x64, 0x29, 0xde, 0x10, 0x64, 0x29, 0xdf, 0x10, 0x64, 0x2c, 0x20,
+ 0x10, 0x64, 0x2c, 0x21, 0x10, 0x64, 0x2c, 0x60, 0x10, 0x64, 0x2c, 0x61, 0x10, 0x64, 0x2c, 0xa0,
+ 0x10, 0x64, 0x2c, 0xa1, 0x10, 0x64, 0x2c, 0xe0, 0x10, 0x64, 0x2c, 0xe1, 0x7c, 0x84, 0x28, 0x00,
+ 0x7c, 0x84, 0x28, 0x40, 0xfc, 0x84, 0x28, 0x40, 0xfc, 0x84, 0x28, 0x00, 0xfc, 0x9c, 0x00, 0x80,
+ 0x7c, 0x64, 0x28, 0xee, 0x7c, 0x64, 0x28, 0xae, 0x7c, 0x64, 0x2a, 0xee, 0x7c, 0x64, 0x2a, 0xae,
+ 0x7c, 0x64, 0x2a, 0x6e, 0x7c, 0x64, 0x2a, 0x2e, 0x7c, 0x64, 0x28, 0x6e, 0x7c, 0x64, 0x28, 0x2e,
+ 0x7c, 0xc4, 0x29, 0xee, 0x7c, 0xc4, 0x29, 0xae, 0x7c, 0xc4, 0x2b, 0x6e, 0x7c, 0xc4, 0x2b, 0x2e,
+ 0x7c, 0xc4, 0x29, 0x6e, 0x7c, 0xc4, 0x29, 0x2e, 0x7c, 0x64, 0x2e, 0x2c, 0x7c, 0x64, 0x2c, 0x2c,
+ 0x7c, 0xc4, 0x2f, 0x2c, 0x7c, 0xc4, 0x2d, 0x2c, 0x7c, 0xa4, 0x0c, 0xaa, 0x7c, 0x64, 0x2c, 0x2a,
+ 0x7c, 0xc4, 0x0d, 0xaa, 0x7c, 0xc4, 0x2d, 0x2a, 0x7c, 0x64, 0x28, 0x28, 0x7c, 0xc4, 0x29, 0x2d,
+ 0x7c, 0x64, 0x2c, 0xee, 0x7c, 0x64, 0x2c, 0xae, 0x7c, 0x64, 0x2c, 0x6e, 0x7c, 0x64, 0x2c, 0x2e,
+ 0x7c, 0xc4, 0x2d, 0xee, 0x7c, 0xc4, 0x2d, 0xae, 0x7c, 0xc4, 0x2f, 0xae, 0x7c, 0xc4, 0x2d, 0x6e,
+ 0x7c, 0xc4, 0x2d, 0x2e, 0x4e, 0xb6, 0xba, 0x02, 0x4e, 0xb6, 0xb9, 0x02, 0x4e, 0xb6, 0xba, 0x42,
+ 0x4e, 0xb6, 0xb9, 0xc2, 0x4e, 0xb6, 0xb8, 0x42, 0x4e, 0xb6, 0xbb, 0x82, 0x4e, 0xb6, 0xbb, 0x42,
+ 0x4e, 0xb6, 0xb9, 0x82, 0x4c, 0x9c, 0x00, 0x00, 0x7d, 0x04, 0x28, 0x08, 0x7c, 0x80, 0x04, 0x00,
+ 0x7c, 0x60, 0x00, 0x26, 0x7c, 0x60, 0x00, 0xa6, 0x7c, 0x68, 0x02, 0xa6, 0x7c, 0x6c, 0x42, 0xe6,
+ 0x7c, 0xcf, 0xf1, 0x20, 0x7c, 0xc0, 0x01, 0x24, 0x7c, 0x68, 0x03, 0xa6, 0x7c, 0x04, 0x28, 0xac,
+ 0x7c, 0x04, 0x2b, 0xac, 0x7c, 0x04, 0x28, 0x6c, 0x7c, 0x04, 0x2a, 0x2c, 0x7c, 0x04, 0x29, 0xec,
+ 0x7c, 0x04, 0x2f, 0xec, 0x7c, 0x04, 0x2f, 0xac, 0x7c, 0x60, 0x04, 0xa6, 0x7c, 0x60, 0x2d, 0x26,
+ 0x7c, 0xc0, 0x01, 0xa4, 0x7c, 0xc0, 0x29, 0xe4, 0x7c, 0x00, 0x2a, 0x64, 0x7c, 0x64, 0x2a, 0x6c,
+ 0x7c, 0xc4, 0x2b, 0x6c, 0x10, 0x64, 0x29, 0x0c, 0x10, 0xc4, 0x29, 0x0e, 0x10, 0x64, 0x29, 0x4c,
+ 0x10, 0xc4, 0x29, 0x4e, 0x10, 0x84, 0x28, 0x00, 0x10, 0x84, 0x28, 0x40, 0x10, 0x84, 0x28, 0x80,
+ 0x10, 0x84, 0x28, 0xc0, 0x10, 0x04, 0x2f, 0xec, 0x48, 0x00, 0x10, 0x00, 0x48, 0x00, 0x10, 0x02,
+ 0x48, 0x00, 0x10, 0x01, 0x48, 0x00, 0x10, 0x03, 0x41, 0x82, 0xff, 0xf4, 0x41, 0x82, 0xff, 0xf6,
+ 0x41, 0x82, 0xff, 0xf5, 0x41, 0x82, 0xff, 0xf7, 0x4d, 0x82, 0x04, 0x20, 0x4d, 0x82, 0x04, 0x21,
+ 0x4d, 0x82, 0x00, 0x20, 0x4d, 0x82, 0x00, 0x21,
+};
+
+TEST(Assembler, AllInstructions)
+{
+ auto res = Assemble(instructions, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(expected_instructions));
+
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], expected_instructions[i]) << " -> i=" << i;
+ }
+}
+
+constexpr char extended_instructions[] = "subi 0, 4, 8\n"
+ "subis 0, 4, 8\n"
+ "subic 0, 4, 8\n"
+ "subic. 0, 4, 8\n"
+ "cmpwi 0, 4\n"
+ "cmpwi 0, 4, 8\n"
+ "cmpw 0, 4\n"
+ "cmpw 0, 4, 8\n"
+ "cmplwi 0, 4\n"
+ "cmplwi 0, 4, 8\n"
+ "cmplw 0, 4\n"
+ "cmplw 0, 4, 8\n"
+ "crset 0\n"
+ "crclr 0\n"
+ "crmove 0, 4\n"
+ "crnot 0, 4\n"
+ "twlt 0, 4\n"
+ "twlti 0, 4\n"
+ "twle 0, 4\n"
+ "twlei 0, 4\n"
+ "tweq 0, 4\n"
+ "tweqi 0, 4\n"
+ "twge 0, 4\n"
+ "twgei 0, 4\n"
+ "twgt 0, 4\n"
+ "twgti 0, 4\n"
+ "twnl 0, 4\n"
+ "twnli 0, 4\n"
+ "twne 0, 4\n"
+ "twnei 0, 4\n"
+ "twng 0, 4\n"
+ "twngi 0, 4\n"
+ "twllt 0, 4\n"
+ "twllti 0, 4\n"
+ "twlle 0, 4\n"
+ "twllei 0, 4\n"
+ "twlge 0, 4\n"
+ "twlgei 0, 4\n"
+ "twlgt 0, 4\n"
+ "twlgti 0, 4\n"
+ "twlnl 0, 4\n"
+ "twlnli 0, 4\n"
+ "twlng 0, 4\n"
+ "twlngi 0, 4\n"
+ "trap \n"
+ "mtxer 0\n"
+ "mfxer 0\n"
+ "mtlr 0\n"
+ "mflr 0\n"
+ "mtctr 0\n"
+ "mfctr 0\n"
+ "mtdsisr 0\n"
+ "mfdsisr 0\n"
+ "mtdar 0\n"
+ "mfdar 0\n"
+ "mtdec 0\n"
+ "mfdec 0\n"
+ "mtsdr1 0\n"
+ "mfsdr1 0\n"
+ "mtsrr0 0\n"
+ "mfsrr0 0\n"
+ "mtsrr1 0\n"
+ "mfsrr1 0\n"
+ "mtear 0\n"
+ "mfear 0\n"
+ "mttbl 0\n"
+ "mftbl 0\n"
+ "mttbu 0\n"
+ "mftbu 0\n"
+ "mtsprg 0, 4\n"
+ "mfsprg 0, 1\n"
+ "mtibatu 0, 1\n"
+ "mfibatu 0, 1\n"
+ "mtibatl 0, 1\n"
+ "mfibatl 0, 1\n"
+ "mtdbatu 0, 1\n"
+ "mfdbatu 0, 1\n"
+ "mtdbatl 0, 1\n"
+ "mfdbatl 0, 1\n"
+ "nop \n"
+ "li 0, 4\n"
+ "lis 0, 4\n"
+ "la 0, 4(8)\n"
+ "mtcr 0\n"
+ "mfspr 0, 4\n"
+ "mftb 0, 268\n"
+ "mtspr 0, 4\n"
+ "sub 0, 4, 8\n"
+ "sub. 0, 4, 8\n"
+ "subo 0, 4, 8\n"
+ "subo. 0, 4, 8\n"
+ "subc 0, 4, 8\n"
+ "subc. 0, 4, 8\n"
+ "subco 0, 4, 8\n"
+ "subco. 0, 4, 8\n"
+ "extlwi 0, 4, 8, 12\n"
+ "extlwi. 0, 4, 8, 12\n"
+ "extrwi 0, 4, 8, 12\n"
+ "extrwi. 0, 4, 8, 12\n"
+ "inslwi 0, 4, 8, 12\n"
+ "inslwi. 0, 4, 8, 12\n"
+ "insrwi 0, 4, 8, 12\n"
+ "insrwi. 0, 4, 8, 12\n"
+ "rotlwi 0, 4, 8\n"
+ "rotlwi. 0, 4, 8\n"
+ "rotrwi 0, 4, 8\n"
+ "rotrwi. 0, 4, 8\n"
+ "rotlw 0, 4, 8\n"
+ "rotlw. 0, 4, 8\n"
+ "slwi 0, 4, 8\n"
+ "slwi. 0, 4, 8\n"
+ "srwi 0, 4, 8\n"
+ "srwi. 0, 4, 8\n"
+ "clrlwi 0, 4, 8\n"
+ "clrlwi. 0, 4, 8\n"
+ "clrrwi 0, 4, 8\n"
+ "clrrwi. 0, 4, 8\n"
+ "clrlslwi 0, 4, 12, 8\n"
+ "clrlslwi. 0, 4, 12, 8\n"
+ "mr 0, 4\n"
+ "mr. 0, 4\n"
+ "not 0, 4\n"
+ "not. 0, 4\n"
+ "bt 0, 4\n"
+ "btl 0, 4\n"
+ "bta 0, 4\n"
+ "btla 0, 4\n"
+ "bt- 0, 4\n"
+ "btl- 0, 4\n"
+ "bta- 0, 4\n"
+ "btla- 0, 4\n"
+ "bt+ 0, 4\n"
+ "btl+ 0, 4\n"
+ "bta+ 0, 4\n"
+ "btla+ 0, 4\n"
+ "bf 0, 4\n"
+ "bfl 0, 4\n"
+ "bfa 0, 4\n"
+ "bfla 0, 4\n"
+ "bf- 0, 4\n"
+ "bfl- 0, 4\n"
+ "bfa- 0, 4\n"
+ "bfla- 0, 4\n"
+ "bf+ 0, 4\n"
+ "bfl+ 0, 4\n"
+ "bfa+ 0, 4\n"
+ "bfla+ 0, 4\n"
+ "bdnz 0\n"
+ "bdnzl 0\n"
+ "bdnza 0\n"
+ "bdnzla 0\n"
+ "bdnz- 0\n"
+ "bdnzl- 0\n"
+ "bdnza- 0\n"
+ "bdnzla- 0\n"
+ "bdnz+ 0\n"
+ "bdnzl+ 0\n"
+ "bdnza+ 0\n"
+ "bdnzla+ 0\n"
+ "bdnzt 0, 4\n"
+ "bdnztl 0, 4\n"
+ "bdnzta 0, 4\n"
+ "bdnztla 0, 4\n"
+ "bdnzt- 0, 4\n"
+ "bdnztl- 0, 4\n"
+ "bdnzta- 0, 4\n"
+ "bdnztla- 0, 4\n"
+ "bdnzt+ 0, 4\n"
+ "bdnztl+ 0, 4\n"
+ "bdnzta+ 0, 4\n"
+ "bdnztla+ 0, 4\n"
+ "bdnzf 0, 4\n"
+ "bdnzfl 0, 4\n"
+ "bdnzfa 0, 4\n"
+ "bdnzfla 0, 4\n"
+ "bdnzf- 0, 4\n"
+ "bdnzfl- 0, 4\n"
+ "bdnzfa- 0, 4\n"
+ "bdnzfla- 0, 4\n"
+ "bdnzf+ 0, 4\n"
+ "bdnzfl+ 0, 4\n"
+ "bdnzfa+ 0, 4\n"
+ "bdnzfla+ 0, 4\n"
+ "bdz 0\n"
+ "bdzl 0\n"
+ "bdza 0\n"
+ "bdzla 0\n"
+ "bdz- 0\n"
+ "bdzl- 0\n"
+ "bdza- 0\n"
+ "bdzla- 0\n"
+ "bdz+ 0\n"
+ "bdzl+ 0\n"
+ "bdza+ 0\n"
+ "bdzla+ 0\n"
+ "bdzt 0, 4\n"
+ "bdztl 0, 4\n"
+ "bdzta 0, 4\n"
+ "bdztla 0, 4\n"
+ "bdzt- 0, 4\n"
+ "bdztl- 0, 4\n"
+ "bdzta- 0, 4\n"
+ "bdztla- 0, 4\n"
+ "bdzt+ 0, 4\n"
+ "bdztl+ 0, 4\n"
+ "bdzta+ 0, 4\n"
+ "bdztla+ 0, 4\n"
+ "bdzf 0, 4\n"
+ "bdzfl 0, 4\n"
+ "bdzfa 0, 4\n"
+ "bdzfla 0, 4\n"
+ "bdzf- 0, 4\n"
+ "bdzfl- 0, 4\n"
+ "bdzfa- 0, 4\n"
+ "bdzfla- 0, 4\n"
+ "bdzf+ 0, 4\n"
+ "bdzfl+ 0, 4\n"
+ "bdzfa+ 0, 4\n"
+ "bdzfla+ 0, 4\n"
+ "blt 0 \n"
+ "blt 0, 4\n"
+ "bltl 0 \n"
+ "bltl 0, 4\n"
+ "blta 0 \n"
+ "blta 0, 4\n"
+ "bltla 0 \n"
+ "bltla 0, 4\n"
+ "blt- 0 \n"
+ "blt- 0, 4\n"
+ "bltl- 0 \n"
+ "bltl- 0, 4\n"
+ "blta- 0 \n"
+ "blta- 0, 4\n"
+ "bltla- 0 \n"
+ "bltla- 0, 4\n"
+ "blt+ 0 \n"
+ "blt+ 0, 4\n"
+ "bltl+ 0 \n"
+ "bltl+ 0, 4\n"
+ "blta+ 0 \n"
+ "blta+ 0, 4\n"
+ "bltla+ 0 \n"
+ "bltla+ 0, 4\n"
+ "ble 0 \n"
+ "ble 0, 4\n"
+ "blel 0 \n"
+ "blel 0, 4\n"
+ "blea 0 \n"
+ "blea 0, 4\n"
+ "blela 0 \n"
+ "blela 0, 4\n"
+ "ble- 0 \n"
+ "ble- 0, 4\n"
+ "blel- 0 \n"
+ "blel- 0, 4\n"
+ "blea- 0 \n"
+ "blea- 0, 4\n"
+ "blela- 0 \n"
+ "blela- 0, 4\n"
+ "ble+ 0 \n"
+ "ble+ 0, 4\n"
+ "blel+ 0 \n"
+ "blel+ 0, 4\n"
+ "blea+ 0 \n"
+ "blea+ 0, 4\n"
+ "blela+ 0 \n"
+ "blela+ 0, 4\n"
+ "beq 0 \n"
+ "beq 0, 4\n"
+ "beql 0 \n"
+ "beql 0, 4\n"
+ "beqa 0 \n"
+ "beqa 0, 4\n"
+ "beqla 0 \n"
+ "beqla 0, 4\n"
+ "beq- 0 \n"
+ "beq- 0, 4\n"
+ "beql- 0 \n"
+ "beql- 0, 4\n"
+ "beqa- 0 \n"
+ "beqa- 0, 4\n"
+ "beqla- 0 \n"
+ "beqla- 0, 4\n"
+ "beq+ 0 \n"
+ "beq+ 0, 4\n"
+ "beql+ 0 \n"
+ "beql+ 0, 4\n"
+ "beqa+ 0 \n"
+ "beqa+ 0, 4\n"
+ "beqla+ 0 \n"
+ "beqla+ 0, 4\n"
+ "bge 0 \n"
+ "bge 0, 4\n"
+ "bgel 0 \n"
+ "bgel 0, 4\n"
+ "bgea 0 \n"
+ "bgea 0, 4\n"
+ "bgela 0 \n"
+ "bgela 0, 4\n"
+ "bge- 0 \n"
+ "bge- 0, 4\n"
+ "bgel- 0 \n"
+ "bgel- 0, 4\n"
+ "bgea- 0 \n"
+ "bgea- 0, 4\n"
+ "bgela- 0 \n"
+ "bgela- 0, 4\n"
+ "bge+ 0 \n"
+ "bge+ 0, 4\n"
+ "bgel+ 0 \n"
+ "bgel+ 0, 4\n"
+ "bgea+ 0 \n"
+ "bgea+ 0, 4\n"
+ "bgela+ 0 \n"
+ "bgela+ 0, 4\n"
+ "bgt 0 \n"
+ "bgt 0, 4\n"
+ "bgtl 0 \n"
+ "bgtl 0, 4\n"
+ "bgta 0 \n"
+ "bgta 0, 4\n"
+ "bgtla 0 \n"
+ "bgtla 0, 4\n"
+ "bgt- 0 \n"
+ "bgt- 0, 4\n"
+ "bgtl- 0 \n"
+ "bgtl- 0, 4\n"
+ "bgta- 0 \n"
+ "bgta- 0, 4\n"
+ "bgtla- 0 \n"
+ "bgtla- 0, 4\n"
+ "bgt+ 0 \n"
+ "bgt+ 0, 4\n"
+ "bgtl+ 0 \n"
+ "bgtl+ 0, 4\n"
+ "bgta+ 0 \n"
+ "bgta+ 0, 4\n"
+ "bgtla+ 0 \n"
+ "bgtla+ 0, 4\n"
+ "bnl 0 \n"
+ "bnl 0, 4\n"
+ "bnll 0 \n"
+ "bnll 0, 4\n"
+ "bnla 0 \n"
+ "bnla 0, 4\n"
+ "bnlla 0 \n"
+ "bnlla 0, 4\n"
+ "bnl- 0 \n"
+ "bnl- 0, 4\n"
+ "bnll- 0 \n"
+ "bnll- 0, 4\n"
+ "bnla- 0 \n"
+ "bnla- 0, 4\n"
+ "bnlla- 0 \n"
+ "bnlla- 0, 4\n"
+ "bnl+ 0 \n"
+ "bnl+ 0, 4\n"
+ "bnll+ 0 \n"
+ "bnll+ 0, 4\n"
+ "bnla+ 0 \n"
+ "bnla+ 0, 4\n"
+ "bnlla+ 0 \n"
+ "bnlla+ 0, 4\n"
+ "bne 0 \n"
+ "bne 0, 4\n"
+ "bnel 0 \n"
+ "bnel 0, 4\n"
+ "bnea 0 \n"
+ "bnea 0, 4\n"
+ "bnela 0 \n"
+ "bnela 0, 4\n"
+ "bne- 0 \n"
+ "bne- 0, 4\n"
+ "bnel- 0 \n"
+ "bnel- 0, 4\n"
+ "bnea- 0 \n"
+ "bnea- 0, 4\n"
+ "bnela- 0 \n"
+ "bnela- 0, 4\n"
+ "bne+ 0 \n"
+ "bne+ 0, 4\n"
+ "bnel+ 0 \n"
+ "bnel+ 0, 4\n"
+ "bnea+ 0 \n"
+ "bnea+ 0, 4\n"
+ "bnela+ 0 \n"
+ "bnela+ 0, 4\n"
+ "bng 0 \n"
+ "bng 0, 4\n"
+ "bngl 0 \n"
+ "bngl 0, 4\n"
+ "bnga 0 \n"
+ "bnga 0, 4\n"
+ "bngla 0 \n"
+ "bngla 0, 4\n"
+ "bng- 0 \n"
+ "bng- 0, 4\n"
+ "bngl- 0 \n"
+ "bngl- 0, 4\n"
+ "bnga- 0 \n"
+ "bnga- 0, 4\n"
+ "bngla- 0 \n"
+ "bngla- 0, 4\n"
+ "bng+ 0 \n"
+ "bng+ 0, 4\n"
+ "bngl+ 0 \n"
+ "bngl+ 0, 4\n"
+ "bnga+ 0 \n"
+ "bnga+ 0, 4\n"
+ "bngla+ 0 \n"
+ "bngla+ 0, 4\n"
+ "bso 0 \n"
+ "bso 0, 4\n"
+ "bsol 0 \n"
+ "bsol 0, 4\n"
+ "bsoa 0 \n"
+ "bsoa 0, 4\n"
+ "bsola 0 \n"
+ "bsola 0, 4\n"
+ "bso- 0 \n"
+ "bso- 0, 4\n"
+ "bsol- 0 \n"
+ "bsol- 0, 4\n"
+ "bsoa- 0 \n"
+ "bsoa- 0, 4\n"
+ "bsola- 0 \n"
+ "bsola- 0, 4\n"
+ "bso+ 0 \n"
+ "bso+ 0, 4\n"
+ "bsol+ 0 \n"
+ "bsol+ 0, 4\n"
+ "bsoa+ 0 \n"
+ "bsoa+ 0, 4\n"
+ "bsola+ 0 \n"
+ "bsola+ 0, 4\n"
+ "bns 0 \n"
+ "bns 0, 4\n"
+ "bnsl 0 \n"
+ "bnsl 0, 4\n"
+ "bnsa 0 \n"
+ "bnsa 0, 4\n"
+ "bnsla 0 \n"
+ "bnsla 0, 4\n"
+ "bns- 0 \n"
+ "bns- 0, 4\n"
+ "bnsl- 0 \n"
+ "bnsl- 0, 4\n"
+ "bnsa- 0 \n"
+ "bnsa- 0, 4\n"
+ "bnsla- 0 \n"
+ "bnsla- 0, 4\n"
+ "bns+ 0 \n"
+ "bns+ 0, 4\n"
+ "bnsl+ 0 \n"
+ "bnsl+ 0, 4\n"
+ "bnsa+ 0 \n"
+ "bnsa+ 0, 4\n"
+ "bnsla+ 0 \n"
+ "bnsla+ 0, 4\n"
+ "bun 0 \n"
+ "bun 0, 4\n"
+ "bunl 0 \n"
+ "bunl 0, 4\n"
+ "buna 0 \n"
+ "buna 0, 4\n"
+ "bunla 0 \n"
+ "bunla 0, 4\n"
+ "bun- 0 \n"
+ "bun- 0, 4\n"
+ "bunl- 0 \n"
+ "bunl- 0, 4\n"
+ "buna- 0 \n"
+ "buna- 0, 4\n"
+ "bunla- 0 \n"
+ "bunla- 0, 4\n"
+ "bun+ 0 \n"
+ "bun+ 0, 4\n"
+ "bunl+ 0 \n"
+ "bunl+ 0, 4\n"
+ "buna+ 0 \n"
+ "buna+ 0, 4\n"
+ "bunla+ 0 \n"
+ "bunla+ 0, 4\n"
+ "bnu 0 \n"
+ "bnu 0, 4\n"
+ "bnul 0 \n"
+ "bnul 0, 4\n"
+ "bnua 0 \n"
+ "bnua 0, 4\n"
+ "bnula 0 \n"
+ "bnula 0, 4\n"
+ "bnu- 0 \n"
+ "bnu- 0, 4\n"
+ "bnul- 0 \n"
+ "bnul- 0, 4\n"
+ "bnua- 0 \n"
+ "bnua- 0, 4\n"
+ "bnula- 0 \n"
+ "bnula- 0, 4\n"
+ "bnu+ 0 \n"
+ "bnu+ 0, 4\n"
+ "bnul+ 0 \n"
+ "bnul+ 0, 4\n"
+ "bnua+ 0 \n"
+ "bnua+ 0, 4\n"
+ "bnula+ 0 \n"
+ "bnula+ 0, 4\n"
+ "blr \n"
+ "blrl \n"
+ "bctr \n"
+ "bctrl \n"
+ "btlr 0\n"
+ "btlrl 0\n"
+ "btlr- 0\n"
+ "btlrl- 0\n"
+ "btlr+ 0\n"
+ "btlrl+ 0\n"
+ "btctr 0\n"
+ "btctrl 0\n"
+ "btctr- 0\n"
+ "btctrl- 0\n"
+ "btctr+ 0\n"
+ "btctrl+ 0\n"
+ "bflr 0\n"
+ "bflrl 0\n"
+ "bflr- 0\n"
+ "bflrl- 0\n"
+ "bflr+ 0\n"
+ "bflrl+ 0\n"
+ "bfctr 0\n"
+ "bfctrl 0\n"
+ "bfctr- 0\n"
+ "bfctrl- 0\n"
+ "bfctr+ 0\n"
+ "bfctrl+ 0\n"
+ "bdnzlr \n"
+ "bdnzlrl \n"
+ "bdnzlr- \n"
+ "bdnzlrl- \n"
+ "bdnzlr+ \n"
+ "bdnzlrl+ \n"
+ "bdnztlr 0\n"
+ "bdnztlrl 0\n"
+ "bdnztlr- 0\n"
+ "bdnztlrl- 0\n"
+ "bdnztlr+ 0\n"
+ "bdnztlrl+ 0\n"
+ "bdnzflr 0\n"
+ "bdnzflrl 0\n"
+ "bdnzflr- 0\n"
+ "bdnzflrl- 0\n"
+ "bdnzflr+ 0\n"
+ "bdnzflrl+ 0\n"
+ "bdzlr \n"
+ "bdzlrl \n"
+ "bdzlr- \n"
+ "bdzlrl- \n"
+ "bdzlr+ \n"
+ "bdzlrl+ \n"
+ "bdztlr 0\n"
+ "bdztlrl 0\n"
+ "bdztlr- 0\n"
+ "bdztlrl- 0\n"
+ "bdztlr+ 0\n"
+ "bdztlrl+ 0\n"
+ "bdzflr 0\n"
+ "bdzflrl 0\n"
+ "bdzflr- 0\n"
+ "bdzflrl- 0\n"
+ "bdzflr+ 0\n"
+ "bdzflrl+ 0\n"
+ "bltlr\n"
+ "bltlr 0\n"
+ "bltlrl\n"
+ "bltlrl 0\n"
+ "bltlr-\n"
+ "bltlr- 0\n"
+ "bltlrl-\n"
+ "bltlrl- 0\n"
+ "bltlr+\n"
+ "bltlr+ 0\n"
+ "bltlrl+\n"
+ "bltlrl+ 0\n"
+ "bltctr\n"
+ "bltctr 0\n"
+ "bltctrl\n"
+ "bltctrl 0\n"
+ "bltctr-\n"
+ "bltctr- 0\n"
+ "bltctrl-\n"
+ "bltctrl- 0\n"
+ "bltctr+\n"
+ "bltctr+ 0\n"
+ "bltctrl+\n"
+ "bltctrl+ 0\n"
+ "blelr\n"
+ "blelr 0\n"
+ "blelrl\n"
+ "blelrl 0\n"
+ "blelr-\n"
+ "blelr- 0\n"
+ "blelrl-\n"
+ "blelrl- 0\n"
+ "blelr+\n"
+ "blelr+ 0\n"
+ "blelrl+\n"
+ "blelrl+ 0\n"
+ "blectr\n"
+ "blectr 0\n"
+ "blectrl\n"
+ "blectrl 0\n"
+ "blectr-\n"
+ "blectr- 0\n"
+ "blectrl-\n"
+ "blectrl- 0\n"
+ "blectr+\n"
+ "blectr+ 0\n"
+ "blectrl+\n"
+ "blectrl+ 0\n"
+ "beqlr\n"
+ "beqlr 0\n"
+ "beqlrl\n"
+ "beqlrl 0\n"
+ "beqlr-\n"
+ "beqlr- 0\n"
+ "beqlrl-\n"
+ "beqlrl- 0\n"
+ "beqlr+\n"
+ "beqlr+ 0\n"
+ "beqlrl+\n"
+ "beqlrl+ 0\n"
+ "beqctr\n"
+ "beqctr 0\n"
+ "beqctrl\n"
+ "beqctrl 0\n"
+ "beqctr-\n"
+ "beqctr- 0\n"
+ "beqctrl-\n"
+ "beqctrl- 0\n"
+ "beqctr+\n"
+ "beqctr+ 0\n"
+ "beqctrl+\n"
+ "beqctrl+ 0\n"
+ "bgelr\n"
+ "bgelr 0\n"
+ "bgelrl\n"
+ "bgelrl 0\n"
+ "bgelr-\n"
+ "bgelr- 0\n"
+ "bgelrl-\n"
+ "bgelrl- 0\n"
+ "bgelr+\n"
+ "bgelr+ 0\n"
+ "bgelrl+\n"
+ "bgelrl+ 0\n"
+ "bgectr\n"
+ "bgectr 0\n"
+ "bgectrl\n"
+ "bgectrl 0\n"
+ "bgectr-\n"
+ "bgectr- 0\n"
+ "bgectrl-\n"
+ "bgectrl- 0\n"
+ "bgectr+\n"
+ "bgectr+ 0\n"
+ "bgectrl+\n"
+ "bgectrl+ 0\n"
+ "bgtlr\n"
+ "bgtlr 0\n"
+ "bgtlrl\n"
+ "bgtlrl 0\n"
+ "bgtlr-\n"
+ "bgtlr- 0\n"
+ "bgtlrl-\n"
+ "bgtlrl- 0\n"
+ "bgtlr+\n"
+ "bgtlr+ 0\n"
+ "bgtlrl+\n"
+ "bgtlrl+ 0\n"
+ "bgtctr\n"
+ "bgtctr 0\n"
+ "bgtctrl\n"
+ "bgtctrl 0\n"
+ "bgtctr-\n"
+ "bgtctr- 0\n"
+ "bgtctrl-\n"
+ "bgtctrl- 0\n"
+ "bgtctr+\n"
+ "bgtctr+ 0\n"
+ "bgtctrl+\n"
+ "bgtctrl+ 0\n"
+ "bnllr\n"
+ "bnllr 0\n"
+ "bnllrl\n"
+ "bnllrl 0\n"
+ "bnllr-\n"
+ "bnllr- 0\n"
+ "bnllrl-\n"
+ "bnllrl- 0\n"
+ "bnllr+\n"
+ "bnllr+ 0\n"
+ "bnllrl+\n"
+ "bnllrl+ 0\n"
+ "bnlctr\n"
+ "bnlctr 0\n"
+ "bnlctrl\n"
+ "bnlctrl 0\n"
+ "bnlctr-\n"
+ "bnlctr- 0\n"
+ "bnlctrl-\n"
+ "bnlctrl- 0\n"
+ "bnlctr+\n"
+ "bnlctr+ 0\n"
+ "bnlctrl+\n"
+ "bnlctrl+ 0\n"
+ "bnelr\n"
+ "bnelr 0\n"
+ "bnelrl\n"
+ "bnelrl 0\n"
+ "bnelr-\n"
+ "bnelr- 0\n"
+ "bnelrl-\n"
+ "bnelrl- 0\n"
+ "bnelr+\n"
+ "bnelr+ 0\n"
+ "bnelrl+\n"
+ "bnelrl+ 0\n"
+ "bnectr\n"
+ "bnectr 0\n"
+ "bnectrl\n"
+ "bnectrl 0\n"
+ "bnectr-\n"
+ "bnectr- 0\n"
+ "bnectrl-\n"
+ "bnectrl- 0\n"
+ "bnectr+\n"
+ "bnectr+ 0\n"
+ "bnectrl+\n"
+ "bnectrl+ 0\n"
+ "bnglr\n"
+ "bnglr 0\n"
+ "bnglrl\n"
+ "bnglrl 0\n"
+ "bnglr-\n"
+ "bnglr- 0\n"
+ "bnglrl-\n"
+ "bnglrl- 0\n"
+ "bnglr+\n"
+ "bnglr+ 0\n"
+ "bnglrl+\n"
+ "bnglrl+ 0\n"
+ "bngctr\n"
+ "bngctr 0\n"
+ "bngctrl\n"
+ "bngctrl 0\n"
+ "bngctr-\n"
+ "bngctr- 0\n"
+ "bngctrl-\n"
+ "bngctrl- 0\n"
+ "bngctr+\n"
+ "bngctr+ 0\n"
+ "bngctrl+\n"
+ "bngctrl+ 0\n"
+ "bsolr\n"
+ "bsolr 0\n"
+ "bsolrl\n"
+ "bsolrl 0\n"
+ "bsolr-\n"
+ "bsolr- 0\n"
+ "bsolrl-\n"
+ "bsolrl- 0\n"
+ "bsolr+\n"
+ "bsolr+ 0\n"
+ "bsolrl+\n"
+ "bsolrl+ 0\n"
+ "bsoctr\n"
+ "bsoctr 0\n"
+ "bsoctrl\n"
+ "bsoctrl 0\n"
+ "bsoctr-\n"
+ "bsoctr- 0\n"
+ "bsoctrl-\n"
+ "bsoctrl- 0\n"
+ "bsoctr+\n"
+ "bsoctr+ 0\n"
+ "bsoctrl+\n"
+ "bsoctrl+ 0\n"
+ "bnslr\n"
+ "bnslr 0\n"
+ "bnslrl\n"
+ "bnslrl 0\n"
+ "bnslr-\n"
+ "bnslr- 0\n"
+ "bnslrl-\n"
+ "bnslrl- 0\n"
+ "bnslr+\n"
+ "bnslr+ 0\n"
+ "bnslrl+\n"
+ "bnslrl+ 0\n"
+ "bnsctr\n"
+ "bnsctr 0\n"
+ "bnsctrl\n"
+ "bnsctrl 0\n"
+ "bnsctr-\n"
+ "bnsctr- 0\n"
+ "bnsctrl-\n"
+ "bnsctrl- 0\n"
+ "bnsctr+\n"
+ "bnsctr+ 0\n"
+ "bnsctrl+\n"
+ "bnsctrl+ 0\n"
+ "bunlr\n"
+ "bunlr 0\n"
+ "bunlrl\n"
+ "bunlrl 0\n"
+ "bunlr-\n"
+ "bunlr- 0\n"
+ "bunlrl-\n"
+ "bunlrl- 0\n"
+ "bunlr+\n"
+ "bunlr+ 0\n"
+ "bunlrl+\n"
+ "bunlrl+ 0\n"
+ "bunctr\n"
+ "bunctr 0\n"
+ "bunctrl\n"
+ "bunctrl 0\n"
+ "bunctr-\n"
+ "bunctr- 0\n"
+ "bunctrl-\n"
+ "bunctrl- 0\n"
+ "bunctr+\n"
+ "bunctr+ 0\n"
+ "bunctrl+\n"
+ "bunctrl+ 0\n"
+ "bnulr\n"
+ "bnulr 0\n"
+ "bnulrl\n"
+ "bnulrl 0\n"
+ "bnulr-\n"
+ "bnulr- 0\n"
+ "bnulrl-\n"
+ "bnulrl- 0\n"
+ "bnulr+\n"
+ "bnulr+ 0\n"
+ "bnulrl+\n"
+ "bnulrl+ 0\n"
+ "bnuctr\n"
+ "bnuctr 0\n"
+ "bnuctrl\n"
+ "bnuctrl 0\n"
+ "bnuctr-\n"
+ "bnuctr- 0\n"
+ "bnuctrl-\n"
+ "bnuctrl- 0\n"
+ "bnuctr+\n"
+ "bnuctr+ 0\n"
+ "bnuctrl+\n"
+ "bnuctrl+ 0\n";
+
+constexpr u8 extended_expect[] = {
+ 0x38, 0x04, 0xff, 0xf8, 0x3c, 0x04, 0xff, 0xf8, 0x30, 0x04, 0xff, 0xf8, 0x34, 0x04, 0xff, 0xf8,
+ 0x2c, 0x00, 0x00, 0x04, 0x2c, 0x04, 0x00, 0x08, 0x7c, 0x00, 0x20, 0x00, 0x7c, 0x04, 0x40, 0x00,
+ 0x28, 0x00, 0x00, 0x04, 0x28, 0x04, 0x00, 0x08, 0x7c, 0x00, 0x20, 0x40, 0x7c, 0x04, 0x40, 0x40,
+ 0x4c, 0x00, 0x02, 0x42, 0x4c, 0x00, 0x01, 0x82, 0x4c, 0x04, 0x23, 0x82, 0x4c, 0x04, 0x20, 0x42,
+ 0x7e, 0x00, 0x20, 0x08, 0x0e, 0x00, 0x00, 0x04, 0x7e, 0x80, 0x20, 0x08, 0x0e, 0x80, 0x00, 0x04,
+ 0x7c, 0x80, 0x20, 0x08, 0x0c, 0x80, 0x00, 0x04, 0x7d, 0x80, 0x20, 0x08, 0x0d, 0x80, 0x00, 0x04,
+ 0x7d, 0x00, 0x20, 0x08, 0x0d, 0x00, 0x00, 0x04, 0x7d, 0x80, 0x20, 0x08, 0x0d, 0x80, 0x00, 0x04,
+ 0x7f, 0x00, 0x20, 0x08, 0x0f, 0x00, 0x00, 0x04, 0x7e, 0x80, 0x20, 0x08, 0x0e, 0x80, 0x00, 0x04,
+ 0x7c, 0x40, 0x20, 0x08, 0x0c, 0x40, 0x00, 0x04, 0x7c, 0xc0, 0x20, 0x08, 0x0c, 0xc0, 0x00, 0x04,
+ 0x7c, 0xa0, 0x20, 0x08, 0x0c, 0xa0, 0x00, 0x04, 0x7c, 0x20, 0x20, 0x08, 0x0c, 0x20, 0x00, 0x04,
+ 0x7c, 0xa0, 0x20, 0x08, 0x0c, 0xa0, 0x00, 0x04, 0x7c, 0xc0, 0x20, 0x08, 0x0c, 0xc0, 0x00, 0x04,
+ 0x7f, 0xe0, 0x00, 0x08, 0x7c, 0x01, 0x03, 0xa6, 0x7c, 0x01, 0x02, 0xa6, 0x7c, 0x08, 0x03, 0xa6,
+ 0x7c, 0x08, 0x02, 0xa6, 0x7c, 0x09, 0x03, 0xa6, 0x7c, 0x09, 0x02, 0xa6, 0x7c, 0x12, 0x03, 0xa6,
+ 0x7c, 0x12, 0x02, 0xa6, 0x7c, 0x13, 0x03, 0xa6, 0x7c, 0x13, 0x02, 0xa6, 0x7c, 0x16, 0x03, 0xa6,
+ 0x7c, 0x16, 0x02, 0xa6, 0x7c, 0x19, 0x03, 0xa6, 0x7c, 0x19, 0x02, 0xa6, 0x7c, 0x1a, 0x03, 0xa6,
+ 0x7c, 0x1a, 0x02, 0xa6, 0x7c, 0x1b, 0x03, 0xa6, 0x7c, 0x1b, 0x02, 0xa6, 0x7c, 0x1a, 0x43, 0xa6,
+ 0x7c, 0x1a, 0x42, 0xa6, 0x7c, 0x1c, 0x43, 0xa6, 0x7c, 0x0c, 0x42, 0xe6, 0x7c, 0x1d, 0x43, 0xa6,
+ 0x7c, 0x0d, 0x42, 0xe6, 0x7c, 0x90, 0x43, 0xa6, 0x7c, 0x11, 0x42, 0xa6, 0x7c, 0x30, 0x83, 0xa6,
+ 0x7c, 0x12, 0x82, 0xa6, 0x7c, 0x31, 0x83, 0xa6, 0x7c, 0x13, 0x82, 0xa6, 0x7c, 0x38, 0x83, 0xa6,
+ 0x7c, 0x1a, 0x82, 0xa6, 0x7c, 0x39, 0x83, 0xa6, 0x7c, 0x1b, 0x82, 0xa6, 0x60, 0x00, 0x00, 0x00,
+ 0x38, 0x00, 0x00, 0x04, 0x3c, 0x00, 0x00, 0x04, 0x38, 0x08, 0x00, 0x04, 0x7c, 0x0f, 0xf1, 0x20,
+ 0x7c, 0x04, 0x02, 0xa6, 0x7c, 0x0c, 0x42, 0xe6, 0x7c, 0x80, 0x03, 0xa6, 0x7c, 0x08, 0x20, 0x50,
+ 0x7c, 0x08, 0x20, 0x51, 0x7c, 0x08, 0x24, 0x50, 0x7c, 0x08, 0x24, 0x51, 0x7c, 0x08, 0x20, 0x10,
+ 0x7c, 0x08, 0x20, 0x11, 0x7c, 0x08, 0x24, 0x10, 0x7c, 0x08, 0x24, 0x11, 0x54, 0x80, 0x60, 0x0e,
+ 0x54, 0x80, 0x60, 0x0f, 0x54, 0x80, 0xa6, 0x3e, 0x54, 0x80, 0xa6, 0x3f, 0x50, 0x80, 0xa3, 0x26,
+ 0x50, 0x80, 0xa3, 0x27, 0x50, 0x80, 0x63, 0x26, 0x50, 0x80, 0x63, 0x27, 0x54, 0x80, 0x40, 0x3e,
+ 0x54, 0x80, 0x40, 0x3f, 0x54, 0x80, 0xc0, 0x3e, 0x54, 0x80, 0xc0, 0x3f, 0x5c, 0x80, 0x40, 0x3e,
+ 0x5c, 0x80, 0x40, 0x3f, 0x54, 0x80, 0x40, 0x2e, 0x54, 0x80, 0x40, 0x2f, 0x54, 0x80, 0xc2, 0x3e,
+ 0x54, 0x80, 0xc2, 0x3f, 0x54, 0x80, 0x02, 0x3e, 0x54, 0x80, 0x02, 0x3f, 0x54, 0x80, 0x00, 0x2e,
+ 0x54, 0x80, 0x00, 0x2f, 0x54, 0x80, 0x41, 0x2e, 0x54, 0x80, 0x41, 0x2f, 0x7c, 0x80, 0x23, 0x78,
+ 0x7c, 0x80, 0x23, 0x79, 0x7c, 0x80, 0x20, 0xf8, 0x7c, 0x80, 0x20, 0xf9, 0x41, 0x80, 0x00, 0x04,
+ 0x41, 0x80, 0x00, 0x05, 0x41, 0x80, 0x00, 0x06, 0x41, 0x80, 0x00, 0x07, 0x41, 0x80, 0x00, 0x04,
+ 0x41, 0x80, 0x00, 0x05, 0x41, 0x80, 0x00, 0x06, 0x41, 0x80, 0x00, 0x07, 0x41, 0xa0, 0x00, 0x04,
+ 0x41, 0xa0, 0x00, 0x05, 0x41, 0xa0, 0x00, 0x06, 0x41, 0xa0, 0x00, 0x07, 0x40, 0x80, 0x00, 0x04,
+ 0x40, 0x80, 0x00, 0x05, 0x40, 0x80, 0x00, 0x06, 0x40, 0x80, 0x00, 0x07, 0x40, 0x80, 0x00, 0x04,
+ 0x40, 0x80, 0x00, 0x05, 0x40, 0x80, 0x00, 0x06, 0x40, 0x80, 0x00, 0x07, 0x40, 0xa0, 0x00, 0x04,
+ 0x40, 0xa0, 0x00, 0x05, 0x40, 0xa0, 0x00, 0x06, 0x40, 0xa0, 0x00, 0x07, 0x42, 0x00, 0x00, 0x00,
+ 0x42, 0x00, 0x00, 0x01, 0x42, 0x00, 0x00, 0x02, 0x42, 0x00, 0x00, 0x03, 0x42, 0x00, 0x00, 0x00,
+ 0x42, 0x00, 0x00, 0x01, 0x42, 0x00, 0x00, 0x02, 0x42, 0x00, 0x00, 0x03, 0x42, 0x20, 0x00, 0x00,
+ 0x42, 0x20, 0x00, 0x01, 0x42, 0x20, 0x00, 0x02, 0x42, 0x20, 0x00, 0x03, 0x41, 0x00, 0x00, 0x04,
+ 0x41, 0x00, 0x00, 0x05, 0x41, 0x00, 0x00, 0x06, 0x41, 0x00, 0x00, 0x07, 0x41, 0x00, 0x00, 0x04,
+ 0x41, 0x00, 0x00, 0x05, 0x41, 0x00, 0x00, 0x06, 0x41, 0x00, 0x00, 0x07, 0x41, 0x20, 0x00, 0x04,
+ 0x41, 0x20, 0x00, 0x05, 0x41, 0x20, 0x00, 0x06, 0x41, 0x20, 0x00, 0x07, 0x40, 0x00, 0x00, 0x04,
+ 0x40, 0x00, 0x00, 0x05, 0x40, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x04,
+ 0x40, 0x00, 0x00, 0x05, 0x40, 0x00, 0x00, 0x06, 0x40, 0x00, 0x00, 0x07, 0x40, 0x20, 0x00, 0x04,
+ 0x40, 0x20, 0x00, 0x05, 0x40, 0x20, 0x00, 0x06, 0x40, 0x20, 0x00, 0x07, 0x42, 0x40, 0x00, 0x00,
+ 0x42, 0x40, 0x00, 0x01, 0x42, 0x40, 0x00, 0x02, 0x42, 0x40, 0x00, 0x03, 0x42, 0x40, 0x00, 0x00,
+ 0x42, 0x40, 0x00, 0x01, 0x42, 0x40, 0x00, 0x02, 0x42, 0x40, 0x00, 0x03, 0x42, 0x60, 0x00, 0x00,
+ 0x42, 0x60, 0x00, 0x01, 0x42, 0x60, 0x00, 0x02, 0x42, 0x60, 0x00, 0x03, 0x41, 0x40, 0x00, 0x04,
+ 0x41, 0x40, 0x00, 0x05, 0x41, 0x40, 0x00, 0x06, 0x41, 0x40, 0x00, 0x07, 0x41, 0x40, 0x00, 0x04,
+ 0x41, 0x40, 0x00, 0x05, 0x41, 0x40, 0x00, 0x06, 0x41, 0x40, 0x00, 0x07, 0x41, 0x60, 0x00, 0x04,
+ 0x41, 0x60, 0x00, 0x05, 0x41, 0x60, 0x00, 0x06, 0x41, 0x60, 0x00, 0x07, 0x40, 0x40, 0x00, 0x04,
+ 0x40, 0x40, 0x00, 0x05, 0x40, 0x40, 0x00, 0x06, 0x40, 0x40, 0x00, 0x07, 0x40, 0x40, 0x00, 0x04,
+ 0x40, 0x40, 0x00, 0x05, 0x40, 0x40, 0x00, 0x06, 0x40, 0x40, 0x00, 0x07, 0x40, 0x60, 0x00, 0x04,
+ 0x40, 0x60, 0x00, 0x05, 0x40, 0x60, 0x00, 0x06, 0x40, 0x60, 0x00, 0x07, 0x41, 0x80, 0x00, 0x00,
+ 0x41, 0x80, 0x00, 0x04, 0x41, 0x80, 0x00, 0x01, 0x41, 0x80, 0x00, 0x05, 0x41, 0x80, 0x00, 0x02,
+ 0x41, 0x80, 0x00, 0x06, 0x41, 0x80, 0x00, 0x03, 0x41, 0x80, 0x00, 0x07, 0x41, 0x80, 0x00, 0x00,
+ 0x41, 0x80, 0x00, 0x04, 0x41, 0x80, 0x00, 0x01, 0x41, 0x80, 0x00, 0x05, 0x41, 0x80, 0x00, 0x02,
+ 0x41, 0x80, 0x00, 0x06, 0x41, 0x80, 0x00, 0x03, 0x41, 0x80, 0x00, 0x07, 0x41, 0xa0, 0x00, 0x00,
+ 0x41, 0xa0, 0x00, 0x04, 0x41, 0xa0, 0x00, 0x01, 0x41, 0xa0, 0x00, 0x05, 0x41, 0xa0, 0x00, 0x02,
+ 0x41, 0xa0, 0x00, 0x06, 0x41, 0xa0, 0x00, 0x03, 0x41, 0xa0, 0x00, 0x07, 0x40, 0x81, 0x00, 0x00,
+ 0x40, 0x81, 0x00, 0x04, 0x40, 0x81, 0x00, 0x01, 0x40, 0x81, 0x00, 0x05, 0x40, 0x81, 0x00, 0x02,
+ 0x40, 0x81, 0x00, 0x06, 0x40, 0x81, 0x00, 0x03, 0x40, 0x81, 0x00, 0x07, 0x40, 0x81, 0x00, 0x00,
+ 0x40, 0x81, 0x00, 0x04, 0x40, 0x81, 0x00, 0x01, 0x40, 0x81, 0x00, 0x05, 0x40, 0x81, 0x00, 0x02,
+ 0x40, 0x81, 0x00, 0x06, 0x40, 0x81, 0x00, 0x03, 0x40, 0x81, 0x00, 0x07, 0x40, 0xa1, 0x00, 0x00,
+ 0x40, 0xa1, 0x00, 0x04, 0x40, 0xa1, 0x00, 0x01, 0x40, 0xa1, 0x00, 0x05, 0x40, 0xa1, 0x00, 0x02,
+ 0x40, 0xa1, 0x00, 0x06, 0x40, 0xa1, 0x00, 0x03, 0x40, 0xa1, 0x00, 0x07, 0x41, 0x82, 0x00, 0x00,
+ 0x41, 0x82, 0x00, 0x04, 0x41, 0x82, 0x00, 0x01, 0x41, 0x82, 0x00, 0x05, 0x41, 0x82, 0x00, 0x02,
+ 0x41, 0x82, 0x00, 0x06, 0x41, 0x82, 0x00, 0x03, 0x41, 0x82, 0x00, 0x07, 0x41, 0x82, 0x00, 0x00,
+ 0x41, 0x82, 0x00, 0x04, 0x41, 0x82, 0x00, 0x01, 0x41, 0x82, 0x00, 0x05, 0x41, 0x82, 0x00, 0x02,
+ 0x41, 0x82, 0x00, 0x06, 0x41, 0x82, 0x00, 0x03, 0x41, 0x82, 0x00, 0x07, 0x41, 0xa2, 0x00, 0x00,
+ 0x41, 0xa2, 0x00, 0x04, 0x41, 0xa2, 0x00, 0x01, 0x41, 0xa2, 0x00, 0x05, 0x41, 0xa2, 0x00, 0x02,
+ 0x41, 0xa2, 0x00, 0x06, 0x41, 0xa2, 0x00, 0x03, 0x41, 0xa2, 0x00, 0x07, 0x40, 0x80, 0x00, 0x00,
+ 0x40, 0x80, 0x00, 0x04, 0x40, 0x80, 0x00, 0x01, 0x40, 0x80, 0x00, 0x05, 0x40, 0x80, 0x00, 0x02,
+ 0x40, 0x80, 0x00, 0x06, 0x40, 0x80, 0x00, 0x03, 0x40, 0x80, 0x00, 0x07, 0x40, 0x80, 0x00, 0x00,
+ 0x40, 0x80, 0x00, 0x04, 0x40, 0x80, 0x00, 0x01, 0x40, 0x80, 0x00, 0x05, 0x40, 0x80, 0x00, 0x02,
+ 0x40, 0x80, 0x00, 0x06, 0x40, 0x80, 0x00, 0x03, 0x40, 0x80, 0x00, 0x07, 0x40, 0xa0, 0x00, 0x00,
+ 0x40, 0xa0, 0x00, 0x04, 0x40, 0xa0, 0x00, 0x01, 0x40, 0xa0, 0x00, 0x05, 0x40, 0xa0, 0x00, 0x02,
+ 0x40, 0xa0, 0x00, 0x06, 0x40, 0xa0, 0x00, 0x03, 0x40, 0xa0, 0x00, 0x07, 0x41, 0x81, 0x00, 0x00,
+ 0x41, 0x81, 0x00, 0x04, 0x41, 0x81, 0x00, 0x01, 0x41, 0x81, 0x00, 0x05, 0x41, 0x81, 0x00, 0x02,
+ 0x41, 0x81, 0x00, 0x06, 0x41, 0x81, 0x00, 0x03, 0x41, 0x81, 0x00, 0x07, 0x41, 0x81, 0x00, 0x00,
+ 0x41, 0x81, 0x00, 0x04, 0x41, 0x81, 0x00, 0x01, 0x41, 0x81, 0x00, 0x05, 0x41, 0x81, 0x00, 0x02,
+ 0x41, 0x81, 0x00, 0x06, 0x41, 0x81, 0x00, 0x03, 0x41, 0x81, 0x00, 0x07, 0x41, 0xa1, 0x00, 0x00,
+ 0x41, 0xa1, 0x00, 0x04, 0x41, 0xa1, 0x00, 0x01, 0x41, 0xa1, 0x00, 0x05, 0x41, 0xa1, 0x00, 0x02,
+ 0x41, 0xa1, 0x00, 0x06, 0x41, 0xa1, 0x00, 0x03, 0x41, 0xa1, 0x00, 0x07, 0x40, 0x80, 0x00, 0x00,
+ 0x40, 0x80, 0x00, 0x04, 0x40, 0x80, 0x00, 0x01, 0x40, 0x80, 0x00, 0x05, 0x40, 0x80, 0x00, 0x02,
+ 0x40, 0x80, 0x00, 0x06, 0x40, 0x80, 0x00, 0x03, 0x40, 0x80, 0x00, 0x07, 0x40, 0x80, 0x00, 0x00,
+ 0x40, 0x80, 0x00, 0x04, 0x40, 0x80, 0x00, 0x01, 0x40, 0x80, 0x00, 0x05, 0x40, 0x80, 0x00, 0x02,
+ 0x40, 0x80, 0x00, 0x06, 0x40, 0x80, 0x00, 0x03, 0x40, 0x80, 0x00, 0x07, 0x40, 0xa0, 0x00, 0x00,
+ 0x40, 0xa0, 0x00, 0x04, 0x40, 0xa0, 0x00, 0x01, 0x40, 0xa0, 0x00, 0x05, 0x40, 0xa0, 0x00, 0x02,
+ 0x40, 0xa0, 0x00, 0x06, 0x40, 0xa0, 0x00, 0x03, 0x40, 0xa0, 0x00, 0x07, 0x40, 0x82, 0x00, 0x00,
+ 0x40, 0x82, 0x00, 0x04, 0x40, 0x82, 0x00, 0x01, 0x40, 0x82, 0x00, 0x05, 0x40, 0x82, 0x00, 0x02,
+ 0x40, 0x82, 0x00, 0x06, 0x40, 0x82, 0x00, 0x03, 0x40, 0x82, 0x00, 0x07, 0x40, 0x82, 0x00, 0x00,
+ 0x40, 0x82, 0x00, 0x04, 0x40, 0x82, 0x00, 0x01, 0x40, 0x82, 0x00, 0x05, 0x40, 0x82, 0x00, 0x02,
+ 0x40, 0x82, 0x00, 0x06, 0x40, 0x82, 0x00, 0x03, 0x40, 0x82, 0x00, 0x07, 0x40, 0xa2, 0x00, 0x00,
+ 0x40, 0xa2, 0x00, 0x04, 0x40, 0xa2, 0x00, 0x01, 0x40, 0xa2, 0x00, 0x05, 0x40, 0xa2, 0x00, 0x02,
+ 0x40, 0xa2, 0x00, 0x06, 0x40, 0xa2, 0x00, 0x03, 0x40, 0xa2, 0x00, 0x07, 0x40, 0x81, 0x00, 0x00,
+ 0x40, 0x81, 0x00, 0x04, 0x40, 0x81, 0x00, 0x01, 0x40, 0x81, 0x00, 0x05, 0x40, 0x81, 0x00, 0x02,
+ 0x40, 0x81, 0x00, 0x06, 0x40, 0x81, 0x00, 0x03, 0x40, 0x81, 0x00, 0x07, 0x40, 0x81, 0x00, 0x00,
+ 0x40, 0x81, 0x00, 0x04, 0x40, 0x81, 0x00, 0x01, 0x40, 0x81, 0x00, 0x05, 0x40, 0x81, 0x00, 0x02,
+ 0x40, 0x81, 0x00, 0x06, 0x40, 0x81, 0x00, 0x03, 0x40, 0x81, 0x00, 0x07, 0x40, 0xa1, 0x00, 0x00,
+ 0x40, 0xa1, 0x00, 0x04, 0x40, 0xa1, 0x00, 0x01, 0x40, 0xa1, 0x00, 0x05, 0x40, 0xa1, 0x00, 0x02,
+ 0x40, 0xa1, 0x00, 0x06, 0x40, 0xa1, 0x00, 0x03, 0x40, 0xa1, 0x00, 0x07, 0x41, 0x83, 0x00, 0x00,
+ 0x41, 0x83, 0x00, 0x04, 0x41, 0x83, 0x00, 0x01, 0x41, 0x83, 0x00, 0x05, 0x41, 0x83, 0x00, 0x02,
+ 0x41, 0x83, 0x00, 0x06, 0x41, 0x83, 0x00, 0x03, 0x41, 0x83, 0x00, 0x07, 0x41, 0x83, 0x00, 0x00,
+ 0x41, 0x83, 0x00, 0x04, 0x41, 0x83, 0x00, 0x01, 0x41, 0x83, 0x00, 0x05, 0x41, 0x83, 0x00, 0x02,
+ 0x41, 0x83, 0x00, 0x06, 0x41, 0x83, 0x00, 0x03, 0x41, 0x83, 0x00, 0x07, 0x41, 0xa3, 0x00, 0x00,
+ 0x41, 0xa3, 0x00, 0x04, 0x41, 0xa3, 0x00, 0x01, 0x41, 0xa3, 0x00, 0x05, 0x41, 0xa3, 0x00, 0x02,
+ 0x41, 0xa3, 0x00, 0x06, 0x41, 0xa3, 0x00, 0x03, 0x41, 0xa3, 0x00, 0x07, 0x40, 0x83, 0x00, 0x00,
+ 0x40, 0x83, 0x00, 0x04, 0x40, 0x83, 0x00, 0x01, 0x40, 0x83, 0x00, 0x05, 0x40, 0x83, 0x00, 0x02,
+ 0x40, 0x83, 0x00, 0x06, 0x40, 0x83, 0x00, 0x03, 0x40, 0x83, 0x00, 0x07, 0x40, 0x83, 0x00, 0x00,
+ 0x40, 0x83, 0x00, 0x04, 0x40, 0x83, 0x00, 0x01, 0x40, 0x83, 0x00, 0x05, 0x40, 0x83, 0x00, 0x02,
+ 0x40, 0x83, 0x00, 0x06, 0x40, 0x83, 0x00, 0x03, 0x40, 0x83, 0x00, 0x07, 0x40, 0xa3, 0x00, 0x00,
+ 0x40, 0xa3, 0x00, 0x04, 0x40, 0xa3, 0x00, 0x01, 0x40, 0xa3, 0x00, 0x05, 0x40, 0xa3, 0x00, 0x02,
+ 0x40, 0xa3, 0x00, 0x06, 0x40, 0xa3, 0x00, 0x03, 0x40, 0xa3, 0x00, 0x07, 0x41, 0x83, 0x00, 0x00,
+ 0x41, 0x83, 0x00, 0x04, 0x41, 0x83, 0x00, 0x01, 0x41, 0x83, 0x00, 0x05, 0x41, 0x83, 0x00, 0x02,
+ 0x41, 0x83, 0x00, 0x06, 0x41, 0x83, 0x00, 0x03, 0x41, 0x83, 0x00, 0x07, 0x41, 0x83, 0x00, 0x00,
+ 0x41, 0x83, 0x00, 0x04, 0x41, 0x83, 0x00, 0x01, 0x41, 0x83, 0x00, 0x05, 0x41, 0x83, 0x00, 0x02,
+ 0x41, 0x83, 0x00, 0x06, 0x41, 0x83, 0x00, 0x03, 0x41, 0x83, 0x00, 0x07, 0x41, 0xa3, 0x00, 0x00,
+ 0x41, 0xa3, 0x00, 0x04, 0x41, 0xa3, 0x00, 0x01, 0x41, 0xa3, 0x00, 0x05, 0x41, 0xa3, 0x00, 0x02,
+ 0x41, 0xa3, 0x00, 0x06, 0x41, 0xa3, 0x00, 0x03, 0x41, 0xa3, 0x00, 0x07, 0x40, 0x83, 0x00, 0x00,
+ 0x40, 0x83, 0x00, 0x04, 0x40, 0x83, 0x00, 0x01, 0x40, 0x83, 0x00, 0x05, 0x40, 0x83, 0x00, 0x02,
+ 0x40, 0x83, 0x00, 0x06, 0x40, 0x83, 0x00, 0x03, 0x40, 0x83, 0x00, 0x07, 0x40, 0x83, 0x00, 0x00,
+ 0x40, 0x83, 0x00, 0x04, 0x40, 0x83, 0x00, 0x01, 0x40, 0x83, 0x00, 0x05, 0x40, 0x83, 0x00, 0x02,
+ 0x40, 0x83, 0x00, 0x06, 0x40, 0x83, 0x00, 0x03, 0x40, 0x83, 0x00, 0x07, 0x40, 0xa3, 0x00, 0x00,
+ 0x40, 0xa3, 0x00, 0x04, 0x40, 0xa3, 0x00, 0x01, 0x40, 0xa3, 0x00, 0x05, 0x40, 0xa3, 0x00, 0x02,
+ 0x40, 0xa3, 0x00, 0x06, 0x40, 0xa3, 0x00, 0x03, 0x40, 0xa3, 0x00, 0x07, 0x4e, 0x80, 0x00, 0x20,
+ 0x4e, 0x80, 0x00, 0x21, 0x4e, 0x80, 0x04, 0x20, 0x4e, 0x80, 0x04, 0x21, 0x4d, 0x80, 0x00, 0x20,
+ 0x4d, 0x80, 0x00, 0x21, 0x4d, 0x80, 0x00, 0x20, 0x4d, 0x80, 0x00, 0x21, 0x4d, 0xa0, 0x00, 0x20,
+ 0x4d, 0xa0, 0x00, 0x21, 0x4d, 0x80, 0x04, 0x20, 0x4d, 0x80, 0x04, 0x21, 0x4d, 0x80, 0x04, 0x20,
+ 0x4d, 0x80, 0x04, 0x21, 0x4d, 0xa0, 0x04, 0x20, 0x4d, 0xa0, 0x04, 0x21, 0x4c, 0x80, 0x00, 0x20,
+ 0x4c, 0x80, 0x00, 0x21, 0x4c, 0x80, 0x00, 0x20, 0x4c, 0x80, 0x00, 0x21, 0x4c, 0xa0, 0x00, 0x20,
+ 0x4c, 0xa0, 0x00, 0x21, 0x4c, 0x80, 0x04, 0x20, 0x4c, 0x80, 0x04, 0x21, 0x4c, 0x80, 0x04, 0x20,
+ 0x4c, 0x80, 0x04, 0x21, 0x4c, 0xa0, 0x04, 0x20, 0x4c, 0xa0, 0x04, 0x21, 0x4e, 0x00, 0x00, 0x20,
+ 0x4e, 0x00, 0x00, 0x21, 0x4e, 0x00, 0x00, 0x20, 0x4e, 0x00, 0x00, 0x21, 0x4e, 0x20, 0x00, 0x20,
+ 0x4e, 0x20, 0x00, 0x21, 0x4d, 0x00, 0x00, 0x20, 0x4d, 0x00, 0x00, 0x21, 0x4d, 0x00, 0x00, 0x20,
+ 0x4d, 0x00, 0x00, 0x21, 0x4d, 0x20, 0x00, 0x20, 0x4d, 0x20, 0x00, 0x21, 0x4c, 0x00, 0x00, 0x20,
+ 0x4c, 0x00, 0x00, 0x21, 0x4c, 0x00, 0x00, 0x20, 0x4c, 0x00, 0x00, 0x21, 0x4c, 0x20, 0x00, 0x20,
+ 0x4c, 0x20, 0x00, 0x21, 0x4e, 0x40, 0x00, 0x20, 0x4e, 0x40, 0x00, 0x21, 0x4e, 0x40, 0x00, 0x20,
+ 0x4e, 0x40, 0x00, 0x21, 0x4e, 0x60, 0x00, 0x20, 0x4e, 0x60, 0x00, 0x21, 0x4d, 0x40, 0x00, 0x20,
+ 0x4d, 0x40, 0x00, 0x21, 0x4d, 0x40, 0x00, 0x20, 0x4d, 0x40, 0x00, 0x21, 0x4d, 0x60, 0x00, 0x20,
+ 0x4d, 0x60, 0x00, 0x21, 0x4c, 0x40, 0x00, 0x20, 0x4c, 0x40, 0x00, 0x21, 0x4c, 0x40, 0x00, 0x20,
+ 0x4c, 0x40, 0x00, 0x21, 0x4c, 0x60, 0x00, 0x20, 0x4c, 0x60, 0x00, 0x21, 0x4d, 0x80, 0x00, 0x20,
+ 0x4d, 0x80, 0x00, 0x20, 0x4d, 0x80, 0x00, 0x21, 0x4d, 0x80, 0x00, 0x21, 0x4d, 0x80, 0x00, 0x20,
+ 0x4d, 0x80, 0x00, 0x20, 0x4d, 0x80, 0x00, 0x21, 0x4d, 0x80, 0x00, 0x21, 0x4d, 0xa0, 0x00, 0x20,
+ 0x4d, 0xa0, 0x00, 0x20, 0x4d, 0xa0, 0x00, 0x21, 0x4d, 0xa0, 0x00, 0x21, 0x4d, 0x80, 0x04, 0x20,
+ 0x4d, 0x80, 0x04, 0x20, 0x4d, 0x80, 0x04, 0x21, 0x4d, 0x80, 0x04, 0x21, 0x4d, 0x80, 0x04, 0x20,
+ 0x4d, 0x80, 0x04, 0x20, 0x4d, 0x80, 0x04, 0x21, 0x4d, 0x80, 0x04, 0x21, 0x4d, 0xa0, 0x04, 0x20,
+ 0x4d, 0xa0, 0x04, 0x20, 0x4d, 0xa0, 0x04, 0x21, 0x4d, 0xa0, 0x04, 0x21, 0x4c, 0x81, 0x00, 0x20,
+ 0x4c, 0x81, 0x00, 0x20, 0x4c, 0x81, 0x00, 0x21, 0x4c, 0x81, 0x00, 0x21, 0x4c, 0x81, 0x00, 0x20,
+ 0x4c, 0x81, 0x00, 0x20, 0x4c, 0x81, 0x00, 0x21, 0x4c, 0x81, 0x00, 0x21, 0x4c, 0xa1, 0x00, 0x20,
+ 0x4c, 0xa1, 0x00, 0x20, 0x4c, 0xa1, 0x00, 0x21, 0x4c, 0xa1, 0x00, 0x21, 0x4c, 0x81, 0x04, 0x20,
+ 0x4c, 0x81, 0x04, 0x20, 0x4c, 0x81, 0x04, 0x21, 0x4c, 0x81, 0x04, 0x21, 0x4c, 0x81, 0x04, 0x20,
+ 0x4c, 0x81, 0x04, 0x20, 0x4c, 0x81, 0x04, 0x21, 0x4c, 0x81, 0x04, 0x21, 0x4c, 0xa1, 0x04, 0x20,
+ 0x4c, 0xa1, 0x04, 0x20, 0x4c, 0xa1, 0x04, 0x21, 0x4c, 0xa1, 0x04, 0x21, 0x4d, 0x82, 0x00, 0x20,
+ 0x4d, 0x82, 0x00, 0x20, 0x4d, 0x82, 0x00, 0x21, 0x4d, 0x82, 0x00, 0x21, 0x4d, 0x82, 0x00, 0x20,
+ 0x4d, 0x82, 0x00, 0x20, 0x4d, 0x82, 0x00, 0x21, 0x4d, 0x82, 0x00, 0x21, 0x4d, 0xa2, 0x00, 0x20,
+ 0x4d, 0xa2, 0x00, 0x20, 0x4d, 0xa2, 0x00, 0x21, 0x4d, 0xa2, 0x00, 0x21, 0x4d, 0x82, 0x04, 0x20,
+ 0x4d, 0x82, 0x04, 0x20, 0x4d, 0x82, 0x04, 0x21, 0x4d, 0x82, 0x04, 0x21, 0x4d, 0x82, 0x04, 0x20,
+ 0x4d, 0x82, 0x04, 0x20, 0x4d, 0x82, 0x04, 0x21, 0x4d, 0x82, 0x04, 0x21, 0x4d, 0xa2, 0x04, 0x20,
+ 0x4d, 0xa2, 0x04, 0x20, 0x4d, 0xa2, 0x04, 0x21, 0x4d, 0xa2, 0x04, 0x21, 0x4c, 0x80, 0x00, 0x20,
+ 0x4c, 0x80, 0x00, 0x20, 0x4c, 0x80, 0x00, 0x21, 0x4c, 0x80, 0x00, 0x21, 0x4c, 0x80, 0x00, 0x20,
+ 0x4c, 0x80, 0x00, 0x20, 0x4c, 0x80, 0x00, 0x21, 0x4c, 0x80, 0x00, 0x21, 0x4c, 0xa0, 0x00, 0x20,
+ 0x4c, 0xa0, 0x00, 0x20, 0x4c, 0xa0, 0x00, 0x21, 0x4c, 0xa0, 0x00, 0x21, 0x4c, 0x80, 0x04, 0x20,
+ 0x4c, 0x80, 0x04, 0x20, 0x4c, 0x80, 0x04, 0x21, 0x4c, 0x80, 0x04, 0x21, 0x4c, 0x80, 0x04, 0x20,
+ 0x4c, 0x80, 0x04, 0x20, 0x4c, 0x80, 0x04, 0x21, 0x4c, 0x80, 0x04, 0x21, 0x4c, 0xa0, 0x04, 0x20,
+ 0x4c, 0xa0, 0x04, 0x20, 0x4c, 0xa0, 0x04, 0x21, 0x4c, 0xa0, 0x04, 0x21, 0x4d, 0x81, 0x00, 0x20,
+ 0x4d, 0x81, 0x00, 0x20, 0x4d, 0x81, 0x00, 0x21, 0x4d, 0x81, 0x00, 0x21, 0x4d, 0x81, 0x00, 0x20,
+ 0x4d, 0x81, 0x00, 0x20, 0x4d, 0x81, 0x00, 0x21, 0x4d, 0x81, 0x00, 0x21, 0x4d, 0xa1, 0x00, 0x20,
+ 0x4d, 0xa1, 0x00, 0x20, 0x4d, 0xa1, 0x00, 0x21, 0x4d, 0xa1, 0x00, 0x21, 0x4d, 0x81, 0x04, 0x20,
+ 0x4d, 0x81, 0x04, 0x20, 0x4d, 0x81, 0x04, 0x21, 0x4d, 0x81, 0x04, 0x21, 0x4d, 0x81, 0x04, 0x20,
+ 0x4d, 0x81, 0x04, 0x20, 0x4d, 0x81, 0x04, 0x21, 0x4d, 0x81, 0x04, 0x21, 0x4d, 0xa1, 0x04, 0x20,
+ 0x4d, 0xa1, 0x04, 0x20, 0x4d, 0xa1, 0x04, 0x21, 0x4d, 0xa1, 0x04, 0x21, 0x4c, 0x80, 0x00, 0x20,
+ 0x4c, 0x80, 0x00, 0x20, 0x4c, 0x80, 0x00, 0x21, 0x4c, 0x80, 0x00, 0x21, 0x4c, 0x80, 0x00, 0x20,
+ 0x4c, 0x80, 0x00, 0x20, 0x4c, 0x80, 0x00, 0x21, 0x4c, 0x80, 0x00, 0x21, 0x4c, 0xa0, 0x00, 0x20,
+ 0x4c, 0xa0, 0x00, 0x20, 0x4c, 0xa0, 0x00, 0x21, 0x4c, 0xa0, 0x00, 0x21, 0x4c, 0x80, 0x04, 0x20,
+ 0x4c, 0x80, 0x04, 0x20, 0x4c, 0x80, 0x04, 0x21, 0x4c, 0x80, 0x04, 0x21, 0x4c, 0x80, 0x04, 0x20,
+ 0x4c, 0x80, 0x04, 0x20, 0x4c, 0x80, 0x04, 0x21, 0x4c, 0x80, 0x04, 0x21, 0x4c, 0xa0, 0x04, 0x20,
+ 0x4c, 0xa0, 0x04, 0x20, 0x4c, 0xa0, 0x04, 0x21, 0x4c, 0xa0, 0x04, 0x21, 0x4c, 0x82, 0x00, 0x20,
+ 0x4c, 0x82, 0x00, 0x20, 0x4c, 0x82, 0x00, 0x21, 0x4c, 0x82, 0x00, 0x21, 0x4c, 0x82, 0x00, 0x20,
+ 0x4c, 0x82, 0x00, 0x20, 0x4c, 0x82, 0x00, 0x21, 0x4c, 0x82, 0x00, 0x21, 0x4c, 0xa2, 0x00, 0x20,
+ 0x4c, 0xa2, 0x00, 0x20, 0x4c, 0xa2, 0x00, 0x21, 0x4c, 0xa2, 0x00, 0x21, 0x4c, 0x82, 0x04, 0x20,
+ 0x4c, 0x82, 0x04, 0x20, 0x4c, 0x82, 0x04, 0x21, 0x4c, 0x82, 0x04, 0x21, 0x4c, 0x82, 0x04, 0x20,
+ 0x4c, 0x82, 0x04, 0x20, 0x4c, 0x82, 0x04, 0x21, 0x4c, 0x82, 0x04, 0x21, 0x4c, 0xa2, 0x04, 0x20,
+ 0x4c, 0xa2, 0x04, 0x20, 0x4c, 0xa2, 0x04, 0x21, 0x4c, 0xa2, 0x04, 0x21, 0x4c, 0x81, 0x00, 0x20,
+ 0x4c, 0x81, 0x00, 0x20, 0x4c, 0x81, 0x00, 0x21, 0x4c, 0x81, 0x00, 0x21, 0x4c, 0x81, 0x00, 0x20,
+ 0x4c, 0x81, 0x00, 0x20, 0x4c, 0x81, 0x00, 0x21, 0x4c, 0x81, 0x00, 0x21, 0x4c, 0xa1, 0x00, 0x20,
+ 0x4c, 0xa1, 0x00, 0x20, 0x4c, 0xa1, 0x00, 0x21, 0x4c, 0xa1, 0x00, 0x21, 0x4c, 0x81, 0x04, 0x20,
+ 0x4c, 0x81, 0x04, 0x20, 0x4c, 0x81, 0x04, 0x21, 0x4c, 0x81, 0x04, 0x21, 0x4c, 0x81, 0x04, 0x20,
+ 0x4c, 0x81, 0x04, 0x20, 0x4c, 0x81, 0x04, 0x21, 0x4c, 0x81, 0x04, 0x21, 0x4c, 0xa1, 0x04, 0x20,
+ 0x4c, 0xa1, 0x04, 0x20, 0x4c, 0xa1, 0x04, 0x21, 0x4c, 0xa1, 0x04, 0x21, 0x4d, 0x83, 0x00, 0x20,
+ 0x4d, 0x83, 0x00, 0x20, 0x4d, 0x83, 0x00, 0x21, 0x4d, 0x83, 0x00, 0x21, 0x4d, 0x83, 0x00, 0x20,
+ 0x4d, 0x83, 0x00, 0x20, 0x4d, 0x83, 0x00, 0x21, 0x4d, 0x83, 0x00, 0x21, 0x4d, 0xa3, 0x00, 0x20,
+ 0x4d, 0xa3, 0x00, 0x20, 0x4d, 0xa3, 0x00, 0x21, 0x4d, 0xa3, 0x00, 0x21, 0x4d, 0x83, 0x04, 0x20,
+ 0x4d, 0x83, 0x04, 0x20, 0x4d, 0x83, 0x04, 0x21, 0x4d, 0x83, 0x04, 0x21, 0x4d, 0x83, 0x04, 0x20,
+ 0x4d, 0x83, 0x04, 0x20, 0x4d, 0x83, 0x04, 0x21, 0x4d, 0x83, 0x04, 0x21, 0x4d, 0xa3, 0x04, 0x20,
+ 0x4d, 0xa3, 0x04, 0x20, 0x4d, 0xa3, 0x04, 0x21, 0x4d, 0xa3, 0x04, 0x21, 0x4c, 0x83, 0x00, 0x20,
+ 0x4c, 0x83, 0x00, 0x20, 0x4c, 0x83, 0x00, 0x21, 0x4c, 0x83, 0x00, 0x21, 0x4c, 0x83, 0x00, 0x20,
+ 0x4c, 0x83, 0x00, 0x20, 0x4c, 0x83, 0x00, 0x21, 0x4c, 0x83, 0x00, 0x21, 0x4c, 0xa3, 0x00, 0x20,
+ 0x4c, 0xa3, 0x00, 0x20, 0x4c, 0xa3, 0x00, 0x21, 0x4c, 0xa3, 0x00, 0x21, 0x4c, 0x83, 0x04, 0x20,
+ 0x4c, 0x83, 0x04, 0x20, 0x4c, 0x83, 0x04, 0x21, 0x4c, 0x83, 0x04, 0x21, 0x4c, 0x83, 0x04, 0x20,
+ 0x4c, 0x83, 0x04, 0x20, 0x4c, 0x83, 0x04, 0x21, 0x4c, 0x83, 0x04, 0x21, 0x4c, 0xa3, 0x04, 0x20,
+ 0x4c, 0xa3, 0x04, 0x20, 0x4c, 0xa3, 0x04, 0x21, 0x4c, 0xa3, 0x04, 0x21, 0x4d, 0x83, 0x00, 0x20,
+ 0x4d, 0x83, 0x00, 0x20, 0x4d, 0x83, 0x00, 0x21, 0x4d, 0x83, 0x00, 0x21, 0x4d, 0x83, 0x00, 0x20,
+ 0x4d, 0x83, 0x00, 0x20, 0x4d, 0x83, 0x00, 0x21, 0x4d, 0x83, 0x00, 0x21, 0x4d, 0xa3, 0x00, 0x20,
+ 0x4d, 0xa3, 0x00, 0x20, 0x4d, 0xa3, 0x00, 0x21, 0x4d, 0xa3, 0x00, 0x21, 0x4d, 0x83, 0x04, 0x20,
+ 0x4d, 0x83, 0x04, 0x20, 0x4d, 0x83, 0x04, 0x21, 0x4d, 0x83, 0x04, 0x21, 0x4d, 0x83, 0x04, 0x20,
+ 0x4d, 0x83, 0x04, 0x20, 0x4d, 0x83, 0x04, 0x21, 0x4d, 0x83, 0x04, 0x21, 0x4d, 0xa3, 0x04, 0x20,
+ 0x4d, 0xa3, 0x04, 0x20, 0x4d, 0xa3, 0x04, 0x21, 0x4d, 0xa3, 0x04, 0x21, 0x4c, 0x83, 0x00, 0x20,
+ 0x4c, 0x83, 0x00, 0x20, 0x4c, 0x83, 0x00, 0x21, 0x4c, 0x83, 0x00, 0x21, 0x4c, 0x83, 0x00, 0x20,
+ 0x4c, 0x83, 0x00, 0x20, 0x4c, 0x83, 0x00, 0x21, 0x4c, 0x83, 0x00, 0x21, 0x4c, 0xa3, 0x00, 0x20,
+ 0x4c, 0xa3, 0x00, 0x20, 0x4c, 0xa3, 0x00, 0x21, 0x4c, 0xa3, 0x00, 0x21, 0x4c, 0x83, 0x04, 0x20,
+ 0x4c, 0x83, 0x04, 0x20, 0x4c, 0x83, 0x04, 0x21, 0x4c, 0x83, 0x04, 0x21, 0x4c, 0x83, 0x04, 0x20,
+ 0x4c, 0x83, 0x04, 0x20, 0x4c, 0x83, 0x04, 0x21, 0x4c, 0x83, 0x04, 0x21, 0x4c, 0xa3, 0x04, 0x20,
+ 0x4c, 0xa3, 0x04, 0x20, 0x4c, 0xa3, 0x04, 0x21, 0x4c, 0xa3, 0x04, 0x21,
+};
+
+TEST(Assembler, AllExtendedInstructions)
+{
+ auto res = Assemble(extended_instructions, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(extended_expect));
+
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], extended_expect[i]) << "->i=" << i;
+ }
+}
+
+TEST(Assembler, ByteDirectivesSimple)
+{
+ constexpr char assembly[] = ".byte 0\n"
+ ".Byte 0xff\n"
+ ".bYte 0x100\n"
+ ".2bYTe 0\n"
+ ".2bytE 0xff\n"
+ ".2BYte 0x100\n"
+ ".2bYTe 0xffff\n"
+ ".2byTE 0x10000\n"
+ ".4BytE 0\n"
+ ".4BYTe 0xff\n"
+ ".4bYTE 0x100\n"
+ ".4ByTE 0xffff\n"
+ ".4BYtE 0x10000\n"
+ ".4BYTE 0xffffffff\n"
+ ".4ByTe 0x100000000\n"
+ ".8bYtE 0\n"
+ ".8byte 0xff\n"
+ ".8byte 0x100\n"
+ ".8byte 0xffff\n"
+ ".8byte 0x10000\n"
+ ".8byte 0xffffffff\n"
+ ".8byte 0x100000000\n"
+ ".8byte 0xffffffffffffffff\n"
+ ".8byte 0x10000000000000000\n";
+ constexpr u8 expect[] = {
+ 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00,
+ 0x01, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+
+ auto res = Assemble(assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(expect));
+
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], expect[i]) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, MultiOperandDirectives)
+{
+ constexpr char assembly[] = ".byte 0, 1, 2\n"
+ ".2byte 3, 4, 5\n"
+ ".4byte 6, 7, 8\n"
+ ".8byte 9, 10, 11\n";
+ constexpr u8 expect[] = {
+ 0, 1, 2, 0, 3, 0, 4, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0,
+ 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 11,
+ };
+ auto res = Assemble(assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(expect));
+
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], expect[i]) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, OperandExpressionDirectives)
+{
+ constexpr char assembly[] = ".byte 0 + 1, 1 * 4, 2 * 8\n"
+ ".2byte 3*6*9, 5*5*12, 81/9\n"
+ ".4byte 1<<12, 5>>3, 8^8\n"
+ ".8byte 0b1010 & 0b1101, 0b1010 | 0b0101, 0x12 + 010\n";
+ constexpr u8 expect[] = {
+ 1, 4, 16, 0, 162, 1, 44, 0, 9, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0x12 + 010,
+ };
+ auto res = Assemble(assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(expect));
+
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], expect[i]) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, FloatDirectives)
+{
+ constexpr char assembly[] = ".float 0\n"
+ ".float 1, 2, 3.0\n"
+ ".float 1.25, 1.5e6, -2e-5\n"
+ ".double 0\n"
+ ".double 1, 2, 3.0\n"
+ ".double 1.0000001, 0.0000025, .000006e9\n";
+ constexpr u8 expect[] = {
+ 0, 0, 0, 0, 0x3f, 0x80, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x40,
+ 0x00, 0x00, 0x3f, 0xa0, 0x00, 0x00, 0x49, 0xb7, 0x1b, 0x00, 0xb7, 0xa7, 0xc5, 0xac,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x1a, 0xd7, 0xf2, 0x9b, 0x3e, 0xc4,
+ 0xf8, 0xb5, 0x88, 0xe3, 0x68, 0xf1, 0x40, 0xb7, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ auto res = Assemble(assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(expect));
+
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], expect[i]) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, ZeroDirectives)
+{
+ constexpr char assembly[] = ".zeros 0\n"
+ ".zeros 1\n"
+ ".zeros 5 + 5\n";
+ constexpr u8 expect[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ auto res = Assemble(assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(expect));
+
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], expect[i]) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, StringDirectives)
+{
+ constexpr char assembly[] = ".ascii \"test string\"\n"
+ ".ascii \"string with \\n escapes \\r\"\n"
+ ".ascii \"string with octals \\123 \\0\"\n"
+ ".ascii \"string with hex \\x12\\x45\\x9912\"\n"
+ ".asciz \"null terminator\"\n";
+ constexpr u8 expect[] = {
+ 't', 'e', 's', 't', ' ', 's', 't', 'r', 'i', 'n', 'g', 's', 't', 'r', 'i', 'n',
+ 'g', ' ', 'w', 'i', 't', 'h', ' ', '\n', ' ', 'e', 's', 'c', 'a', 'p', 'e', 's',
+ ' ', '\r', 's', 't', 'r', 'i', 'n', 'g', ' ', 'w', 'i', 't', 'h', ' ', 'o', 'c',
+ 't', 'a', 'l', 's', ' ', '\123', ' ', '\0', 's', 't', 'r', 'i', 'n', 'g', ' ', 'w',
+ 'i', 't', 'h', ' ', 'h', 'e', 'x', ' ', '\x12', '\x45', '\x12', 'n', 'u', 'l', 'l', ' ',
+ 't', 'e', 'r', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0'};
+ auto res = Assemble(assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(expect));
+
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], expect[i]) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, RelocateDirective)
+{
+ constexpr char assembly[] = ".zeros 5\n"
+ ".locate 100\n"
+ ".zeros 9\n"
+ ".locate 110\n"
+ ".zeros 10\n"
+ ".locate 120\n"
+ ".zeros 29\n"
+ ".locate 120 + 5*5+4 + 1\n"
+ ".zeros 1\n";
+ constexpr u32 expect_addr[] = {0, 100, 110, 120, 150};
+ constexpr size_t expect_size[] = {5, 9, 10, 29, 1};
+
+ auto res = Assemble(assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), sizeof(expect_size) / sizeof(expect_size[0]));
+ for (size_t i = 0; i < code_blocks.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[i].instructions.size(), expect_size[i]) << " -> i=" << i;
+ EXPECT_EQ(code_blocks[i].block_address, expect_addr[i]) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, AlignmentDirectives)
+{
+ constexpr char assembly_align[] = ".zeros 1\n"
+ ".align 0\n"
+ ".zeros 1\n"
+ ".align 1\n"
+ ".zeros 1\n"
+ ".align 2\n"
+ ".zeros 1\n"
+ ".align 4\n"
+ ".zeros 1\n"
+ ".align 4\n"
+ ".zeros 1\n"
+ ".align 10\n"
+ ".byte 1\n"
+ ".padalign 0\n"
+ ".byte 1\n"
+ ".padalign 1\n"
+ ".byte 1\n"
+ ".padalign 2\n"
+ ".byte 1\n"
+ ".padalign 4\n"
+ ".byte 1\n"
+ ".padalign 4\n"
+ ".byte 1\n"
+ ".padalign 10\n";
+ constexpr u32 expect_addr[] = {0, 4, 16, 32, 1024};
+
+ auto res = Assemble(assembly_align, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), sizeof(expect_addr) / sizeof(expect_addr[0]));
+ for (size_t i = 0; i < code_blocks.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[i].block_address, expect_addr[i]) << " -> i=" << i;
+ }
+
+ auto&& last_block = code_blocks.back().instructions;
+ ASSERT_EQ(last_block.size(), 1024);
+ for (size_t i = 0; i < 3; i++)
+ {
+ EXPECT_EQ(last_block[i], 1) << " -> i=" << i;
+ }
+ EXPECT_EQ(last_block[3], 0) << " -> i=4";
+ EXPECT_EQ(last_block[4], 1) << " -> i=4";
+ for (size_t i = 5; i < 16; i++)
+ {
+ EXPECT_EQ(last_block[i], 0) << " -> i=" << i;
+ }
+ EXPECT_EQ(last_block[16], 1) << " -> i=16";
+ for (size_t i = 17; i < 32; i++)
+ {
+ EXPECT_EQ(last_block[i], 0) << " -> i=" << i;
+ }
+ EXPECT_EQ(last_block[32], 1) << " -> i=32";
+ for (size_t i = 33; i < last_block.size(); i++)
+ {
+ EXPECT_EQ(last_block[i], 0) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, SkipDirective)
+{
+ constexpr char assembly_align[] = ".byte 5\n"
+ ".skip 0\n"
+ ".byte 6\n"
+ ".skip 1\n"
+ ".byte 7\n"
+ ".skip 10 * 10\n"
+ ".byte 8\n";
+ constexpr u32 expect_addr[] = {0, 3, 104};
+
+ auto res = Assemble(assembly_align, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), sizeof(expect_addr) / sizeof(expect_addr[0]));
+
+ EXPECT_EQ(code_blocks[0].block_address, expect_addr[0]) << " -> i=0";
+ ASSERT_EQ(code_blocks[0].instructions.size(), 2);
+ EXPECT_EQ(code_blocks[0].instructions[0], 5) << " -> i=0";
+ EXPECT_EQ(code_blocks[0].instructions[1], 6) << " -> i=0";
+
+ EXPECT_EQ(code_blocks[1].block_address, expect_addr[1]) << " -> i=1";
+ ASSERT_EQ(code_blocks[1].instructions.size(), 1);
+ EXPECT_EQ(code_blocks[1].instructions[0], 7) << " -> i=1";
+
+ EXPECT_EQ(code_blocks[2].block_address, expect_addr[2]) << " -> i=2";
+ ASSERT_EQ(code_blocks[2].instructions.size(), 1);
+ EXPECT_EQ(code_blocks[2].instructions[0], 8) << " -> i=2";
+}
+
+TEST(Assembler, DefvarDirective)
+{
+ constexpr char assembly[] = ".defvar NewVar, 0\n"
+ ".defvar NewVar2, 123\n"
+ ".defvar __Name, 1*2+3+4\n"
+ ".defvar AB_cd00, 5*5+__Name\n"
+ ".2byte NewVar\n"
+ ".2byte NewVar2\n"
+ ".4byte __Name\n"
+ ".4byte AB_cd00\n"
+ ".4byte AB_cd00 + NewVar2\n";
+ constexpr u8 expect[] = {
+ 0, 0, 0, 123, 0, 0, 0, 9, 0, 0, 0, 34, 0, 0, 0, 157,
+ };
+
+ auto res = Assemble(assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], expect[i]) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, VariousOperandExpressions)
+{
+ constexpr char assembly[] = ".locate 0x400\n"
+ "b .\n"
+ "b .\n"
+ ".locate 0x800\n"
+ "post_locate:\n"
+ "b `0x900`\n"
+ "b `0x800`\n"
+ "b `. + 0x10`\n"
+ "b post_locate\n"
+ "lis r0, post_locate_2@ha\n"
+ "ori r0, r0, post_locate_2@l\n"
+ "li r0, TestValue\n"
+ ".defvar TestValue, 1234\n"
+ "li r0, TestValue\n"
+ ".locate 0x80001234\n"
+ "post_locate_2:\n";
+ constexpr u8 expect_0[] = {
+ 0x48, 0x00, 0x04, 0x00, 0x48, 0x00, 0x04, 0x04,
+ };
+ constexpr u8 expect_1[] = {
+ 0x48, 0x00, 0x01, 0x00, 0x4b, 0xff, 0xff, 0xfc, 0x48, 0x00, 0x00,
+ 0x10, 0x4b, 0xff, 0xff, 0xf4, 0x3c, 0x00, 0x80, 0x00, 0x60, 0x00,
+ 0x12, 0x34, 0x38, 0x00, 0x04, 0xd2, 0x38, 0x00, 0x04, 0xd2,
+ };
+
+ auto res = Assemble(assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 2);
+
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(expect_0));
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], expect_0[i]) << " -> i=" << i;
+ }
+
+ ASSERT_EQ(code_blocks[1].instructions.size(), sizeof(expect_1));
+ for (size_t i = 0; i < code_blocks[1].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[1].instructions[i], expect_1[i]) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, AbsRel)
+{
+ constexpr char assembly[] = ".locate 0x80001234\n"
+ "lbl0:\n"
+ ".defvar abs_loc, lbl0\n"
+ ".4byte lbl0\n"
+ ".2byte lbl0@ha\n"
+ ".2byte lbl0@l\n"
+ ".4byte .\n"
+ "b lbl0\n"
+ "b `abs_loc`\n";
+ constexpr u8 expect[] = {
+ 0x80, 0x00, 0x12, 0x34, 0x80, 0x00, 0x12, 0x34, 0x80, 0x00,
+ 0x12, 0x3c, 0x4b, 0xff, 0xff, 0xf4, 0x4b, 0xff, 0xff, 0xf0,
+ };
+
+ auto res = Assemble(assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(expect));
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], expect[i]) << " -> i=" << i;
+ }
+}
+
+TEST(Assembler, BadTokens)
+{
+ constexpr char unterminated_str[] = ".ascii \"no terminator";
+ constexpr char bad_hex_in_str[] = ".ascii \"\\xnot hex\"";
+ constexpr char newline_in_str[] = ".ascii \"abc\nd\"";
+ constexpr char bad_float_0[] = ".float";
+ constexpr char bad_float_1[] = ".float 1.";
+ constexpr char bad_float_2[] = ".float .";
+ constexpr char bad_float_3[] = ".float -.5e";
+ constexpr char bad_float_4[] = ".float -.6e+";
+
+ EXPECT_TRUE(IsFailure(Assemble(unterminated_str, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(bad_hex_in_str, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(newline_in_str, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(bad_float_0, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(bad_float_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(bad_float_2, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(bad_float_3, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(bad_float_4, 0)));
+}
+
+TEST(Assembler, RangeTest)
+{
+ constexpr char gpr_range_0[] = "mr r3, -1";
+ constexpr char gpr_range_1[] = "mr r3, 0";
+ constexpr char gpr_range_2[] = "mr r3, 32";
+ constexpr char gpr_range_3[] = "mr r3, 31";
+ constexpr char crf_range_0[] = "cmpw -1, 0, 0";
+ constexpr char crf_range_1[] = "cmpw 0, 0, 0";
+ constexpr char crf_range_2[] = "cmpw 8, 0, 0";
+ constexpr char crf_range_3[] = "cmpw 7, 0, 0";
+ constexpr char bc_range_0[] = "beq 1 << 15";
+ constexpr char bc_range_1[] = "beq (1 << 15) - 4";
+ constexpr char bc_range_2[] = "beq -(1 << 15) - 4";
+ constexpr char bc_range_3[] = "beq -(1 << 15)";
+ constexpr char b_range_0[] = "b 1 << 25";
+ constexpr char b_range_1[] = "b (1 << 25) - 4";
+ constexpr char b_range_2[] = "b -(1 << 25) - 4";
+ constexpr char b_range_3[] = "b -(1 << 25)";
+ constexpr char crb_range_0[] = "cror -1, -1, -1";
+ constexpr char crb_range_1[] = "cror 0, 0, 0";
+ constexpr char crb_range_2[] = "cror 32, 32, 32";
+ constexpr char crb_range_3[] = "cror 31, 31, 31";
+ constexpr char off_range_0[] = "lwz r0, 1 << 15(r3)";
+ constexpr char off_range_1[] = "lwz r0, (1 << 15) - 1(r3)";
+ constexpr char off_range_2[] = "lwz r0, -(1 << 15) - 1(r3)";
+ constexpr char off_range_3[] = "lwz r0, -(1 << 15)(r3)";
+ constexpr char psoff_range_0[] = "psq_l f0, 1 << 11(r3), 0, 0";
+ constexpr char psoff_range_1[] = "psq_l f0, (1 << 11) - 1(r3), 0, 0";
+ constexpr char psoff_range_2[] = "psq_l f0, -(1 << 11) - 1(r3), 0, 0";
+ constexpr char psoff_range_3[] = "psq_l f0, -(1 << 11)(r3), 0, 0";
+ constexpr char simm_range_0[] = "addi r0, r1, 0x8000";
+ constexpr char simm_range_1[] = "addi r0, r1, 0x7fff";
+ constexpr char simm_range_2[] = "addi r0, r1, -0x8001";
+ constexpr char simm_range_3[] = "addi r0, r1, -0x8000";
+ constexpr char uimm_range_0[] = "andi. r0, r1, 0x10000";
+ constexpr char uimm_range_1[] = "andi. r0, r1, 0xffff";
+ constexpr char uimm_range_2[] = "andi. r0, r1, -1";
+ constexpr char uimm_range_3[] = "andi. r0, r1, 0";
+
+ EXPECT_TRUE(IsFailure(Assemble(gpr_range_0, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(gpr_range_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(gpr_range_2, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(gpr_range_3, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(crf_range_0, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(crf_range_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(crf_range_2, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(crf_range_3, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(bc_range_0, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(bc_range_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(bc_range_2, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(bc_range_3, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(b_range_0, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(b_range_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(b_range_2, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(b_range_3, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(crb_range_0, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(crb_range_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(crb_range_2, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(crb_range_3, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(off_range_0, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(off_range_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(off_range_2, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(off_range_3, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(psoff_range_0, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(psoff_range_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(psoff_range_2, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(psoff_range_3, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(psoff_range_0, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(psoff_range_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(psoff_range_2, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(psoff_range_3, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(simm_range_0, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(simm_range_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(simm_range_2, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(simm_range_3, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(uimm_range_0, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(uimm_range_1, 0)));
+ EXPECT_TRUE(IsFailure(Assemble(uimm_range_2, 0)));
+ EXPECT_TRUE(!IsFailure(Assemble(uimm_range_3, 0)));
+}
+
+TEST(Assembly, MalformedExpressions)
+{
+ constexpr char missing_arg[] = "add 0, 1";
+ constexpr char missing_paren_0[] = ".4byte (1 + 2), ((3 * 6) + 7";
+ constexpr char missing_paren_1[] = ".4byte (1 + 2), `(3 * 6) + 7";
+ constexpr char mismatched_paren[] = ".4byte (1 + 2), (`3 * 6) + 7`";
+ constexpr char wrong_arg_format[] = "lwz r3, 100, r4";
+ constexpr char no_operator[] = "b . .";
+ constexpr char no_operand[] = "b 4 + +";
+
+ auto res = Assemble(missing_arg, 0);
+ EXPECT_TRUE(IsFailure(res) && GetFailure(res).message == "Expected ',' but found '<EOF>'")
+ << GetFailure(res).message;
+ res = Assemble(missing_paren_0, 0);
+ EXPECT_TRUE(IsFailure(res) && GetFailure(res).message == "Expected ')' but found '<EOF>'")
+ << GetFailure(res).message;
+ res = Assemble(missing_paren_1, 0);
+ EXPECT_TRUE(IsFailure(res) && GetFailure(res).message == "Expected '`' but found '<EOF>'")
+ << GetFailure(res).message;
+ res = Assemble(mismatched_paren, 0);
+ EXPECT_TRUE(IsFailure(res) && GetFailure(res).message == "Expected '`' but found ')'")
+ << GetFailure(res).message;
+ res = Assemble(wrong_arg_format, 0);
+ EXPECT_TRUE(IsFailure(res) && GetFailure(res).message == "Expected '(' but found ','")
+ << GetFailure(res).message;
+ res = Assemble(no_operator, 0);
+ EXPECT_TRUE(IsFailure(res) &&
+ GetFailure(res).message == "Unexpected token '.' where line should have ended")
+ << GetFailure(res).message;
+ res = Assemble(no_operand, 0);
+ EXPECT_TRUE(IsFailure(res) && GetFailure(res).message == "Unexpected token '+' in expression")
+ << GetFailure(res).message;
+}
+
+// Modified listing of a subroutine, listing generated by IDA
+// Expect bytes are based on disc contents
+TEST(Assembly, RealAssembly)
+{
+ constexpr char real_assembly[] = ".locate 0x8046A690\n"
+ ".defvar back_chain, -0x30\n"
+ ".defvar var_28, -0x28\n"
+ ".defvar pre_back_chain, 0\n"
+ ".defvar sender_lr, 4\n"
+ "stwu r1, back_chain(r1)\n"
+ "mfspr r0, LR\n"
+ "stw r0, 0x30+sender_lr(r1)\n"
+ "addi r11, r1, 0x30+pre_back_chain\n"
+ "bl `0x802BCA84`\n"
+ "li r0, 0\n"
+ "mr r28, r7\n"
+ "stw r0, 0(r8)\n"
+ "mr r24, r3\n"
+ "mr r25, r4\n"
+ "mr r26, r5\n"
+ "mr r27, r6\n"
+ "mr r29, r8\n"
+ "mr r30, r9\n"
+ "mr r31, r10\n"
+ "mr r3, r28\n"
+ "bl `0x80468140`\n"
+ "cmplwi r3, 0x1A\n"
+ "bge loc_8046A6F0\n"
+ "mulli r0, r3, 0x14\n"
+ "lis r3, -0x7FA4\n"
+ "addi r3, r3, 0x870\n"
+ "add r3, r3, r0\n"
+ "b loc_8046A6F4\n"
+ "loc_8046A6F0:\n"
+ "li r3, 0\n"
+ "loc_8046A6F4:\n"
+ "cmpwi r3, 0\n"
+ "beq loc_8046A704\n"
+ "lwz r0, 0xC(r3)\n"
+ "b loc_8046A708\n"
+ "loc_8046A704:\n"
+ "li r0, 0\n"
+ "loc_8046A708:\n"
+ "cmplw r26, r0\n"
+ "bge loc_8046A7EC\n"
+ "cmpwi r26, 0\n"
+ "bne loc_8046A758\n"
+ "mr r12, r30\n"
+ "mr r3, r28\n"
+ "mr r4, r25\n"
+ "addi r5, r1, 0x30+var_28\n"
+ "mtspr CTR, r12\n"
+ "bctrl\n"
+ "cmpwi r3, 0\n"
+ "stw r3, 0(r29)\n"
+ "beq loc_8046A744\n"
+ "li r3, 0\n"
+ "b loc_8046A7F0\n"
+ "loc_8046A744:\n"
+ "stw r24, 0(r27)\n"
+ "li r0, 0\n"
+ "li r3, 0\n"
+ "stw r0, 0(r29)\n"
+ "b loc_8046A7F0\n"
+ "loc_8046A758:\n"
+ "cmplwi r26, 1\n"
+ "bne loc_8046A7DC\n"
+ "mr r3, r28\n"
+ "bl `0x80468140`\n"
+ "cmplwi r3, 0x1A\n"
+ "bge loc_8046A784\n"
+ "mulli r0, r3, 0x14\n"
+ "lis r3, -0x7FA4\n"
+ "addi r3, r3, 0x870\n"
+ "add r3, r3, r0\n"
+ "b loc_8046A788\n"
+ "loc_8046A784:\n"
+ "li r3, 0\n"
+ "loc_8046A788:\n"
+ "cmpwi r3, 0\n"
+ "beq loc_8046A798\n"
+ "lwz r0, 8(r3)\n"
+ "b loc_8046A79C\n"
+ "loc_8046A798:\n"
+ "li r0, 1\n"
+ "loc_8046A79C:\n"
+ "cmplwi r0, 2\n"
+ "bne loc_8046A7DC\n"
+ "mr r12, r31\n"
+ "mr r3, r25\n"
+ "mtspr CTR, r12\n"
+ "bctrl\n"
+ "cmpwi r3, 0\n"
+ "stw r3, 0(r29)\n"
+ "beq loc_8046A7C8\n"
+ "li r3, 0\n"
+ "b loc_8046A7F0\n"
+ "loc_8046A7C8:\n"
+ "stw r24, 0(r27)\n"
+ "li r0, 0\n"
+ "li r3, 0\n"
+ "stw r0, 0(r29)\n"
+ "b loc_8046A7F0\n"
+ "loc_8046A7DC:\n"
+ "li r0, -0x16\n"
+ "li r3, 0\n"
+ "stw r0, 0(r29)\n"
+ "b loc_8046A7F0\n"
+ "loc_8046A7EC:\n"
+ "li r3, 1\n"
+ "loc_8046A7F0:\n"
+ "addi r11, r1, 0x30+pre_back_chain\n"
+ "bl `0x802BCAD0`\n"
+ "lwz r0, 0x30+sender_lr(r1)\n"
+ "mtspr LR, r0\n"
+ "addi r1, r1, 0x30\n"
+ "blr\n"
+ "loc_8046A804:\n";
+
+ constexpr u8 real_expect[] = {
+ 0x94, 0x21, 0xff, 0xd0, 0x7c, 0x08, 0x02, 0xa6, 0x90, 0x01, 0x00, 0x34, 0x39, 0x61, 0x00,
+ 0x30, 0x4b, 0xe5, 0x23, 0xe5, 0x38, 0x00, 0x00, 0x00, 0x7c, 0xfc, 0x3b, 0x78, 0x90, 0x08,
+ 0x00, 0x00, 0x7c, 0x78, 0x1b, 0x78, 0x7c, 0x99, 0x23, 0x78, 0x7c, 0xba, 0x2b, 0x78, 0x7c,
+ 0xdb, 0x33, 0x78, 0x7d, 0x1d, 0x43, 0x78, 0x7d, 0x3e, 0x4b, 0x78, 0x7d, 0x5f, 0x53, 0x78,
+ 0x7f, 0x83, 0xe3, 0x78, 0x4b, 0xff, 0xda, 0x71, 0x28, 0x03, 0x00, 0x1a, 0x40, 0x80, 0x00,
+ 0x18, 0x1c, 0x03, 0x00, 0x14, 0x3c, 0x60, 0x80, 0x5c, 0x38, 0x63, 0x08, 0x70, 0x7c, 0x63,
+ 0x02, 0x14, 0x48, 0x00, 0x00, 0x08, 0x38, 0x60, 0x00, 0x00, 0x2c, 0x03, 0x00, 0x00, 0x41,
+ 0x82, 0x00, 0x0c, 0x80, 0x03, 0x00, 0x0c, 0x48, 0x00, 0x00, 0x08, 0x38, 0x00, 0x00, 0x00,
+ 0x7c, 0x1a, 0x00, 0x40, 0x40, 0x80, 0x00, 0xe0, 0x2c, 0x1a, 0x00, 0x00, 0x40, 0x82, 0x00,
+ 0x44, 0x7f, 0xcc, 0xf3, 0x78, 0x7f, 0x83, 0xe3, 0x78, 0x7f, 0x24, 0xcb, 0x78, 0x38, 0xa1,
+ 0x00, 0x08, 0x7d, 0x89, 0x03, 0xa6, 0x4e, 0x80, 0x04, 0x21, 0x2c, 0x03, 0x00, 0x00, 0x90,
+ 0x7d, 0x00, 0x00, 0x41, 0x82, 0x00, 0x0c, 0x38, 0x60, 0x00, 0x00, 0x48, 0x00, 0x00, 0xb0,
+ 0x93, 0x1b, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x60, 0x00, 0x00, 0x90, 0x1d, 0x00,
+ 0x00, 0x48, 0x00, 0x00, 0x9c, 0x28, 0x1a, 0x00, 0x01, 0x40, 0x82, 0x00, 0x80, 0x7f, 0x83,
+ 0xe3, 0x78, 0x4b, 0xff, 0xd9, 0xdd, 0x28, 0x03, 0x00, 0x1a, 0x40, 0x80, 0x00, 0x18, 0x1c,
+ 0x03, 0x00, 0x14, 0x3c, 0x60, 0x80, 0x5c, 0x38, 0x63, 0x08, 0x70, 0x7c, 0x63, 0x02, 0x14,
+ 0x48, 0x00, 0x00, 0x08, 0x38, 0x60, 0x00, 0x00, 0x2c, 0x03, 0x00, 0x00, 0x41, 0x82, 0x00,
+ 0x0c, 0x80, 0x03, 0x00, 0x08, 0x48, 0x00, 0x00, 0x08, 0x38, 0x00, 0x00, 0x01, 0x28, 0x00,
+ 0x00, 0x02, 0x40, 0x82, 0x00, 0x3c, 0x7f, 0xec, 0xfb, 0x78, 0x7f, 0x23, 0xcb, 0x78, 0x7d,
+ 0x89, 0x03, 0xa6, 0x4e, 0x80, 0x04, 0x21, 0x2c, 0x03, 0x00, 0x00, 0x90, 0x7d, 0x00, 0x00,
+ 0x41, 0x82, 0x00, 0x0c, 0x38, 0x60, 0x00, 0x00, 0x48, 0x00, 0x00, 0x2c, 0x93, 0x1b, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x38, 0x60, 0x00, 0x00, 0x90, 0x1d, 0x00, 0x00, 0x48, 0x00,
+ 0x00, 0x18, 0x38, 0x00, 0xff, 0xea, 0x38, 0x60, 0x00, 0x00, 0x90, 0x1d, 0x00, 0x00, 0x48,
+ 0x00, 0x00, 0x08, 0x38, 0x60, 0x00, 0x01, 0x39, 0x61, 0x00, 0x30, 0x4b, 0xe5, 0x22, 0xdd,
+ 0x80, 0x01, 0x00, 0x34, 0x7c, 0x08, 0x03, 0xa6, 0x38, 0x21, 0x00, 0x30, 0x4e, 0x80, 0x00,
+ 0x20,
+ };
+
+ auto res = Assemble(real_assembly, 0);
+ ASSERT_TRUE(!IsFailure(res));
+ auto&& code_blocks = GetT(res);
+ ASSERT_EQ(code_blocks.size(), 1);
+ ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(real_expect));
+
+ EXPECT_EQ(code_blocks[0].block_address, 0x8046a690);
+ for (size_t i = 0; i < code_blocks[0].instructions.size(); i++)
+ {
+ EXPECT_EQ(code_blocks[0].instructions[i], real_expect[i]) << " -> i=" << i;
+ }
+}
diff --git a/Source/UnitTests/Common/CMakeLists.txt b/Source/UnitTests/Common/CMakeLists.txt
index 034e8ab6db..8865c024c1 100644
--- a/Source/UnitTests/Common/CMakeLists.txt
+++ b/Source/UnitTests/Common/CMakeLists.txt
@@ -1,3 +1,4 @@
+add_dolphin_test(AssemblerTest AssemblerTest.cpp)
add_dolphin_test(BitFieldTest BitFieldTest.cpp)
add_dolphin_test(BitSetTest BitSetTest.cpp)
add_dolphin_test(BitUtilsTest BitUtilsTest.cpp)