1
0
mirror of https://github.com/dolphin-emu/dolphin synced 2024-07-08 12:15:54 +00:00

Merge pull request #11497 from vyuuui/debugger_assembler_ui

Built-in assembler for debugger interface
This commit is contained in:
Tilka 2023-12-16 21:15:31 +00:00 committed by GitHub
commit 773ffd04b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
120 changed files with 9774 additions and 11 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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);

View File

@ -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,

View File

@ -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" />

View File

@ -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

View File

@ -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;
}

Some files were not shown because too many files have changed in this diff Show More