mirror of
https://github.com/izzy2lost/dolphin.git
synced 2026-06-19 01:16:48 -07:00
Parser and Assembler implementations
This commit is contained in:
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -897,6 +897,8 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
||||
s_user_paths[D_GBASAVES_IDX] = s_user_paths[D_GBAUSER_IDX] + GBASAVES_DIR DIR_SEP;
|
||||
s_user_paths[F_GBABIOS_IDX] = s_user_paths[D_GBAUSER_IDX] + GBA_BIOS;
|
||||
|
||||
s_user_paths[D_ASM_ROOT_IDX] = s_user_paths[D_USER_IDX] + ASSEMBLY_DIR DIR_SEP;
|
||||
|
||||
// The shader cache has moved to the cache directory, so remove the old one.
|
||||
// TODO: remove that someday.
|
||||
File::DeleteDirRecursively(s_user_paths[D_USER_IDX] + SHADERCACHE_LEGACY_DIR DIR_SEP);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -198,6 +198,12 @@ add_executable(dolphin-emu
|
||||
Config/WiimoteControllersWidget.h
|
||||
ConvertDialog.cpp
|
||||
ConvertDialog.h
|
||||
Debugger/AssembleInstructionDialog.cpp
|
||||
Debugger/AssembleInstructionDialog.h
|
||||
Debugger/AssemblerWidget.cpp
|
||||
Debugger/AssemblerWidget.h
|
||||
Debugger/AssemblyEditor.cpp
|
||||
Debugger/AssemblyEditor.h
|
||||
Debugger/BreakpointDialog.cpp
|
||||
Debugger/BreakpointDialog.h
|
||||
Debugger/BreakpointWidget.cpp
|
||||
@@ -208,6 +214,8 @@ add_executable(dolphin-emu
|
||||
Debugger/CodeViewWidget.h
|
||||
Debugger/CodeWidget.cpp
|
||||
Debugger/CodeWidget.h
|
||||
Debugger/GekkoSyntaxHighlight.cpp
|
||||
Debugger/GekkoSyntaxHighlight.h
|
||||
Debugger/JITWidget.cpp
|
||||
Debugger/JITWidget.h
|
||||
Debugger/MemoryViewWidget.cpp
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
// 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)
|
||||
{
|
||||
return QObject::tr("<span style=\"color: red; font-weight: bold\">Error</span> on line %1 col %2")
|
||||
.arg(err.line + 1)
|
||||
.arg(err.col + 1);
|
||||
}
|
||||
|
||||
QString HtmlFormatErrorLine(const Common::GekkoAssembler::AssemblerError& err)
|
||||
{
|
||||
const QString line_pre_error =
|
||||
QString::fromStdString(std::string(err.error_line.substr(0, err.col))).toHtmlEscaped();
|
||||
const QString line_error =
|
||||
QString::fromStdString(std::string(err.error_line.substr(err.col, err.len))).toHtmlEscaped();
|
||||
const QString line_post_error =
|
||||
QString::fromStdString(std::string(err.error_line.substr(err.col + err.len))).toHtmlEscaped();
|
||||
|
||||
return QObject::tr("%1<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(new QLabel(tr("Inline Assembler")));
|
||||
layout->addWidget(m_error_loc_label);
|
||||
layout->addWidget(m_input_edit);
|
||||
layout->addWidget(m_error_line_label);
|
||||
layout->addWidget(m_msg_label);
|
||||
layout->addWidget(m_button_box);
|
||||
m_input_edit->setText(QStringLiteral(".4byte 0x%1").arg(m_code, 8, 16, QLatin1Char('0')));
|
||||
|
||||
setLayout(layout);
|
||||
OnEditChanged();
|
||||
}
|
||||
|
||||
void AssembleInstructionDialog::ConnectWidgets()
|
||||
{
|
||||
connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
connect(m_input_edit, &QLineEdit::textChanged, this, &AssembleInstructionDialog::OnEditChanged);
|
||||
}
|
||||
|
||||
void AssembleInstructionDialog::OnEditChanged()
|
||||
{
|
||||
using namespace Common::GekkoAssembler;
|
||||
std::string line = m_input_edit->text().toStdString();
|
||||
Common::ToLower(&line);
|
||||
|
||||
FailureOr<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(tr("<span style=\"color: red; font-weight: bold\">Error</span>"));
|
||||
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(tr("<span style=\"color: green; font-weight: bold\">Ok</span>"));
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user