// Copyright 2023 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Common/Assembler/GekkoIRGen.h" #include #include #include #include #include #include #include #include #include #include "Common/Assembler/AssemblerShared.h" #include "Common/Assembler/GekkoParser.h" #include "Common/Assert.h" #include "Common/BitUtils.h" namespace Common::GekkoAssembler::detail { namespace { class GekkoIRPlugin : public ParsePlugin { public: GekkoIRPlugin(GekkoIR& result, u32 base_addr) : m_output_result(result), m_active_var(nullptr), m_operand_scan_begin(0) { m_active_block = &m_output_result.blocks.emplace_back(base_addr); } virtual ~GekkoIRPlugin() = default; void OnDirectivePre(GekkoDirective directive) override; void OnDirectivePost(GekkoDirective directive) override; void OnInstructionPre(const ParseInfo& mnemonic_info, bool extended) override; void OnInstructionPost(const ParseInfo& mnemonic_info, bool extended) override; void OnOperandPre() override; void OnOperandPost() override; void OnResolvedExprPost() override; void OnOperator(AsmOp operation) override; void OnTerminal(Terminal type, const AssemblerToken& val) override; void OnHiaddr(std::string_view id) override; void OnLoaddr(std::string_view id) override; void OnCloseParen(ParenType type) override; void OnLabelDecl(std::string_view name) override; void OnVarDecl(std::string_view name) override; void PostParseAction() override; u32 CurrentAddress() const; std::optional LookupVar(std::string_view lab); std::optional LookupLabel(std::string_view lab); template T& GetChunk(); template void AddBytes(T val); void AddStringBytes(std::string_view str, bool null_term); void PadAlign(u32 bits); void PadSpace(size_t space); void StartBlock(u32 address); void StartBlockAlign(u32 bits); void StartInstruction(size_t mnemonic_index, bool extended); void FinishInstruction(); void SaveOperandFixup(size_t str_left, size_t str_right); void AddBinaryEvaluator(u32 (*evaluator)(u32, u32)); void AddUnaryEvaluator(u32 (*evaluator)(u32)); void AddAbsoluteAddressConv(); void AddLiteral(u32 lit); void AddSymbolResolve(std::string_view sym, bool absolute); void RunFixups(); void EvalOperatorRel(AsmOp operation); void EvalOperatorAbs(AsmOp operation); void EvalTerminalRel(Terminal type, const AssemblerToken& tok); void EvalTerminalAbs(Terminal type, const AssemblerToken& tok); private: enum class EvalMode { RelAddrDoublePass, AbsAddrSinglePass, }; GekkoIR& m_output_result; IRBlock* m_active_block; GekkoInstruction m_build_inst; u64* m_active_var; size_t m_operand_scan_begin; std::map> m_labels; std::map> m_constants; std::set m_symset; EvalMode m_evaluation_mode; // For operand parsing std::stack> m_fixup_stack; std::vector> m_operand_fixups; size_t m_operand_str_start; // For directive parsing std::vector m_eval_stack; std::variant, std::vector> m_floats_list; std::string_view m_string_lit; GekkoDirective m_active_directive; }; /////////////// // OVERRIDES // /////////////// void GekkoIRPlugin::OnDirectivePre(GekkoDirective directive) { m_evaluation_mode = EvalMode::AbsAddrSinglePass; m_active_directive = directive; m_eval_stack = std::vector{}; switch (directive) { case GekkoDirective::Float: m_floats_list = std::vector{}; break; case GekkoDirective::Double: m_floats_list = std::vector{}; break; default: break; } } void GekkoIRPlugin::OnDirectivePost(GekkoDirective directive) { switch (directive) { // .nbyte directives are handled by OnResolvedExprPost default: break; case GekkoDirective::Float: case GekkoDirective::Double: std::visit( [this](auto&& vec) { for (auto&& val : vec) { AddBytes(val); } }, m_floats_list); break; case GekkoDirective::DefVar: ASSERT(m_active_var != nullptr); *m_active_var = m_eval_stack.back(); m_active_var = nullptr; break; case GekkoDirective::Locate: StartBlock(static_cast(m_eval_stack.back())); break; case GekkoDirective::Zeros: PadSpace(static_cast(m_eval_stack.back())); break; case GekkoDirective::Skip: { const u32 skip_len = static_cast(m_eval_stack.back()); if (skip_len > 0) { StartBlock(CurrentAddress() + skip_len); } break; } case GekkoDirective::PadAlign: PadAlign(static_cast(m_eval_stack.back())); break; case GekkoDirective::Align: StartBlockAlign(static_cast(m_eval_stack.back())); break; case GekkoDirective::Ascii: AddStringBytes(m_string_lit, false); break; case GekkoDirective::Asciz: AddStringBytes(m_string_lit, true); break; } m_eval_stack = {}; } void GekkoIRPlugin::OnInstructionPre(const ParseInfo& mnemonic_info, bool extended) { m_evaluation_mode = EvalMode::RelAddrDoublePass; StartInstruction(mnemonic_info.mnemonic_index, extended); } void GekkoIRPlugin::OnInstructionPost(const ParseInfo&, bool) { FinishInstruction(); } void GekkoIRPlugin::OnOperandPre() { m_operand_str_start = m_owner->lexer.ColNumber(); } void GekkoIRPlugin::OnOperandPost() { SaveOperandFixup(m_operand_str_start, m_owner->lexer.ColNumber()); } void GekkoIRPlugin::OnResolvedExprPost() { switch (m_active_directive) { case GekkoDirective::Byte: AddBytes(static_cast(m_eval_stack.back())); break; case GekkoDirective::_2byte: AddBytes(static_cast(m_eval_stack.back())); break; case GekkoDirective::_4byte: AddBytes(static_cast(m_eval_stack.back())); break; case GekkoDirective::_8byte: AddBytes(static_cast(m_eval_stack.back())); break; default: return; } m_eval_stack.clear(); } void GekkoIRPlugin::OnOperator(AsmOp operation) { if (m_evaluation_mode == EvalMode::RelAddrDoublePass) { EvalOperatorRel(operation); } else { EvalOperatorAbs(operation); } } void GekkoIRPlugin::OnTerminal(Terminal type, const AssemblerToken& val) { if (type == Terminal::Str) { m_string_lit = val.token_val; } else if (m_evaluation_mode == EvalMode::RelAddrDoublePass) { EvalTerminalRel(type, val); } else { EvalTerminalAbs(type, val); } } void GekkoIRPlugin::OnHiaddr(std::string_view id) { if (m_evaluation_mode == EvalMode::RelAddrDoublePass) { AddSymbolResolve(id, true); AddLiteral(16); AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs >> rhs; }); AddLiteral(0xffff); AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs & rhs; }); } else { u32 base; if (auto lbl = LookupLabel(id); lbl) { base = *lbl; } else if (auto var = LookupVar(id); var) { base = *var; } else { m_owner->EmitErrorHere(fmt::format("Undefined reference to Label/Constant '{}'", id)); return; } m_eval_stack.push_back((base >> 16) & 0xffff); } } void GekkoIRPlugin::OnLoaddr(std::string_view id) { if (m_evaluation_mode == EvalMode::RelAddrDoublePass) { AddSymbolResolve(id, true); AddLiteral(0xffff); AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs & rhs; }); } else { u32 base; if (auto lbl = LookupLabel(id); lbl) { base = *lbl; } else if (auto var = LookupVar(id); var) { base = *var; } else { m_owner->EmitErrorHere(fmt::format("Undefined reference to Label/Constant '{}'", id)); return; } m_eval_stack.push_back(base & 0xffff); } } void GekkoIRPlugin::OnCloseParen(ParenType type) { if (type != ParenType::RelConv) { return; } if (m_evaluation_mode == EvalMode::RelAddrDoublePass) { AddAbsoluteAddressConv(); } else { m_eval_stack.push_back(CurrentAddress()); EvalOperatorAbs(AsmOp::Sub); } } void GekkoIRPlugin::OnLabelDecl(std::string_view name) { const std::string name_str(name); if (m_symset.contains(name_str)) { m_owner->EmitErrorHere(fmt::format("Label/Constant {} is already defined", name)); return; } m_labels[name_str] = m_active_block->BlockEndAddress(); m_symset.insert(name_str); } void GekkoIRPlugin::OnVarDecl(std::string_view name) { const std::string name_str(name); if (m_symset.contains(name_str)) { m_owner->EmitErrorHere(fmt::format("Label/Constant {} is already defined", name)); return; } m_active_var = &m_constants[name_str]; m_symset.insert(name_str); } void GekkoIRPlugin::PostParseAction() { RunFixups(); } ////////////////////// // HELPER FUNCTIONS // ////////////////////// u32 GekkoIRPlugin::CurrentAddress() const { return m_active_block->BlockEndAddress(); } std::optional GekkoIRPlugin::LookupVar(std::string_view var) { auto var_it = m_constants.find(var); return var_it == m_constants.end() ? std::nullopt : std::optional(var_it->second); } std::optional GekkoIRPlugin::LookupLabel(std::string_view lab) { auto label_it = m_labels.find(lab); return label_it == m_labels.end() ? std::nullopt : std::optional(label_it->second); } void GekkoIRPlugin::AddStringBytes(std::string_view str, bool null_term) { ByteChunk& bytes = GetChunk(); ConvertStringLiteral(str, &bytes); if (null_term) { bytes.push_back('\0'); } } template T& GekkoIRPlugin::GetChunk() { if (!m_active_block->chunks.empty() && std::holds_alternative(m_active_block->chunks.back())) { return std::get(m_active_block->chunks.back()); } return std::get(m_active_block->chunks.emplace_back(T{})); } template void GekkoIRPlugin::AddBytes(T val) { if constexpr (std::is_integral_v) { ByteChunk& bytes = GetChunk(); for (size_t i = sizeof(T) - 1; i > 0; i--) { bytes.push_back((val >> (8 * i)) & 0xff); } bytes.push_back(val & 0xff); } else if constexpr (std::is_same_v) { static_assert(sizeof(double) == sizeof(u64)); AddBytes(std::bit_cast(val)); } else { // std::is_same_v static_assert(sizeof(double) == sizeof(u64)); AddBytes(std::bit_cast(val)); } } void GekkoIRPlugin::PadAlign(u32 bits) { const u32 align_mask = (1 << bits) - 1; const u32 current_addr = m_active_block->BlockEndAddress(); if (current_addr & align_mask) { PadChunk& current_pad = GetChunk(); current_pad += (1 << bits) - (current_addr & align_mask); } } void GekkoIRPlugin::PadSpace(size_t space) { GetChunk() += space; } void GekkoIRPlugin::StartBlock(u32 address) { m_active_block = &m_output_result.blocks.emplace_back(address); } void GekkoIRPlugin::StartBlockAlign(u32 bits) { const u32 align_mask = (1 << bits) - 1; const u32 current_addr = m_active_block->BlockEndAddress(); if (current_addr & align_mask) { StartBlock((1 << bits) + (current_addr & ~align_mask)); } } void GekkoIRPlugin::StartInstruction(size_t mnemonic_index, bool extended) { m_build_inst = GekkoInstruction{ .mnemonic_index = mnemonic_index, .raw_text = m_owner->lexer.CurrentLine(), .line_number = m_owner->lexer.LineNumber(), .is_extended = extended, }; m_operand_scan_begin = m_output_result.operand_pool.size(); } void GekkoIRPlugin::AddBinaryEvaluator(u32 (*evaluator)(u32, u32)) { std::function rhs = std::move(m_fixup_stack.top()); m_fixup_stack.pop(); std::function lhs = std::move(m_fixup_stack.top()); m_fixup_stack.pop(); m_fixup_stack.emplace([evaluator, lhs = std::move(lhs), rhs = std::move(rhs)]() { return evaluator(lhs(), rhs()); }); } void GekkoIRPlugin::AddUnaryEvaluator(u32 (*evaluator)(u32)) { std::function sub = std::move(m_fixup_stack.top()); m_fixup_stack.pop(); m_fixup_stack.emplace([evaluator, sub = std::move(sub)]() { return evaluator(sub()); }); } void GekkoIRPlugin::AddAbsoluteAddressConv() { const u32 inst_address = m_active_block->BlockEndAddress(); std::function sub = std::move(m_fixup_stack.top()); m_fixup_stack.pop(); m_fixup_stack.emplace([inst_address, sub = std::move(sub)] { return sub() - inst_address; }); } void GekkoIRPlugin::AddLiteral(u32 lit) { m_fixup_stack.emplace([lit] { return lit; }); } void GekkoIRPlugin::AddSymbolResolve(std::string_view sym, bool absolute) { const u32 source_address = m_active_block->BlockEndAddress(); AssemblerError err_on_fail = AssemblerError{ fmt::format("Unresolved symbol '{}'", sym), m_owner->lexer.CurrentLine(), m_owner->lexer.LineNumber(), // Lexer should currently point to the label, as it hasn't been eaten yet m_owner->lexer.ColNumber(), sym.size(), }; m_fixup_stack.emplace( [this, sym, absolute, source_address, err_on_fail = std::move(err_on_fail)] { auto label_it = m_labels.find(sym); if (label_it != m_labels.end()) { if (absolute) { return label_it->second; } return label_it->second - source_address; } auto var_it = m_constants.find(sym); if (var_it != m_constants.end()) { return static_cast(var_it->second); } m_owner->error = std::move(err_on_fail); return u32{0}; }); } void GekkoIRPlugin::SaveOperandFixup(size_t str_left, size_t str_right) { m_operand_fixups.emplace_back(std::move(m_fixup_stack.top())); m_fixup_stack.pop(); m_output_result.operand_pool.emplace_back(Interval{str_left, str_right - str_left}, 0); } void GekkoIRPlugin::RunFixups() { for (size_t i = 0; i < m_operand_fixups.size(); i++) { ValueOf(m_output_result.operand_pool[i]) = m_operand_fixups[i](); if (m_owner->error) { return; } } } void GekkoIRPlugin::FinishInstruction() { m_build_inst.op_interval.begin = m_operand_scan_begin; m_build_inst.op_interval.len = m_output_result.operand_pool.size() - m_operand_scan_begin; GetChunk().emplace_back(m_build_inst); m_operand_scan_begin = 0; } void GekkoIRPlugin::EvalOperatorAbs(AsmOp operation) { #define EVAL_BINARY_OP(OPERATOR) \ { \ u64 rhs = m_eval_stack.back(); \ m_eval_stack.pop_back(); \ m_eval_stack.back() = m_eval_stack.back() OPERATOR rhs; \ } switch (operation) { case AsmOp::Or: EVAL_BINARY_OP(|); break; case AsmOp::Xor: EVAL_BINARY_OP(^); break; case AsmOp::And: EVAL_BINARY_OP(&); break; case AsmOp::Lsh: EVAL_BINARY_OP(<<); break; case AsmOp::Rsh: EVAL_BINARY_OP(>>); break; case AsmOp::Add: EVAL_BINARY_OP(+); break; case AsmOp::Sub: EVAL_BINARY_OP(-); break; case AsmOp::Mul: EVAL_BINARY_OP(*); break; case AsmOp::Div: EVAL_BINARY_OP(/); break; case AsmOp::Neg: m_eval_stack.back() = static_cast(-static_cast(m_eval_stack.back())); break; case AsmOp::Not: m_eval_stack.back() = ~m_eval_stack.back(); break; } #undef EVAL_BINARY_OP #undef EVAL_UNARY_OP } void GekkoIRPlugin::EvalOperatorRel(AsmOp operation) { switch (operation) { case AsmOp::Or: AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs | rhs; }); break; case AsmOp::Xor: AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs ^ rhs; }); break; case AsmOp::And: AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs & rhs; }); break; case AsmOp::Lsh: AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs << rhs; }); break; case AsmOp::Rsh: AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs >> rhs; }); break; case AsmOp::Add: AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs + rhs; }); break; case AsmOp::Sub: AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs - rhs; }); break; case AsmOp::Mul: AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs * rhs; }); break; case AsmOp::Div: AddBinaryEvaluator([](u32 lhs, u32 rhs) { return lhs / rhs; }); break; case AsmOp::Neg: AddUnaryEvaluator([](u32 val) { return static_cast(-static_cast(val)); }); break; case AsmOp::Not: AddUnaryEvaluator([](u32 val) { return ~val; }); break; } } void GekkoIRPlugin::EvalTerminalRel(Terminal type, const AssemblerToken& tok) { switch (type) { case Terminal::Hex: case Terminal::Dec: case Terminal::Oct: case Terminal::Bin: case Terminal::GPR: case Terminal::FPR: case Terminal::SPR: case Terminal::CRField: case Terminal::Lt: case Terminal::Gt: case Terminal::Eq: case Terminal::So: { std::optional val = tok.EvalToken(); ASSERT(val.has_value()); AddLiteral(*val); break; } case Terminal::Dot: AddLiteral(CurrentAddress()); break; case Terminal::Id: { if (auto label_it = m_labels.find(tok.token_val); label_it != m_labels.end()) { AddLiteral(label_it->second - CurrentAddress()); } else if (auto var_it = m_constants.find(tok.token_val); var_it != m_constants.end()) { AddLiteral(var_it->second); } else { AddSymbolResolve(tok.token_val, false); } break; } // Parser should disallow this from happening default: ASSERT(false); break; } } void GekkoIRPlugin::EvalTerminalAbs(Terminal type, const AssemblerToken& tok) { switch (type) { case Terminal::Hex: case Terminal::Dec: case Terminal::Oct: case Terminal::Bin: case Terminal::GPR: case Terminal::FPR: case Terminal::SPR: case Terminal::CRField: case Terminal::Lt: case Terminal::Gt: case Terminal::Eq: case Terminal::So: { std::optional val = tok.EvalToken(); ASSERT(val.has_value()); m_eval_stack.push_back(*val); break; } case Terminal::Flt: { std::visit( [&tok](auto&& vec) { auto opt = tok.EvalToken::value_type>(); ASSERT(opt.has_value()); vec.push_back(*opt); }, m_floats_list); break; } case Terminal::Dot: m_eval_stack.push_back(static_cast(CurrentAddress())); break; case Terminal::Id: { if (auto label_it = m_labels.find(tok.token_val); label_it != m_labels.end()) { m_eval_stack.push_back(label_it->second); } else if (auto var_it = m_constants.find(tok.token_val); var_it != m_constants.end()) { m_eval_stack.push_back(var_it->second); } else { m_owner->EmitErrorHere( fmt::format("Undefined reference to Label/Constant '{}'", tok.ValStr())); return; } break; } // Parser should disallow this from happening default: ASSERT(false); break; } } } // namespace u32 IRBlock::BlockEndAddress() const { return std::accumulate(chunks.begin(), chunks.end(), block_address, [](u32 acc, const ChunkVariant& chunk) { size_t size; if (std::holds_alternative(chunk)) { size = std::get(chunk).size() * 4; } else if (std::holds_alternative(chunk)) { size = std::get(chunk).size(); } else if (std::holds_alternative(chunk)) { size = std::get(chunk); } else { ASSERT(false); size = 0; } return acc + static_cast(size); }); } FailureOr ParseToIR(std::string_view assembly, u32 base_virtual_address) { GekkoIR ret; GekkoIRPlugin plugin(ret, base_virtual_address); ParseWithPlugin(&plugin, assembly); if (plugin.Error()) { return FailureOr(std::move(*plugin.Error())); } return std::move(ret); } } // namespace Common::GekkoAssembler::detail