dolphin/Source/Core/Common/Assembler/GekkoIRGen.cpp

834 lines
20 KiB
C++

// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/Assembler/GekkoIRGen.h"
#include <bit>
#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(std::bit_cast<u32>(val));
}
else
{
// std::is_same_v<T, double>
static_assert(sizeof(double) == sizeof(u64));
AddBytes(std::bit_cast<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