// Copyright 2023 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Common/Assembler/GekkoAssembler.h" #include #include #include #include #include #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 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(operands[i]), static_cast(desc.operand_masks[i].MinVal()), static_cast(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(operands[i]), static_cast(desc.operand_masks[i].MinVal()), static_cast(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> Assemble(std::string_view instruction, u32 current_instruction_address) { FailureOr 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 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(chunk)) { for (const detail::GekkoInstruction& parsed_inst : std::get(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(idx >> 2), adjusted_ops); FailureOr 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(chunk)) { detail::ByteChunk byte_arr = std::get(chunk); new_block.instructions.insert(new_block.instructions.end(), byte_arr.begin(), byte_arr.end()); } else if (std::holds_alternative(chunk)) { detail::PadChunk pad_len = std::get(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