190 lines
6.1 KiB
C++
190 lines
6.1 KiB
C++
// 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
|