mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-09-01 15:16:22 +00:00
Merge branch 'master' of https://github.com/dolphin-emu/dolphin into dolphin-emu-master
This commit is contained in:
commit
c18016e795
767 changed files with 87644 additions and 70168 deletions
|
@ -1795,6 +1795,62 @@ void ARM64XEmitter::ADRP(ARM64Reg Rd, s64 imm)
|
|||
EncodeAddressInst(1, Rd, static_cast<s32>(imm >> 12));
|
||||
}
|
||||
|
||||
// This is using a hand-rolled algorithm. The goal is zero memory allocations, not necessarily
|
||||
// the best JIT-time time complexity. (The number of moves is usually very small.)
|
||||
void ARM64XEmitter::ParallelMoves(RegisterMove* begin, RegisterMove* end,
|
||||
std::array<u8, 32>* source_gpr_usages)
|
||||
{
|
||||
// X0-X7 are used for passing arguments.
|
||||
// X18-X31 are either callee saved or used for special purposes.
|
||||
constexpr size_t temp_reg_begin = 8;
|
||||
constexpr size_t temp_reg_end = 18;
|
||||
|
||||
while (begin != end)
|
||||
{
|
||||
bool removed_moves_during_this_loop_iteration = false;
|
||||
|
||||
RegisterMove* move = end;
|
||||
while (move != begin)
|
||||
{
|
||||
RegisterMove* prev_move = move;
|
||||
--move;
|
||||
if ((*source_gpr_usages)[DecodeReg(move->dst)] == 0)
|
||||
{
|
||||
MOV(move->dst, move->src);
|
||||
(*source_gpr_usages)[DecodeReg(move->src)]--;
|
||||
std::move(prev_move, end, move);
|
||||
--end;
|
||||
removed_moves_during_this_loop_iteration = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!removed_moves_during_this_loop_iteration)
|
||||
{
|
||||
// We need to break a cycle using a temporary register.
|
||||
|
||||
size_t temp_reg = temp_reg_begin;
|
||||
while ((*source_gpr_usages)[temp_reg] != 0)
|
||||
{
|
||||
++temp_reg;
|
||||
ASSERT_MSG(COMMON, temp_reg != temp_reg_end, "Out of registers");
|
||||
}
|
||||
|
||||
const ARM64Reg src = begin->src;
|
||||
const ARM64Reg dst =
|
||||
(Is64Bit(src) ? EncodeRegTo64 : EncodeRegTo32)(static_cast<ARM64Reg>(temp_reg));
|
||||
|
||||
MOV(dst, src);
|
||||
(*source_gpr_usages)[DecodeReg(dst)] = (*source_gpr_usages)[DecodeReg(src)];
|
||||
(*source_gpr_usages)[DecodeReg(src)] = 0;
|
||||
|
||||
std::for_each(begin, end, [src, dst](RegisterMove& move) {
|
||||
if (move.src == src)
|
||||
move.src = dst;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ARM64XEmitter::MOVI2RImpl(ARM64Reg Rd, T imm)
|
||||
{
|
||||
|
@ -1876,13 +1932,13 @@ void ARM64XEmitter::MOVI2RImpl(ARM64Reg Rd, T imm)
|
|||
(imm & 0xFFFF'FFFF'0000'0000) | (imm >> 32),
|
||||
(imm << 48) | (imm & 0x0000'FFFF'FFFF'0000) | (imm >> 48)})
|
||||
{
|
||||
if (LogicalImm(orr_imm, 64))
|
||||
if (LogicalImm(orr_imm, GPRSize::B64))
|
||||
try_base(orr_imm, Approach::ORRBase, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LogicalImm(imm, 32))
|
||||
if (LogicalImm(imm, GPRSize::B32))
|
||||
try_base(imm, Approach::ORRBase, false);
|
||||
}
|
||||
}
|
||||
|
@ -3983,9 +4039,28 @@ void ARM64FloatEmitter::ABI_PopRegisters(BitSet32 registers, ARM64Reg tmp)
|
|||
void ARM64XEmitter::ANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch)
|
||||
{
|
||||
if (!Is64Bit(Rn))
|
||||
imm &= 0xFFFFFFFF;
|
||||
{
|
||||
// To handle 32-bit logical immediates, the very easiest thing is to repeat
|
||||
// the input value twice to make a 64-bit word. The correct encoding of that
|
||||
// as a logical immediate will also be the correct encoding of the 32-bit
|
||||
// value.
|
||||
//
|
||||
// Doing this here instead of in the LogicalImm constructor makes it easier
|
||||
// to check if the input is all ones.
|
||||
|
||||
if (const auto result = LogicalImm(imm, Is64Bit(Rn) ? 64 : 32))
|
||||
imm = (imm << 32) | (imm & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
if (imm == 0)
|
||||
{
|
||||
MOVZ(Rd, 0);
|
||||
}
|
||||
else if ((~imm) == 0)
|
||||
{
|
||||
if (Rd != Rn)
|
||||
MOV(Rd, Rn);
|
||||
}
|
||||
else if (const auto result = LogicalImm(imm, GPRSize::B64))
|
||||
{
|
||||
AND(Rd, Rn, result);
|
||||
}
|
||||
|
@ -4001,7 +4076,29 @@ void ARM64XEmitter::ANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch)
|
|||
|
||||
void ARM64XEmitter::ORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch)
|
||||
{
|
||||
if (const auto result = LogicalImm(imm, Is64Bit(Rn) ? 64 : 32))
|
||||
if (!Is64Bit(Rn))
|
||||
{
|
||||
// To handle 32-bit logical immediates, the very easiest thing is to repeat
|
||||
// the input value twice to make a 64-bit word. The correct encoding of that
|
||||
// as a logical immediate will also be the correct encoding of the 32-bit
|
||||
// value.
|
||||
//
|
||||
// Doing this here instead of in the LogicalImm constructor makes it easier
|
||||
// to check if the input is all ones.
|
||||
|
||||
imm = (imm << 32) | (imm & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
if (imm == 0)
|
||||
{
|
||||
if (Rd != Rn)
|
||||
MOV(Rd, Rn);
|
||||
}
|
||||
else if ((~imm) == 0)
|
||||
{
|
||||
MOVN(Rd, 0);
|
||||
}
|
||||
else if (const auto result = LogicalImm(imm, GPRSize::B64))
|
||||
{
|
||||
ORR(Rd, Rn, result);
|
||||
}
|
||||
|
@ -4017,7 +4114,29 @@ void ARM64XEmitter::ORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch)
|
|||
|
||||
void ARM64XEmitter::EORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch)
|
||||
{
|
||||
if (const auto result = LogicalImm(imm, Is64Bit(Rn) ? 64 : 32))
|
||||
if (!Is64Bit(Rn))
|
||||
{
|
||||
// To handle 32-bit logical immediates, the very easiest thing is to repeat
|
||||
// the input value twice to make a 64-bit word. The correct encoding of that
|
||||
// as a logical immediate will also be the correct encoding of the 32-bit
|
||||
// value.
|
||||
//
|
||||
// Doing this here instead of in the LogicalImm constructor makes it easier
|
||||
// to check if the input is all ones.
|
||||
|
||||
imm = (imm << 32) | (imm & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
if (imm == 0)
|
||||
{
|
||||
if (Rd != Rn)
|
||||
MOV(Rd, Rn);
|
||||
}
|
||||
else if ((~imm) == 0)
|
||||
{
|
||||
MVN(Rd, Rn);
|
||||
}
|
||||
else if (const auto result = LogicalImm(imm, GPRSize::B64))
|
||||
{
|
||||
EOR(Rd, Rn, result);
|
||||
}
|
||||
|
@ -4033,7 +4152,29 @@ void ARM64XEmitter::EORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch)
|
|||
|
||||
void ARM64XEmitter::ANDSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch)
|
||||
{
|
||||
if (const auto result = LogicalImm(imm, Is64Bit(Rn) ? 64 : 32))
|
||||
if (!Is64Bit(Rn))
|
||||
{
|
||||
// To handle 32-bit logical immediates, the very easiest thing is to repeat
|
||||
// the input value twice to make a 64-bit word. The correct encoding of that
|
||||
// as a logical immediate will also be the correct encoding of the 32-bit
|
||||
// value.
|
||||
//
|
||||
// Doing this here instead of in the LogicalImm constructor makes it easier
|
||||
// to check if the input is all ones.
|
||||
|
||||
imm = (imm << 32) | (imm & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
if (imm == 0)
|
||||
{
|
||||
ANDS(Rd, Is64Bit(Rn) ? ARM64Reg::ZR : ARM64Reg::WZR,
|
||||
Is64Bit(Rn) ? ARM64Reg::ZR : ARM64Reg::WZR);
|
||||
}
|
||||
else if ((~imm) == 0)
|
||||
{
|
||||
ANDS(Rd, Rn, Rn);
|
||||
}
|
||||
else if (const auto result = LogicalImm(imm, GPRSize::B64))
|
||||
{
|
||||
ANDS(Rd, Rn, result);
|
||||
}
|
||||
|
@ -4069,11 +4210,24 @@ void ARM64XEmitter::AddImmediate(ARM64Reg Rd, ARM64Reg Rn, u64 imm, bool shift,
|
|||
void ARM64XEmitter::ADDI2R_internal(ARM64Reg Rd, ARM64Reg Rn, u64 imm, bool negative, bool flags,
|
||||
ARM64Reg scratch)
|
||||
{
|
||||
DEBUG_ASSERT(Is64Bit(Rd) == Is64Bit(Rn));
|
||||
|
||||
if (!Is64Bit(Rd))
|
||||
imm &= 0xFFFFFFFFULL;
|
||||
|
||||
bool has_scratch = scratch != ARM64Reg::INVALID_REG;
|
||||
u64 imm_neg = Is64Bit(Rd) ? u64(-s64(imm)) : u64(-s64(imm)) & 0xFFFFFFFFuLL;
|
||||
bool neg_neg = negative ? false : true;
|
||||
|
||||
// Fast paths, aarch64 immediate instructions
|
||||
// Special path for zeroes
|
||||
if (imm == 0 && !flags)
|
||||
{
|
||||
if (Rd != Rn)
|
||||
MOV(Rd, Rn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Regular fast paths, aarch64 immediate instructions
|
||||
// Try them all first
|
||||
if (imm <= 0xFFF)
|
||||
{
|
||||
|
@ -4196,7 +4350,7 @@ bool ARM64XEmitter::TryCMPI2R(ARM64Reg Rn, u64 imm)
|
|||
|
||||
bool ARM64XEmitter::TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm)
|
||||
{
|
||||
if (const auto result = LogicalImm(imm, Is64Bit(Rd) ? 64 : 32))
|
||||
if (const auto result = LogicalImm(imm, Is64Bit(Rd) ? GPRSize::B64 : GPRSize::B32))
|
||||
{
|
||||
AND(Rd, Rn, result);
|
||||
return true;
|
||||
|
@ -4207,7 +4361,7 @@ bool ARM64XEmitter::TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm)
|
|||
|
||||
bool ARM64XEmitter::TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm)
|
||||
{
|
||||
if (const auto result = LogicalImm(imm, Is64Bit(Rd) ? 64 : 32))
|
||||
if (const auto result = LogicalImm(imm, Is64Bit(Rd) ? GPRSize::B64 : GPRSize::B32))
|
||||
{
|
||||
ORR(Rd, Rn, result);
|
||||
return true;
|
||||
|
@ -4218,7 +4372,7 @@ bool ARM64XEmitter::TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm)
|
|||
|
||||
bool ARM64XEmitter::TryEORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm)
|
||||
{
|
||||
if (const auto result = LogicalImm(imm, Is64Bit(Rd) ? 64 : 32))
|
||||
if (const auto result = LogicalImm(imm, Is64Bit(Rd) ? GPRSize::B64 : GPRSize::B32))
|
||||
{
|
||||
EOR(Rd, Rn, result);
|
||||
return true;
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "Common/ArmCommon.h"
|
||||
|
@ -17,6 +19,7 @@
|
|||
#include "Common/Common.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MathUtil.h"
|
||||
#include "Common/SmallVector.h"
|
||||
|
||||
namespace Arm64Gen
|
||||
{
|
||||
|
@ -347,6 +350,12 @@ enum class RoundingMode
|
|||
Z, // round towards zero
|
||||
};
|
||||
|
||||
enum class GPRSize
|
||||
{
|
||||
B32,
|
||||
B64,
|
||||
};
|
||||
|
||||
struct FixupBranch
|
||||
{
|
||||
enum class Type : u32
|
||||
|
@ -519,7 +528,7 @@ struct LogicalImm
|
|||
|
||||
constexpr LogicalImm(u8 r_, u8 s_, bool n_) : r(r_), s(s_), n(n_), valid(true) {}
|
||||
|
||||
constexpr LogicalImm(u64 value, u32 width)
|
||||
constexpr LogicalImm(u64 value, GPRSize size)
|
||||
{
|
||||
// Logical immediates are encoded using parameters n, imm_s and imm_r using
|
||||
// the following table:
|
||||
|
@ -537,17 +546,14 @@ struct LogicalImm
|
|||
// are set. The pattern is rotated right by R, and repeated across a 32 or
|
||||
// 64-bit value, depending on destination register width.
|
||||
|
||||
constexpr int kWRegSizeInBits = 32;
|
||||
|
||||
if (width == kWRegSizeInBits)
|
||||
if (size == GPRSize::B32)
|
||||
{
|
||||
// To handle 32-bit logical immediates, the very easiest thing is to repeat
|
||||
// the input value twice to make a 64-bit word. The correct encoding of that
|
||||
// as a logical immediate will also be the correct encoding of the 32-bit
|
||||
// value.
|
||||
|
||||
value <<= kWRegSizeInBits;
|
||||
value |= value >> kWRegSizeInBits;
|
||||
value = (value << 32) | (value & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
if (value == 0 || (~value) == 0)
|
||||
|
@ -599,6 +605,12 @@ class ARM64XEmitter
|
|||
friend class ARM64FloatEmitter;
|
||||
|
||||
private:
|
||||
struct RegisterMove
|
||||
{
|
||||
ARM64Reg dst;
|
||||
ARM64Reg src;
|
||||
};
|
||||
|
||||
// Pointer to memory where code will be emitted to.
|
||||
u8* m_code = nullptr;
|
||||
|
||||
|
@ -646,6 +658,10 @@ private:
|
|||
|
||||
[[nodiscard]] FixupBranch WriteFixupBranch();
|
||||
|
||||
// This function solves the "parallel moves" problem common in compilers.
|
||||
// The arguments are mutated!
|
||||
void ParallelMoves(RegisterMove* begin, RegisterMove* end, std::array<u8, 32>* source_gpr_usages);
|
||||
|
||||
template <typename T>
|
||||
void MOVI2RImpl(ARM64Reg Rd, T imm);
|
||||
|
||||
|
@ -1058,6 +1074,114 @@ public:
|
|||
void ABI_PushRegisters(BitSet32 registers);
|
||||
void ABI_PopRegisters(BitSet32 registers, BitSet32 ignore_mask = BitSet32(0));
|
||||
|
||||
// Plain function call
|
||||
void QuickCallFunction(ARM64Reg scratchreg, const void* func);
|
||||
template <typename T>
|
||||
void QuickCallFunction(ARM64Reg scratchreg, T func)
|
||||
{
|
||||
QuickCallFunction(scratchreg, (const void*)func);
|
||||
}
|
||||
|
||||
template <typename FuncRet, typename... FuncArgs, typename... Args>
|
||||
void ABI_CallFunction(FuncRet (*func)(FuncArgs...), Args... args)
|
||||
{
|
||||
static_assert(sizeof...(FuncArgs) == sizeof...(Args), "Wrong number of arguments");
|
||||
static_assert(sizeof...(FuncArgs) <= 8, "Passing arguments on the stack is not supported");
|
||||
|
||||
if constexpr (!std::is_void_v<FuncRet>)
|
||||
static_assert(sizeof(FuncRet) <= 16, "Large return types are not supported");
|
||||
|
||||
std::array<u8, 32> source_gpr_uses{};
|
||||
|
||||
auto check_argument = [&](auto& arg) {
|
||||
using Arg = std::decay_t<decltype(arg)>;
|
||||
|
||||
if constexpr (std::is_same_v<Arg, ARM64Reg>)
|
||||
{
|
||||
ASSERT(IsGPR(arg));
|
||||
source_gpr_uses[DecodeReg(arg)]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// To be more correct, we should be checking FuncArgs here rather than Args, but that's a
|
||||
// lot more effort to implement. Let's just do these best-effort checks for now.
|
||||
static_assert(!std::is_floating_point_v<Arg>, "Floating-point arguments are not supported");
|
||||
static_assert(sizeof(Arg) <= 8, "Arguments bigger than a register are not supported");
|
||||
}
|
||||
};
|
||||
|
||||
(check_argument(args), ...);
|
||||
|
||||
{
|
||||
Common::SmallVector<RegisterMove, sizeof...(Args)> pending_moves;
|
||||
|
||||
size_t i = 0;
|
||||
|
||||
auto handle_register_argument = [&](auto& arg) {
|
||||
using Arg = std::decay_t<decltype(arg)>;
|
||||
|
||||
if constexpr (std::is_same_v<Arg, ARM64Reg>)
|
||||
{
|
||||
const ARM64Reg dst_reg =
|
||||
(Is64Bit(arg) ? EncodeRegTo64 : EncodeRegTo32)(static_cast<ARM64Reg>(i));
|
||||
|
||||
if (dst_reg == arg)
|
||||
{
|
||||
// The value is already in the right register.
|
||||
source_gpr_uses[DecodeReg(arg)]--;
|
||||
}
|
||||
else if (source_gpr_uses[i] == 0)
|
||||
{
|
||||
// The destination register isn't used as the source of another move.
|
||||
// We can go ahead and do the move right away.
|
||||
MOV(dst_reg, arg);
|
||||
source_gpr_uses[DecodeReg(arg)]--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The destination register is used as the source of a move we haven't gotten to yet.
|
||||
// Let's record that we need to deal with this move later.
|
||||
pending_moves.emplace_back(dst_reg, arg);
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
};
|
||||
|
||||
(handle_register_argument(args), ...);
|
||||
|
||||
if (!pending_moves.empty())
|
||||
{
|
||||
ParallelMoves(pending_moves.data(), pending_moves.data() + pending_moves.size(),
|
||||
&source_gpr_uses);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
auto handle_immediate_argument = [&](auto& arg) {
|
||||
using Arg = std::decay_t<decltype(arg)>;
|
||||
|
||||
if constexpr (!std::is_same_v<Arg, ARM64Reg>)
|
||||
{
|
||||
const ARM64Reg dst_reg =
|
||||
(sizeof(arg) == 8 ? EncodeRegTo64 : EncodeRegTo32)(static_cast<ARM64Reg>(i));
|
||||
if constexpr (std::is_pointer_v<Arg>)
|
||||
MOVP2R(dst_reg, arg);
|
||||
else
|
||||
MOVI2R(dst_reg, arg);
|
||||
}
|
||||
|
||||
++i;
|
||||
};
|
||||
|
||||
(handle_immediate_argument(args), ...);
|
||||
}
|
||||
|
||||
QuickCallFunction(ARM64Reg::X8, func);
|
||||
}
|
||||
|
||||
// Utility to generate a call to a std::function object.
|
||||
//
|
||||
// Unfortunately, calling operator() directly is undefined behavior in C++
|
||||
|
@ -1069,23 +1193,11 @@ public:
|
|||
return (*f)(args...);
|
||||
}
|
||||
|
||||
// This function expects you to have set up the state.
|
||||
// Overwrites X0 and X8
|
||||
template <typename T, typename... Args>
|
||||
ARM64Reg ABI_SetupLambda(const std::function<T(Args...)>* f)
|
||||
template <typename FuncRet, typename... FuncArgs, typename... Args>
|
||||
void ABI_CallLambdaFunction(const std::function<FuncRet(FuncArgs...)>* f, Args... args)
|
||||
{
|
||||
auto trampoline = &ARM64XEmitter::CallLambdaTrampoline<T, Args...>;
|
||||
MOVP2R(ARM64Reg::X8, trampoline);
|
||||
MOVP2R(ARM64Reg::X0, const_cast<void*>((const void*)f));
|
||||
return ARM64Reg::X8;
|
||||
}
|
||||
|
||||
// Plain function call
|
||||
void QuickCallFunction(ARM64Reg scratchreg, const void* func);
|
||||
template <typename T>
|
||||
void QuickCallFunction(ARM64Reg scratchreg, T func)
|
||||
{
|
||||
QuickCallFunction(scratchreg, (const void*)func);
|
||||
auto trampoline = &ARM64XEmitter::CallLambdaTrampoline<FuncRet, FuncArgs...>;
|
||||
ABI_CallFunction(trampoline, f, args...);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
26
Source/Core/Common/Assembler/AssemblerShared.cpp
Normal file
26
Source/Core/Common/Assembler/AssemblerShared.cpp
Normal file
|
@ -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
|
545
Source/Core/Common/Assembler/AssemblerShared.h
Normal file
545
Source/Core/Common/Assembler/AssemblerShared.h
Normal file
|
@ -0,0 +1,545 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
namespace Common::GekkoAssembler
|
||||
{
|
||||
struct Interval
|
||||
{
|
||||
size_t begin;
|
||||
size_t len;
|
||||
constexpr size_t End() const { return begin + len; }
|
||||
};
|
||||
|
||||
struct AssemblerError
|
||||
{
|
||||
std::string message;
|
||||
std::string_view error_line;
|
||||
size_t line;
|
||||
size_t col;
|
||||
size_t len;
|
||||
|
||||
std::string FormatError() const;
|
||||
};
|
||||
|
||||
template <typename Tag, typename T>
|
||||
using Tagged = std::pair<Tag, T>;
|
||||
template <typename Tag, typename T>
|
||||
constexpr const Tag& TagOf(const Tagged<Tag, T>& val)
|
||||
{
|
||||
return std::get<0>(val);
|
||||
}
|
||||
template <typename Tag, typename T>
|
||||
constexpr Tag& TagOf(Tagged<Tag, T>& val)
|
||||
{
|
||||
return std::get<0>(val);
|
||||
}
|
||||
template <typename Tag, typename T>
|
||||
constexpr const T& ValueOf(const Tagged<Tag, T>& val)
|
||||
{
|
||||
return std::get<1>(val);
|
||||
}
|
||||
template <typename Tag, typename T>
|
||||
constexpr T& ValueOf(Tagged<Tag, T>& val)
|
||||
{
|
||||
return std::get<1>(val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using FailureOr = std::variant<AssemblerError, T>;
|
||||
template <typename T>
|
||||
constexpr bool IsFailure(const FailureOr<T>& var)
|
||||
{
|
||||
return std::holds_alternative<AssemblerError>(var);
|
||||
}
|
||||
template <typename T>
|
||||
constexpr AssemblerError& GetFailure(FailureOr<T>& var)
|
||||
{
|
||||
return std::get<AssemblerError>(var);
|
||||
}
|
||||
template <typename T>
|
||||
constexpr const AssemblerError& GetFailure(const FailureOr<T>& var)
|
||||
{
|
||||
return std::get<AssemblerError>(var);
|
||||
}
|
||||
template <typename T>
|
||||
constexpr const T& GetT(const FailureOr<T>& var)
|
||||
{
|
||||
return std::get<T>(var);
|
||||
}
|
||||
template <typename T>
|
||||
constexpr T& GetT(FailureOr<T>& var)
|
||||
{
|
||||
return std::get<T>(var);
|
||||
}
|
||||
|
||||
enum class GekkoDirective
|
||||
{
|
||||
Byte,
|
||||
_2byte,
|
||||
_4byte,
|
||||
_8byte,
|
||||
Float,
|
||||
Double,
|
||||
Locate,
|
||||
PadAlign,
|
||||
Align,
|
||||
Zeros,
|
||||
Skip,
|
||||
DefVar,
|
||||
Ascii,
|
||||
Asciz
|
||||
};
|
||||
|
||||
enum class GekkoMnemonic : size_t
|
||||
{
|
||||
Add,
|
||||
Addc,
|
||||
Adde,
|
||||
Addi,
|
||||
Addic,
|
||||
AddicDot,
|
||||
Addis,
|
||||
Addme,
|
||||
Addze,
|
||||
Divw,
|
||||
Divwu,
|
||||
Mulhw,
|
||||
Mulhwu,
|
||||
Mulli,
|
||||
Mullw,
|
||||
Neg,
|
||||
Subf,
|
||||
Subfc,
|
||||
Subfe,
|
||||
Subfic,
|
||||
Subfme,
|
||||
Subfze,
|
||||
Cmp,
|
||||
Cmpi,
|
||||
Cmpl,
|
||||
Cmpli,
|
||||
And,
|
||||
Andc,
|
||||
AndiDot,
|
||||
AndisDot,
|
||||
Cntlzw,
|
||||
Eqv,
|
||||
Extsb,
|
||||
Extsh,
|
||||
Nand,
|
||||
Nor,
|
||||
Or,
|
||||
Orc,
|
||||
Ori,
|
||||
Oris,
|
||||
Xor,
|
||||
Xori,
|
||||
Xoris,
|
||||
Rlwimi,
|
||||
Rlwinm,
|
||||
Rlwnm,
|
||||
Slw,
|
||||
Sraw,
|
||||
Srawi,
|
||||
Srw,
|
||||
Fadd,
|
||||
Fadds,
|
||||
Fdiv,
|
||||
Fdivs,
|
||||
Fmul,
|
||||
Fmuls,
|
||||
Fres,
|
||||
Frsqrte,
|
||||
Fsub,
|
||||
Fsubs,
|
||||
Fsel,
|
||||
Fmadd,
|
||||
Fmadds,
|
||||
Fmsub,
|
||||
Fmsubs,
|
||||
Fnmadd,
|
||||
Fnmadds,
|
||||
Fnmsub,
|
||||
Fnmsubs,
|
||||
Fctiw,
|
||||
Fctiwz,
|
||||
Frsp,
|
||||
Fcmpo,
|
||||
Fcmpu,
|
||||
Mcrfs,
|
||||
Mffs,
|
||||
Mtfsb0,
|
||||
Mtfsb1,
|
||||
Mtfsf,
|
||||
Mtfsfi,
|
||||
Lbz,
|
||||
Lbzu,
|
||||
Lbzux,
|
||||
Lbzx,
|
||||
Lha,
|
||||
Lhau,
|
||||
Lhaux,
|
||||
Lhax,
|
||||
Lhz,
|
||||
Lhzu,
|
||||
Lhzux,
|
||||
Lhzx,
|
||||
Lwz,
|
||||
Lwzu,
|
||||
Lwzux,
|
||||
Lwzx,
|
||||
Stb,
|
||||
Stbu,
|
||||
Stbux,
|
||||
Stbx,
|
||||
Sth,
|
||||
Sthu,
|
||||
Sthux,
|
||||
Sthx,
|
||||
Stw,
|
||||
Stwu,
|
||||
Stwux,
|
||||
Stwx,
|
||||
Lhbrx,
|
||||
Lwbrx,
|
||||
Sthbrx,
|
||||
Stwbrx,
|
||||
Lmw,
|
||||
Stmw,
|
||||
Lswi,
|
||||
Lswx,
|
||||
Stswi,
|
||||
Stswx,
|
||||
Eieio,
|
||||
Isync,
|
||||
Lwarx,
|
||||
StwcxDot,
|
||||
Sync,
|
||||
Lfd,
|
||||
Lfdu,
|
||||
Lfdux,
|
||||
Lfdx,
|
||||
Lfs,
|
||||
Lfsu,
|
||||
Lfsux,
|
||||
Lfsx,
|
||||
Stfd,
|
||||
Stfdu,
|
||||
Stfdux,
|
||||
Stfdx,
|
||||
Stfiwx,
|
||||
Stfs,
|
||||
Stfsu,
|
||||
Stfsux,
|
||||
Stfsx,
|
||||
Fabs,
|
||||
Fmr,
|
||||
Fnabs,
|
||||
Fneg,
|
||||
B,
|
||||
Bc,
|
||||
Bcctr,
|
||||
Bclr,
|
||||
Crand,
|
||||
Crandc,
|
||||
Creqv,
|
||||
Crnand,
|
||||
Crnor,
|
||||
Cror,
|
||||
Crorc,
|
||||
Crxor,
|
||||
Mcrf,
|
||||
Rfi,
|
||||
Sc,
|
||||
Tw,
|
||||
Twi,
|
||||
Mcrxr,
|
||||
Mfcr,
|
||||
Mfmsr,
|
||||
Mfspr_nobitswap,
|
||||
Mftb_nobitswap,
|
||||
Mtcrf,
|
||||
Mtmsr,
|
||||
Mtspr_nobitswap,
|
||||
Dcbf,
|
||||
Dcbi,
|
||||
Dcbst,
|
||||
Dcbt,
|
||||
Dcbtst,
|
||||
Dcbz,
|
||||
Icbi,
|
||||
Mfsr,
|
||||
Mfsrin,
|
||||
Mtsr,
|
||||
Mtsrin,
|
||||
Tlbie,
|
||||
Tlbsync,
|
||||
Eciwx,
|
||||
Ecowx,
|
||||
Psq_lx,
|
||||
Psq_stx,
|
||||
Psq_lux,
|
||||
Psq_stux,
|
||||
Psq_l,
|
||||
Psq_lu,
|
||||
Psq_st,
|
||||
Psq_stu,
|
||||
Ps_div,
|
||||
Ps_sub,
|
||||
Ps_add,
|
||||
Ps_sel,
|
||||
Ps_res,
|
||||
Ps_mul,
|
||||
Ps_rsqrte,
|
||||
Ps_msub,
|
||||
Ps_madd,
|
||||
Ps_nmsub,
|
||||
Ps_nmadd,
|
||||
Ps_neg,
|
||||
Ps_mr,
|
||||
Ps_nabs,
|
||||
Ps_abs,
|
||||
Ps_sum0,
|
||||
Ps_sum1,
|
||||
Ps_muls0,
|
||||
Ps_muls1,
|
||||
Ps_madds0,
|
||||
Ps_madds1,
|
||||
Ps_cmpu0,
|
||||
Ps_cmpo0,
|
||||
Ps_cmpu1,
|
||||
Ps_cmpo1,
|
||||
Ps_merge00,
|
||||
Ps_merge01,
|
||||
Ps_merge10,
|
||||
Ps_merge11,
|
||||
Dcbz_l,
|
||||
LastMnemonic = Dcbz_l,
|
||||
InvalidMnemonic,
|
||||
};
|
||||
|
||||
enum class ExtendedGekkoMnemonic : size_t
|
||||
{
|
||||
Subi,
|
||||
Subis,
|
||||
Subic,
|
||||
SubicDot,
|
||||
Sub,
|
||||
Subc,
|
||||
Cmpwi,
|
||||
Cmpw,
|
||||
Cmplwi,
|
||||
Cmplw,
|
||||
Extlwi,
|
||||
Extrwi,
|
||||
Inslwi,
|
||||
Insrwi,
|
||||
Rotlwi,
|
||||
Rotrwi,
|
||||
Rotlw,
|
||||
Slwi,
|
||||
Srwi,
|
||||
Clrlwi,
|
||||
Clrrwi,
|
||||
Clrlslwi,
|
||||
Bt,
|
||||
Bf,
|
||||
Bdnz,
|
||||
Bdnzt,
|
||||
Bdnzf,
|
||||
Bdz,
|
||||
Bdzt,
|
||||
Bdzf,
|
||||
BtPredict,
|
||||
BfPredict,
|
||||
BdnzPredict,
|
||||
BdnztPredict,
|
||||
BdnzfPredict,
|
||||
BdzPredict,
|
||||
BdztPredict,
|
||||
BdzfPredict,
|
||||
Blr,
|
||||
Btlr,
|
||||
Bflr,
|
||||
Bdnzlr,
|
||||
Bdnztlr,
|
||||
Bdnzflr,
|
||||
Bdzlr,
|
||||
Bdztlr,
|
||||
Bdzflr,
|
||||
BtlrPredict,
|
||||
BflrPredict,
|
||||
BdnzlrPredict,
|
||||
BdnztlrPredict,
|
||||
BdnzflrPredict,
|
||||
BdzlrPredict,
|
||||
BdztlrPredict,
|
||||
BdzflrPredict,
|
||||
Bctr,
|
||||
Btctr,
|
||||
Bfctr,
|
||||
BtctrPredict,
|
||||
BfctrPredict,
|
||||
Blt,
|
||||
Ble,
|
||||
Beq,
|
||||
Bge,
|
||||
Bgt,
|
||||
Bnl,
|
||||
Bne,
|
||||
Bng,
|
||||
Bso,
|
||||
Bns,
|
||||
Bun,
|
||||
Bnu,
|
||||
BltPredict,
|
||||
BlePredict,
|
||||
BeqPredict,
|
||||
BgePredict,
|
||||
BgtPredict,
|
||||
BnlPredict,
|
||||
BnePredict,
|
||||
BngPredict,
|
||||
BsoPredict,
|
||||
BnsPredict,
|
||||
BunPredict,
|
||||
BnuPredict,
|
||||
Bltlr,
|
||||
Blelr,
|
||||
Beqlr,
|
||||
Bgelr,
|
||||
Bgtlr,
|
||||
Bnllr,
|
||||
Bnelr,
|
||||
Bnglr,
|
||||
Bsolr,
|
||||
Bnslr,
|
||||
Bunlr,
|
||||
Bnulr,
|
||||
BltlrPredict,
|
||||
BlelrPredict,
|
||||
BeqlrPredict,
|
||||
BgelrPredict,
|
||||
BgtlrPredict,
|
||||
BnllrPredict,
|
||||
BnelrPredict,
|
||||
BnglrPredict,
|
||||
BsolrPredict,
|
||||
BnslrPredict,
|
||||
BunlrPredict,
|
||||
BnulrPredict,
|
||||
Bltctr,
|
||||
Blectr,
|
||||
Beqctr,
|
||||
Bgectr,
|
||||
Bgtctr,
|
||||
Bnlctr,
|
||||
Bnectr,
|
||||
Bngctr,
|
||||
Bsoctr,
|
||||
Bnsctr,
|
||||
Bunctr,
|
||||
Bnuctr,
|
||||
BltctrPredict,
|
||||
BlectrPredict,
|
||||
BeqctrPredict,
|
||||
BgectrPredict,
|
||||
BgtctrPredict,
|
||||
BnlctrPredict,
|
||||
BnectrPredict,
|
||||
BngctrPredict,
|
||||
BsoctrPredict,
|
||||
BnsctrPredict,
|
||||
BunctrPredict,
|
||||
BnuctrPredict,
|
||||
Crset,
|
||||
Crclr,
|
||||
Crmove,
|
||||
Crnot,
|
||||
Twlt,
|
||||
Twlti,
|
||||
Twle,
|
||||
Twlei,
|
||||
Tweq,
|
||||
Tweqi,
|
||||
Twge,
|
||||
Twgei,
|
||||
Twgt,
|
||||
Twgti,
|
||||
Twnl,
|
||||
Twnli,
|
||||
Twne,
|
||||
Twnei,
|
||||
Twng,
|
||||
Twngi,
|
||||
Twllt,
|
||||
Twllti,
|
||||
Twlle,
|
||||
Twllei,
|
||||
Twlge,
|
||||
Twlgei,
|
||||
Twlgt,
|
||||
Twlgti,
|
||||
Twlnl,
|
||||
Twlnli,
|
||||
Twlng,
|
||||
Twlngi,
|
||||
Trap,
|
||||
Mtxer,
|
||||
Mfxer,
|
||||
Mtlr,
|
||||
Mflr,
|
||||
Mtctr,
|
||||
Mfctr,
|
||||
Mtdsisr,
|
||||
Mfdsisr,
|
||||
Mtdar,
|
||||
Mfdar,
|
||||
Mtdec,
|
||||
Mfdec,
|
||||
Mtsdr1,
|
||||
Mfsdr1,
|
||||
Mtsrr0,
|
||||
Mfsrr0,
|
||||
Mtsrr1,
|
||||
Mfsrr1,
|
||||
Mtasr,
|
||||
Mfasr,
|
||||
Mtear,
|
||||
Mfear,
|
||||
Mttbl,
|
||||
Mftbl,
|
||||
Mttbu,
|
||||
Mftbu,
|
||||
Mtsprg,
|
||||
Mfsprg,
|
||||
Mtibatu,
|
||||
Mfibatu,
|
||||
Mtibatl,
|
||||
Mfibatl,
|
||||
Mtdbatu,
|
||||
Mfdbatu,
|
||||
Mtdbatl,
|
||||
Mfdbatl,
|
||||
Nop,
|
||||
Li,
|
||||
Lis,
|
||||
La,
|
||||
Mr,
|
||||
Not,
|
||||
Mtcr,
|
||||
Mfspr,
|
||||
Mftb,
|
||||
Mtspr,
|
||||
LastMnemonic = Mtspr,
|
||||
InvalidMnemonic
|
||||
};
|
||||
} // namespace Common::GekkoAssembler
|
1482
Source/Core/Common/Assembler/AssemblerTables.cpp
Normal file
1482
Source/Core/Common/Assembler/AssemblerTables.cpp
Normal file
File diff suppressed because it is too large
Load diff
152
Source/Core/Common/Assembler/AssemblerTables.h
Normal file
152
Source/Core/Common/Assembler/AssemblerTables.h
Normal file
|
@ -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
|
126
Source/Core/Common/Assembler/CaseInsensitiveDict.h
Normal file
126
Source/Core/Common/Assembler/CaseInsensitiveDict.h
Normal file
|
@ -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
|
189
Source/Core/Common/Assembler/GekkoAssembler.cpp
Normal file
189
Source/Core/Common/Assembler/GekkoAssembler.cpp
Normal file
|
@ -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
|
29
Source/Core/Common/Assembler/GekkoAssembler.h
Normal file
29
Source/Core/Common/Assembler/GekkoAssembler.h
Normal file
|
@ -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
|
832
Source/Core/Common/Assembler/GekkoIRGen.cpp
Normal file
832
Source/Core/Common/Assembler/GekkoIRGen.cpp
Normal file
|
@ -0,0 +1,832 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/Assembler/GekkoIRGen.h"
|
||||
|
||||
#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(BitCast<u32>(val));
|
||||
}
|
||||
else
|
||||
{
|
||||
// std::is_same_v<T, double>
|
||||
static_assert(sizeof(double) == sizeof(u64));
|
||||
AddBytes(BitCast<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
|
50
Source/Core/Common/Assembler/GekkoIRGen.h
Normal file
50
Source/Core/Common/Assembler/GekkoIRGen.h
Normal file
|
@ -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
|
794
Source/Core/Common/Assembler/GekkoLexer.cpp
Normal file
794
Source/Core/Common/Assembler/GekkoLexer.cpp
Normal file
|
@ -0,0 +1,794 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/Assembler/GekkoLexer.h"
|
||||
|
||||
#include "Common/Assert.h"
|
||||
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
|
||||
namespace Common::GekkoAssembler::detail
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr bool IsOctal(char c)
|
||||
{
|
||||
return c >= '0' && c <= '7';
|
||||
}
|
||||
|
||||
constexpr bool IsBinary(char c)
|
||||
{
|
||||
return c == '0' || c == '1';
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr T ConvertNib(char c)
|
||||
{
|
||||
if (c >= 'a' && c <= 'f')
|
||||
{
|
||||
return static_cast<T>(c - 'a' + 10);
|
||||
}
|
||||
if (c >= 'A' && c <= 'F')
|
||||
{
|
||||
return static_cast<T>(c - 'A' + 10);
|
||||
}
|
||||
return static_cast<T>(c - '0');
|
||||
}
|
||||
|
||||
constexpr TokenType SingleCharToken(char ch)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case ',':
|
||||
return TokenType::Comma;
|
||||
case '(':
|
||||
return TokenType::Lparen;
|
||||
case ')':
|
||||
return TokenType::Rparen;
|
||||
case '|':
|
||||
return TokenType::Pipe;
|
||||
case '^':
|
||||
return TokenType::Caret;
|
||||
case '&':
|
||||
return TokenType::Ampersand;
|
||||
case '+':
|
||||
return TokenType::Plus;
|
||||
case '-':
|
||||
return TokenType::Minus;
|
||||
case '*':
|
||||
return TokenType::Star;
|
||||
case '/':
|
||||
return TokenType::Slash;
|
||||
case '~':
|
||||
return TokenType::Tilde;
|
||||
case '@':
|
||||
return TokenType::At;
|
||||
case ':':
|
||||
return TokenType::Colon;
|
||||
case '`':
|
||||
return TokenType::Grave;
|
||||
case '.':
|
||||
return TokenType::Dot;
|
||||
case '\0':
|
||||
return TokenType::Eof;
|
||||
case '\n':
|
||||
return TokenType::Eol;
|
||||
default:
|
||||
return TokenType::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert a string literal into its raw-data form
|
||||
template <typename Cont>
|
||||
void ConvertStringLiteral(std::string_view literal, std::back_insert_iterator<Cont> out_it)
|
||||
{
|
||||
for (size_t i = 1; i < literal.size() - 1;)
|
||||
{
|
||||
if (literal[i] == '\\')
|
||||
{
|
||||
++i;
|
||||
if (IsOctal(literal[i]))
|
||||
{
|
||||
// Octal escape
|
||||
char octal_escape = 0;
|
||||
for (char c = literal[i]; IsOctal(c); c = literal[++i])
|
||||
{
|
||||
octal_escape = (octal_escape << 3) + (c - '0');
|
||||
}
|
||||
out_it = static_cast<u8>(octal_escape);
|
||||
}
|
||||
else if (literal[i] == 'x')
|
||||
{
|
||||
// Hex escape
|
||||
char hex_escape = 0;
|
||||
for (char c = literal[++i]; std::isxdigit(c); c = literal[++i])
|
||||
{
|
||||
hex_escape = (hex_escape << 4) + ConvertNib<char>(c);
|
||||
}
|
||||
out_it = static_cast<u8>(hex_escape);
|
||||
}
|
||||
else
|
||||
{
|
||||
char simple_escape;
|
||||
switch (literal[i])
|
||||
{
|
||||
case '\'':
|
||||
simple_escape = '\x27';
|
||||
break;
|
||||
case '"':
|
||||
simple_escape = '\x22';
|
||||
break;
|
||||
case '?':
|
||||
simple_escape = '\x3f';
|
||||
break;
|
||||
case '\\':
|
||||
simple_escape = '\x5c';
|
||||
break;
|
||||
case 'a':
|
||||
simple_escape = '\x07';
|
||||
break;
|
||||
case 'b':
|
||||
simple_escape = '\x08';
|
||||
break;
|
||||
case 'f':
|
||||
simple_escape = '\x0c';
|
||||
break;
|
||||
case 'n':
|
||||
simple_escape = '\x0a';
|
||||
break;
|
||||
case 'r':
|
||||
simple_escape = '\x0d';
|
||||
break;
|
||||
case 't':
|
||||
simple_escape = '\x09';
|
||||
break;
|
||||
case 'v':
|
||||
simple_escape = '\x0b';
|
||||
break;
|
||||
default:
|
||||
simple_escape = literal[i];
|
||||
break;
|
||||
}
|
||||
out_it = static_cast<u8>(simple_escape);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
out_it = static_cast<u8>(literal[i]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::optional<T> EvalIntegral(TokenType tp, std::string_view val)
|
||||
{
|
||||
constexpr auto hex_step = [](T acc, char c) { return acc << 4 | ConvertNib<T>(c); };
|
||||
constexpr auto dec_step = [](T acc, char c) { return acc * 10 + (c - '0'); };
|
||||
constexpr auto oct_step = [](T acc, char c) { return acc << 3 | (c - '0'); };
|
||||
constexpr auto bin_step = [](T acc, char c) { return acc << 1 | (c - '0'); };
|
||||
|
||||
switch (tp)
|
||||
{
|
||||
case TokenType::HexadecimalLit:
|
||||
return std::accumulate(val.begin() + 2, val.end(), T{0}, hex_step);
|
||||
case TokenType::DecimalLit:
|
||||
return std::accumulate(val.begin(), val.end(), T{0}, dec_step);
|
||||
case TokenType::OctalLit:
|
||||
return std::accumulate(val.begin() + 1, val.end(), T{0}, oct_step);
|
||||
case TokenType::BinaryLit:
|
||||
return std::accumulate(val.begin() + 2, val.end(), T{0}, bin_step);
|
||||
case TokenType::GPR:
|
||||
case TokenType::FPR:
|
||||
return std::accumulate(val.begin() + 1, val.end(), T{0}, dec_step);
|
||||
case TokenType::CRField:
|
||||
return std::accumulate(val.begin() + 2, val.end(), T{0}, dec_step);
|
||||
case TokenType::SPR:
|
||||
return static_cast<T>(*sprg_map.Find(val));
|
||||
case TokenType::Lt:
|
||||
return T{0};
|
||||
case TokenType::Gt:
|
||||
return T{1};
|
||||
case TokenType::Eq:
|
||||
return T{2};
|
||||
case TokenType::So:
|
||||
return T{3};
|
||||
default:
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ConvertStringLiteral(std::string_view literal, std::vector<u8>* out_vec)
|
||||
{
|
||||
ConvertStringLiteral(literal, std::back_inserter(*out_vec));
|
||||
}
|
||||
|
||||
std::string_view TokenTypeToStr(TokenType tp)
|
||||
{
|
||||
switch (tp)
|
||||
{
|
||||
case TokenType::GPR:
|
||||
return "GPR";
|
||||
case TokenType::FPR:
|
||||
return "FPR";
|
||||
case TokenType::SPR:
|
||||
return "SPR";
|
||||
case TokenType::CRField:
|
||||
return "CR Field";
|
||||
case TokenType::Lt:
|
||||
case TokenType::Gt:
|
||||
case TokenType::Eq:
|
||||
case TokenType::So:
|
||||
return "CR Bit";
|
||||
case TokenType::Identifier:
|
||||
return "Identifier";
|
||||
case TokenType::StringLit:
|
||||
return "String Literal";
|
||||
case TokenType::DecimalLit:
|
||||
return "Decimal Literal";
|
||||
case TokenType::BinaryLit:
|
||||
return "Binary Literal";
|
||||
case TokenType::HexadecimalLit:
|
||||
return "Hexadecimal Literal";
|
||||
case TokenType::OctalLit:
|
||||
return "Octal Literal";
|
||||
case TokenType::FloatLit:
|
||||
return "Float Literal";
|
||||
case TokenType::Invalid:
|
||||
return "Invalid";
|
||||
case TokenType::Lsh:
|
||||
return "<<";
|
||||
case TokenType::Rsh:
|
||||
return ">>";
|
||||
case TokenType::Comma:
|
||||
return ",";
|
||||
case TokenType::Lparen:
|
||||
return "(";
|
||||
case TokenType::Rparen:
|
||||
return ")";
|
||||
case TokenType::Pipe:
|
||||
return "|";
|
||||
case TokenType::Caret:
|
||||
return "^";
|
||||
case TokenType::Ampersand:
|
||||
return "&";
|
||||
case TokenType::Plus:
|
||||
return "+";
|
||||
case TokenType::Minus:
|
||||
return "-";
|
||||
case TokenType::Star:
|
||||
return "*";
|
||||
case TokenType::Slash:
|
||||
return "/";
|
||||
case TokenType::Tilde:
|
||||
return "~";
|
||||
case TokenType::At:
|
||||
return "@";
|
||||
case TokenType::Colon:
|
||||
return ":";
|
||||
case TokenType::Grave:
|
||||
return "`";
|
||||
case TokenType::Dot:
|
||||
return ".";
|
||||
case TokenType::Eof:
|
||||
return "End of File";
|
||||
case TokenType::Eol:
|
||||
return "End of Line";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view AssemblerToken::TypeStr() const
|
||||
{
|
||||
return TokenTypeToStr(token_type);
|
||||
}
|
||||
|
||||
std::string_view AssemblerToken::ValStr() const
|
||||
{
|
||||
switch (token_type)
|
||||
{
|
||||
case TokenType::Eol:
|
||||
return "<EOL>";
|
||||
case TokenType::Eof:
|
||||
return "<EOF>";
|
||||
default:
|
||||
return token_val;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
std::optional<float> AssemblerToken::EvalToken() const
|
||||
{
|
||||
if (token_type == TokenType::FloatLit)
|
||||
{
|
||||
return std::stof(std::string(token_val));
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::optional<double> AssemblerToken::EvalToken() const
|
||||
{
|
||||
if (token_type == TokenType::FloatLit)
|
||||
{
|
||||
return std::stod(std::string(token_val));
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <>
|
||||
std::optional<u8> AssemblerToken::EvalToken() const
|
||||
{
|
||||
return EvalIntegral<u8>(token_type, token_val);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::optional<u16> AssemblerToken::EvalToken() const
|
||||
{
|
||||
return EvalIntegral<u16>(token_type, token_val);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::optional<u32> AssemblerToken::EvalToken() const
|
||||
{
|
||||
return EvalIntegral<u32>(token_type, token_val);
|
||||
}
|
||||
|
||||
template <>
|
||||
std::optional<u64> AssemblerToken::EvalToken() const
|
||||
{
|
||||
return EvalIntegral<u64>(token_type, token_val);
|
||||
}
|
||||
|
||||
size_t Lexer::LineNumber() const
|
||||
{
|
||||
return m_lexed_tokens.empty() ? m_pos.line : TagOf(m_lexed_tokens.front()).line;
|
||||
}
|
||||
|
||||
size_t Lexer::ColNumber() const
|
||||
{
|
||||
return m_lexed_tokens.empty() ? m_pos.col : TagOf(m_lexed_tokens.front()).col;
|
||||
}
|
||||
|
||||
std::string_view Lexer::CurrentLine() const
|
||||
{
|
||||
const size_t line_index =
|
||||
m_lexed_tokens.empty() ? m_pos.index : TagOf(m_lexed_tokens.front()).index;
|
||||
size_t begin_index = line_index == 0 ? 0 : line_index - 1;
|
||||
for (; begin_index > 0; begin_index--)
|
||||
{
|
||||
if (m_lex_string[begin_index] == '\n')
|
||||
{
|
||||
begin_index++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
size_t end_index = begin_index;
|
||||
for (; end_index < m_lex_string.size(); end_index++)
|
||||
{
|
||||
if (m_lex_string[end_index] == '\n')
|
||||
{
|
||||
end_index++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return m_lex_string.substr(begin_index, end_index - begin_index);
|
||||
}
|
||||
|
||||
void Lexer::SetIdentifierMatchRule(IdentifierMatchRule set)
|
||||
{
|
||||
FeedbackTokens();
|
||||
m_match_rule = set;
|
||||
}
|
||||
|
||||
const Tagged<CursorPosition, AssemblerToken>& Lexer::LookaheadTagRef(size_t num_fwd) const
|
||||
{
|
||||
while (m_lexed_tokens.size() < num_fwd)
|
||||
{
|
||||
LookaheadRef();
|
||||
}
|
||||
return m_lexed_tokens[num_fwd];
|
||||
}
|
||||
|
||||
AssemblerToken Lexer::Lookahead() const
|
||||
{
|
||||
if (m_lexed_tokens.empty())
|
||||
{
|
||||
CursorPosition pos_pre = m_pos;
|
||||
m_lexed_tokens.emplace_back(pos_pre, LexSingle());
|
||||
}
|
||||
return ValueOf(m_lexed_tokens.front());
|
||||
}
|
||||
|
||||
const AssemblerToken& Lexer::LookaheadRef() const
|
||||
{
|
||||
if (m_lexed_tokens.empty())
|
||||
{
|
||||
CursorPosition pos_pre = m_pos;
|
||||
m_lexed_tokens.emplace_back(pos_pre, LexSingle());
|
||||
}
|
||||
return ValueOf(m_lexed_tokens.front());
|
||||
}
|
||||
|
||||
TokenType Lexer::LookaheadType() const
|
||||
{
|
||||
return LookaheadRef().token_type;
|
||||
}
|
||||
|
||||
AssemblerToken Lexer::LookaheadFloat() const
|
||||
{
|
||||
FeedbackTokens();
|
||||
SkipWs();
|
||||
|
||||
CursorPosition pos_pre = m_pos;
|
||||
ScanStart();
|
||||
|
||||
std::optional<std::string_view> failure_reason = RunDfa(float_dfa);
|
||||
|
||||
// Special case: lex at least a single char for no matches for errors to make sense
|
||||
if (m_scan_pos.index == pos_pre.index)
|
||||
{
|
||||
Step();
|
||||
}
|
||||
|
||||
std::string_view tok_str = ScanFinishOut();
|
||||
AssemblerToken tok;
|
||||
if (!failure_reason)
|
||||
{
|
||||
tok = AssemblerToken{
|
||||
TokenType::FloatLit,
|
||||
tok_str,
|
||||
"",
|
||||
Interval{0, 0},
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
tok = AssemblerToken{
|
||||
TokenType::Invalid,
|
||||
tok_str,
|
||||
*failure_reason,
|
||||
Interval{0, tok_str.length()},
|
||||
};
|
||||
}
|
||||
|
||||
m_lexed_tokens.emplace_back(pos_pre, tok);
|
||||
return tok;
|
||||
}
|
||||
|
||||
void Lexer::Eat()
|
||||
{
|
||||
if (m_lexed_tokens.empty())
|
||||
{
|
||||
LexSingle();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lexed_tokens.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void Lexer::EatAndReset()
|
||||
{
|
||||
Eat();
|
||||
SetIdentifierMatchRule(IdentifierMatchRule::Typical);
|
||||
}
|
||||
|
||||
std::optional<std::string_view> Lexer::RunDfa(const std::vector<DfaNode>& dfa) const
|
||||
{
|
||||
size_t dfa_index = 0;
|
||||
bool transition_found;
|
||||
do
|
||||
{
|
||||
transition_found = false;
|
||||
if (Peek() == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const DfaNode& n = dfa[dfa_index];
|
||||
for (auto&& edge : n.edges)
|
||||
{
|
||||
if (edge.first(Peek()))
|
||||
{
|
||||
transition_found = true;
|
||||
dfa_index = edge.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (transition_found)
|
||||
{
|
||||
Step();
|
||||
}
|
||||
} while (transition_found);
|
||||
|
||||
return dfa[dfa_index].match_failure_reason;
|
||||
}
|
||||
|
||||
void Lexer::SkipWs() const
|
||||
{
|
||||
ScanStart();
|
||||
for (char c = Peek(); std::isspace(c) && c != '\n'; c = Step().Peek())
|
||||
{
|
||||
}
|
||||
if (Peek() == '#')
|
||||
{
|
||||
while (Peek() != '\n' && Peek() != '\0')
|
||||
{
|
||||
Step();
|
||||
}
|
||||
}
|
||||
ScanFinish();
|
||||
}
|
||||
|
||||
void Lexer::FeedbackTokens() const
|
||||
{
|
||||
if (m_lexed_tokens.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_pos = m_scan_pos = TagOf(m_lexed_tokens.front());
|
||||
m_lexed_tokens.clear();
|
||||
}
|
||||
|
||||
bool Lexer::IdentifierHeadExtra(char h) const
|
||||
{
|
||||
switch (m_match_rule)
|
||||
{
|
||||
case IdentifierMatchRule::Typical:
|
||||
case IdentifierMatchRule::Mnemonic:
|
||||
return false;
|
||||
case IdentifierMatchRule::Directive:
|
||||
return std::isdigit(h);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Lexer::IdentifierExtra(char c) const
|
||||
{
|
||||
switch (m_match_rule)
|
||||
{
|
||||
case IdentifierMatchRule::Typical:
|
||||
case IdentifierMatchRule::Directive:
|
||||
return false;
|
||||
case IdentifierMatchRule::Mnemonic:
|
||||
return c == '+' || c == '-' || c == '.';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Lexer::ScanStart() const
|
||||
{
|
||||
m_scan_pos = m_pos;
|
||||
}
|
||||
|
||||
void Lexer::ScanFinish() const
|
||||
{
|
||||
m_pos = m_scan_pos;
|
||||
}
|
||||
|
||||
std::string_view Lexer::ScanFinishOut() const
|
||||
{
|
||||
const size_t start = m_pos.index;
|
||||
m_pos = m_scan_pos;
|
||||
return m_lex_string.substr(start, m_scan_pos.index - start);
|
||||
}
|
||||
|
||||
char Lexer::Peek() const
|
||||
{
|
||||
if (m_scan_pos.index >= m_lex_string.length())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return m_lex_string[m_scan_pos.index];
|
||||
}
|
||||
|
||||
const Lexer& Lexer::Step() const
|
||||
{
|
||||
if (m_scan_pos.index >= m_lex_string.length())
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (Peek() == '\n')
|
||||
{
|
||||
m_scan_pos.line++;
|
||||
m_scan_pos.col = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_scan_pos.col++;
|
||||
}
|
||||
m_scan_pos.index++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TokenType Lexer::LexStringLit(std::string_view& invalid_reason, Interval& invalid_region) const
|
||||
{
|
||||
// The open quote has alread been matched
|
||||
const size_t string_start = m_scan_pos.index - 1;
|
||||
TokenType token_type = TokenType::StringLit;
|
||||
|
||||
std::optional<std::string_view> failure_reason = RunDfa(string_dfa);
|
||||
|
||||
if (failure_reason)
|
||||
{
|
||||
token_type = TokenType::Invalid;
|
||||
invalid_reason = *failure_reason;
|
||||
invalid_region = Interval{0, m_scan_pos.index - string_start};
|
||||
}
|
||||
|
||||
return token_type;
|
||||
}
|
||||
|
||||
TokenType Lexer::ClassifyAlnum() const
|
||||
{
|
||||
const std::string_view alnum = m_lex_string.substr(m_pos.index, m_scan_pos.index - m_pos.index);
|
||||
constexpr auto valid_regnum = [](std::string_view rn) {
|
||||
if (rn.length() == 1 && std::isdigit(rn[0]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (rn.length() == 2 && std::isdigit(rn[0]) && std::isdigit(rn[1]))
|
||||
{
|
||||
if (rn[0] == '1' || rn[0] == '2')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (rn[0] == '3')
|
||||
{
|
||||
return rn[1] <= '2';
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
constexpr auto eq_nocase = [](std::string_view str, std::string_view lwr) {
|
||||
auto it_l = str.cbegin(), it_r = lwr.cbegin();
|
||||
for (; it_l != str.cend() && it_r != lwr.cend(); it_l++, it_r++)
|
||||
{
|
||||
if (std::tolower(*it_l) != *it_r)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return it_l == str.end() && it_r == lwr.end();
|
||||
};
|
||||
|
||||
if (std::tolower(alnum[0]) == 'r' && valid_regnum(alnum.substr(1)))
|
||||
{
|
||||
return TokenType::GPR;
|
||||
}
|
||||
else if (std::tolower(alnum[0]) == 'f' && valid_regnum(alnum.substr(1)))
|
||||
{
|
||||
return TokenType::FPR;
|
||||
}
|
||||
else if (alnum.length() == 3 && eq_nocase(alnum.substr(0, 2), "cr") && alnum[2] >= '0' &&
|
||||
alnum[2] <= '7')
|
||||
{
|
||||
return TokenType::CRField;
|
||||
}
|
||||
else if (eq_nocase(alnum, "lt"))
|
||||
{
|
||||
return TokenType::Lt;
|
||||
}
|
||||
else if (eq_nocase(alnum, "gt"))
|
||||
{
|
||||
return TokenType::Gt;
|
||||
}
|
||||
else if (eq_nocase(alnum, "eq"))
|
||||
{
|
||||
return TokenType::Eq;
|
||||
}
|
||||
else if (eq_nocase(alnum, "so"))
|
||||
{
|
||||
return TokenType::So;
|
||||
}
|
||||
else if (sprg_map.Find(alnum) != nullptr)
|
||||
{
|
||||
return TokenType::SPR;
|
||||
}
|
||||
return TokenType::Identifier;
|
||||
}
|
||||
|
||||
AssemblerToken Lexer::LexSingle() const
|
||||
{
|
||||
SkipWs();
|
||||
|
||||
ScanStart();
|
||||
const char h = Peek();
|
||||
|
||||
TokenType token_type;
|
||||
std::string_view invalid_reason = "";
|
||||
Interval invalid_region = Interval{0, 0};
|
||||
|
||||
Step();
|
||||
|
||||
if (std::isalpha(h) || h == '_' || IdentifierHeadExtra(h))
|
||||
{
|
||||
for (char c = Peek(); std::isalnum(c) || c == '_' || IdentifierExtra(c); c = Step().Peek())
|
||||
{
|
||||
}
|
||||
|
||||
token_type = ClassifyAlnum();
|
||||
}
|
||||
else if (h == '"')
|
||||
{
|
||||
token_type = LexStringLit(invalid_reason, invalid_region);
|
||||
}
|
||||
else if (h == '0')
|
||||
{
|
||||
const char imm_type = Peek();
|
||||
|
||||
if (imm_type == 'x')
|
||||
{
|
||||
token_type = TokenType::HexadecimalLit;
|
||||
Step();
|
||||
for (char c = Peek(); std::isxdigit(c); c = Step().Peek())
|
||||
{
|
||||
}
|
||||
}
|
||||
else if (imm_type == 'b')
|
||||
{
|
||||
token_type = TokenType::BinaryLit;
|
||||
Step();
|
||||
for (char c = Peek(); IsBinary(c); c = Step().Peek())
|
||||
{
|
||||
}
|
||||
}
|
||||
else if (IsOctal(imm_type))
|
||||
{
|
||||
token_type = TokenType::OctalLit;
|
||||
for (char c = Peek(); IsOctal(c); c = Step().Peek())
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
token_type = TokenType::DecimalLit;
|
||||
}
|
||||
}
|
||||
else if (std::isdigit(h))
|
||||
{
|
||||
for (char c = Peek(); std::isdigit(c); c = Step().Peek())
|
||||
{
|
||||
}
|
||||
token_type = TokenType::DecimalLit;
|
||||
}
|
||||
else if (h == '<' || h == '>')
|
||||
{
|
||||
// Special case for two-character operators
|
||||
const char second_ch = Peek();
|
||||
if (second_ch == h)
|
||||
{
|
||||
Step();
|
||||
token_type = second_ch == '<' ? TokenType::Lsh : TokenType::Rsh;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_type = TokenType::Invalid;
|
||||
invalid_reason = "Unrecognized character";
|
||||
invalid_region = Interval{0, 1};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
token_type = SingleCharToken(h);
|
||||
if (token_type == TokenType::Invalid)
|
||||
{
|
||||
invalid_reason = "Unrecognized character";
|
||||
invalid_region = Interval{0, 1};
|
||||
}
|
||||
}
|
||||
|
||||
AssemblerToken new_tok = {token_type, ScanFinishOut(), invalid_reason, invalid_region};
|
||||
SkipWs();
|
||||
return new_tok;
|
||||
}
|
||||
} // namespace Common::GekkoAssembler::detail
|
188
Source/Core/Common/Assembler/GekkoLexer.h
Normal file
188
Source/Core/Common/Assembler/GekkoLexer.h
Normal file
|
@ -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
|
885
Source/Core/Common/Assembler/GekkoParser.cpp
Normal file
885
Source/Core/Common/Assembler/GekkoParser.cpp
Normal file
|
@ -0,0 +1,885 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/Assembler/GekkoParser.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/Assembler/AssemblerShared.h"
|
||||
#include "Common/Assembler/AssemblerTables.h"
|
||||
#include "Common/Assembler/GekkoLexer.h"
|
||||
#include "Common/Assert.h"
|
||||
|
||||
namespace Common::GekkoAssembler::detail
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool MatchOperandFirst(const AssemblerToken& tok)
|
||||
{
|
||||
switch (tok.token_type)
|
||||
{
|
||||
case TokenType::Minus:
|
||||
case TokenType::Tilde:
|
||||
case TokenType::Lparen:
|
||||
case TokenType::Grave:
|
||||
case TokenType::Identifier:
|
||||
case TokenType::DecimalLit:
|
||||
case TokenType::OctalLit:
|
||||
case TokenType::HexadecimalLit:
|
||||
case TokenType::BinaryLit:
|
||||
case TokenType::Dot:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ParseImm(ParseState* state)
|
||||
{
|
||||
AssemblerToken tok = state->lexer.Lookahead();
|
||||
switch (tok.token_type)
|
||||
{
|
||||
case TokenType::HexadecimalLit:
|
||||
state->plugin.OnTerminal(Terminal::Hex, tok);
|
||||
break;
|
||||
case TokenType::DecimalLit:
|
||||
state->plugin.OnTerminal(Terminal::Dec, tok);
|
||||
break;
|
||||
case TokenType::OctalLit:
|
||||
state->plugin.OnTerminal(Terminal::Oct, tok);
|
||||
break;
|
||||
case TokenType::BinaryLit:
|
||||
state->plugin.OnTerminal(Terminal::Bin, tok);
|
||||
break;
|
||||
default:
|
||||
state->EmitErrorHere(fmt::format("Invalid {} with value '{}'", tok.TypeStr(), tok.ValStr()));
|
||||
return;
|
||||
}
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state->lexer.Eat();
|
||||
}
|
||||
|
||||
void ParseId(ParseState* state)
|
||||
{
|
||||
AssemblerToken tok = state->lexer.Lookahead();
|
||||
if (tok.token_type == TokenType::Identifier)
|
||||
{
|
||||
state->plugin.OnTerminal(Terminal::Id, tok);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state->lexer.Eat();
|
||||
}
|
||||
else
|
||||
{
|
||||
state->EmitErrorHere(fmt::format("Expected an identifier, but found '{}'", tok.ValStr()));
|
||||
}
|
||||
}
|
||||
|
||||
void ParseIdLocation(ParseState* state)
|
||||
{
|
||||
std::array<AssemblerToken, 3> toks;
|
||||
state->lexer.LookaheadN(&toks);
|
||||
|
||||
if (toks[1].token_type == TokenType::At)
|
||||
{
|
||||
if (toks[2].token_val == "ha")
|
||||
{
|
||||
state->plugin.OnHiaddr(toks[0].token_val);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state->lexer.EatN<3>();
|
||||
return;
|
||||
}
|
||||
else if (toks[2].token_val == "l")
|
||||
{
|
||||
state->plugin.OnLoaddr(toks[0].token_val);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state->lexer.EatN<3>();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ParseId(state);
|
||||
}
|
||||
|
||||
void ParsePpcBuiltin(ParseState* state)
|
||||
{
|
||||
AssemblerToken tok = state->lexer.Lookahead();
|
||||
switch (tok.token_type)
|
||||
{
|
||||
case TokenType::GPR:
|
||||
state->plugin.OnTerminal(Terminal::GPR, tok);
|
||||
break;
|
||||
case TokenType::FPR:
|
||||
state->plugin.OnTerminal(Terminal::FPR, tok);
|
||||
break;
|
||||
case TokenType::SPR:
|
||||
state->plugin.OnTerminal(Terminal::SPR, tok);
|
||||
break;
|
||||
case TokenType::CRField:
|
||||
state->plugin.OnTerminal(Terminal::CRField, tok);
|
||||
break;
|
||||
case TokenType::Lt:
|
||||
state->plugin.OnTerminal(Terminal::Lt, tok);
|
||||
break;
|
||||
case TokenType::Gt:
|
||||
state->plugin.OnTerminal(Terminal::Gt, tok);
|
||||
break;
|
||||
case TokenType::Eq:
|
||||
state->plugin.OnTerminal(Terminal::Eq, tok);
|
||||
break;
|
||||
case TokenType::So:
|
||||
state->plugin.OnTerminal(Terminal::So, tok);
|
||||
break;
|
||||
default:
|
||||
state->EmitErrorHere(
|
||||
fmt::format("Unexpected token '{}' in ppc builtin", state->lexer.LookaheadRef().ValStr()));
|
||||
break;
|
||||
}
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state->lexer.Eat();
|
||||
}
|
||||
|
||||
void ParseBaseexpr(ParseState* state)
|
||||
{
|
||||
TokenType tok = state->lexer.LookaheadType();
|
||||
switch (tok)
|
||||
{
|
||||
case TokenType::HexadecimalLit:
|
||||
case TokenType::DecimalLit:
|
||||
case TokenType::OctalLit:
|
||||
case TokenType::BinaryLit:
|
||||
ParseImm(state);
|
||||
break;
|
||||
|
||||
case TokenType::Identifier:
|
||||
ParseIdLocation(state);
|
||||
break;
|
||||
|
||||
case TokenType::GPR:
|
||||
case TokenType::FPR:
|
||||
case TokenType::SPR:
|
||||
case TokenType::CRField:
|
||||
case TokenType::Lt:
|
||||
case TokenType::Gt:
|
||||
case TokenType::Eq:
|
||||
case TokenType::So:
|
||||
ParsePpcBuiltin(state);
|
||||
break;
|
||||
|
||||
case TokenType::Dot:
|
||||
state->plugin.OnTerminal(Terminal::Dot, state->lexer.Lookahead());
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state->lexer.Eat();
|
||||
break;
|
||||
|
||||
default:
|
||||
state->EmitErrorHere(
|
||||
fmt::format("Unexpected token '{}' in expression", state->lexer.LookaheadRef().ValStr()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ParseBitor(ParseState* state);
|
||||
void ParseParen(ParseState* state)
|
||||
{
|
||||
if (state->HasToken(TokenType::Lparen))
|
||||
{
|
||||
state->plugin.OnOpenParen(ParenType::Normal);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state->lexer.Eat();
|
||||
ParseBitor(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (state->HasToken(TokenType::Rparen))
|
||||
{
|
||||
state->plugin.OnCloseParen(ParenType::Normal);
|
||||
}
|
||||
state->ParseToken(TokenType::Rparen);
|
||||
}
|
||||
else if (state->HasToken(TokenType::Grave))
|
||||
{
|
||||
state->plugin.OnOpenParen(ParenType::RelConv);
|
||||
|
||||
state->lexer.Eat();
|
||||
ParseBitor(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (state->HasToken(TokenType::Grave))
|
||||
{
|
||||
state->plugin.OnCloseParen(ParenType::RelConv);
|
||||
}
|
||||
state->ParseToken(TokenType::Grave);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseBaseexpr(state);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseUnary(ParseState* state)
|
||||
{
|
||||
TokenType tok = state->lexer.LookaheadType();
|
||||
if (tok == TokenType::Minus || tok == TokenType::Tilde)
|
||||
{
|
||||
state->lexer.Eat();
|
||||
ParseUnary(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tok == TokenType::Minus)
|
||||
{
|
||||
state->plugin.OnOperator(AsmOp::Neg);
|
||||
}
|
||||
else
|
||||
{
|
||||
state->plugin.OnOperator(AsmOp::Not);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseParen(state);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseMultiplication(ParseState* state)
|
||||
{
|
||||
ParseUnary(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TokenType tok = state->lexer.LookaheadType();
|
||||
while (tok == TokenType::Star || tok == TokenType::Slash)
|
||||
{
|
||||
state->lexer.Eat();
|
||||
ParseUnary(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tok == TokenType::Star)
|
||||
{
|
||||
state->plugin.OnOperator(AsmOp::Mul);
|
||||
}
|
||||
else
|
||||
{
|
||||
state->plugin.OnOperator(AsmOp::Div);
|
||||
}
|
||||
tok = state->lexer.LookaheadType();
|
||||
}
|
||||
}
|
||||
|
||||
void ParseAddition(ParseState* state)
|
||||
{
|
||||
ParseMultiplication(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TokenType tok = state->lexer.LookaheadType();
|
||||
while (tok == TokenType::Plus || tok == TokenType::Minus)
|
||||
{
|
||||
state->lexer.Eat();
|
||||
ParseMultiplication(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tok == TokenType::Plus)
|
||||
{
|
||||
state->plugin.OnOperator(AsmOp::Add);
|
||||
}
|
||||
else
|
||||
{
|
||||
state->plugin.OnOperator(AsmOp::Sub);
|
||||
}
|
||||
tok = state->lexer.LookaheadType();
|
||||
}
|
||||
}
|
||||
|
||||
void ParseShift(ParseState* state)
|
||||
{
|
||||
ParseAddition(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TokenType tok = state->lexer.LookaheadType();
|
||||
while (tok == TokenType::Lsh || tok == TokenType::Rsh)
|
||||
{
|
||||
state->lexer.Eat();
|
||||
ParseAddition(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tok == TokenType::Lsh)
|
||||
{
|
||||
state->plugin.OnOperator(AsmOp::Lsh);
|
||||
}
|
||||
else
|
||||
{
|
||||
state->plugin.OnOperator(AsmOp::Rsh);
|
||||
}
|
||||
tok = state->lexer.LookaheadType();
|
||||
}
|
||||
}
|
||||
|
||||
void ParseBitand(ParseState* state)
|
||||
{
|
||||
ParseShift(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (state->HasToken(TokenType::Ampersand))
|
||||
{
|
||||
state->lexer.Eat();
|
||||
ParseShift(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state->plugin.OnOperator(AsmOp::And);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseBitxor(ParseState* state)
|
||||
{
|
||||
ParseBitand(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (state->HasToken(TokenType::Caret))
|
||||
{
|
||||
state->lexer.Eat();
|
||||
ParseBitand(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state->plugin.OnOperator(AsmOp::Xor);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseBitor(ParseState* state)
|
||||
{
|
||||
ParseBitxor(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (state->HasToken(TokenType::Pipe))
|
||||
{
|
||||
state->lexer.Eat();
|
||||
ParseBitxor(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state->plugin.OnOperator(AsmOp::Or);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseOperand(ParseState* state)
|
||||
{
|
||||
state->plugin.OnOperandPre();
|
||||
ParseBitor(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state->plugin.OnOperandPost();
|
||||
}
|
||||
|
||||
void ParseOperandList(ParseState* state, ParseAlg alg)
|
||||
{
|
||||
if (alg == ParseAlg::None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (alg == ParseAlg::NoneOrOp1)
|
||||
{
|
||||
if (MatchOperandFirst(state->lexer.Lookahead()))
|
||||
{
|
||||
ParseOperand(state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
enum ParseStep
|
||||
{
|
||||
_Operand,
|
||||
_Comma,
|
||||
_Lparen,
|
||||
_Rparen,
|
||||
_OptComma
|
||||
};
|
||||
std::vector<ParseStep> steps;
|
||||
|
||||
switch (alg)
|
||||
{
|
||||
case ParseAlg::Op1:
|
||||
steps = {_Operand};
|
||||
break;
|
||||
case ParseAlg::Op1Or2:
|
||||
steps = {_Operand, _OptComma, _Operand};
|
||||
break;
|
||||
case ParseAlg::Op2Or3:
|
||||
steps = {_Operand, _Comma, _Operand, _OptComma, _Operand};
|
||||
break;
|
||||
case ParseAlg::Op1Off1:
|
||||
steps = {_Operand, _Comma, _Operand, _Lparen, _Operand, _Rparen};
|
||||
break;
|
||||
case ParseAlg::Op2:
|
||||
steps = {_Operand, _Comma, _Operand};
|
||||
break;
|
||||
case ParseAlg::Op3:
|
||||
steps = {_Operand, _Comma, _Operand, _Comma, _Operand};
|
||||
break;
|
||||
case ParseAlg::Op4:
|
||||
steps = {_Operand, _Comma, _Operand, _Comma, _Operand, _Comma, _Operand};
|
||||
break;
|
||||
case ParseAlg::Op5:
|
||||
steps = {_Operand, _Comma, _Operand, _Comma, _Operand, _Comma, _Operand, _Comma, _Operand};
|
||||
break;
|
||||
case ParseAlg::Op1Off1Op2:
|
||||
steps = {_Operand, _Comma, _Operand, _Lparen, _Operand,
|
||||
_Rparen, _Comma, _Operand, _Comma, _Operand};
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
for (ParseStep step : steps)
|
||||
{
|
||||
bool stop_parse = false;
|
||||
switch (step)
|
||||
{
|
||||
case _Operand:
|
||||
ParseOperand(state);
|
||||
break;
|
||||
case _Comma:
|
||||
state->ParseToken(TokenType::Comma);
|
||||
break;
|
||||
case _Lparen:
|
||||
state->ParseToken(TokenType::Lparen);
|
||||
break;
|
||||
case _Rparen:
|
||||
state->ParseToken(TokenType::Rparen);
|
||||
break;
|
||||
case _OptComma:
|
||||
if (state->HasToken(TokenType::Comma))
|
||||
{
|
||||
state->ParseToken(TokenType::Comma);
|
||||
}
|
||||
else
|
||||
{
|
||||
stop_parse = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (stop_parse)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParseInstruction(ParseState* state)
|
||||
{
|
||||
state->lexer.SetIdentifierMatchRule(Lexer::IdentifierMatchRule::Mnemonic);
|
||||
|
||||
AssemblerToken mnemonic_token = state->lexer.Lookahead();
|
||||
if (mnemonic_token.token_type != TokenType::Identifier)
|
||||
{
|
||||
state->lexer.SetIdentifierMatchRule(Lexer::IdentifierMatchRule::Typical);
|
||||
return;
|
||||
}
|
||||
|
||||
ParseInfo const* parse_info = mnemonic_tokens.Find(mnemonic_token.token_val);
|
||||
bool is_extended = false;
|
||||
if (parse_info == nullptr)
|
||||
{
|
||||
parse_info = extended_mnemonic_tokens.Find(mnemonic_token.token_val);
|
||||
if (parse_info == nullptr)
|
||||
{
|
||||
state->EmitErrorHere(
|
||||
fmt::format("Unknown or unsupported mnemonic '{}'", mnemonic_token.ValStr()));
|
||||
return;
|
||||
}
|
||||
is_extended = true;
|
||||
}
|
||||
|
||||
state->plugin.OnInstructionPre(*parse_info, is_extended);
|
||||
|
||||
state->lexer.EatAndReset();
|
||||
|
||||
ParseOperandList(state, parse_info->parse_algorithm);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state->plugin.OnInstructionPost(*parse_info, is_extended);
|
||||
}
|
||||
|
||||
void ParseLabel(ParseState* state)
|
||||
{
|
||||
std::array<AssemblerToken, 2> tokens;
|
||||
state->lexer.LookaheadN(&tokens);
|
||||
|
||||
if (tokens[0].token_type == TokenType::Identifier && tokens[1].token_type == TokenType::Colon)
|
||||
{
|
||||
state->plugin.OnLabelDecl(tokens[0].token_val);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state->lexer.EatN<2>();
|
||||
}
|
||||
}
|
||||
|
||||
void ParseResolvedExpr(ParseState* state)
|
||||
{
|
||||
state->plugin.OnResolvedExprPre();
|
||||
ParseBitor(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state->plugin.OnResolvedExprPost();
|
||||
}
|
||||
|
||||
void ParseExpressionList(ParseState* state)
|
||||
{
|
||||
ParseResolvedExpr(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (state->HasToken(TokenType::Comma))
|
||||
{
|
||||
state->lexer.Eat();
|
||||
ParseResolvedExpr(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParseFloat(ParseState* state)
|
||||
{
|
||||
AssemblerToken flt_token = state->lexer.LookaheadFloat();
|
||||
if (flt_token.token_type != TokenType::FloatLit)
|
||||
{
|
||||
state->EmitErrorHere("Invalid floating point literal");
|
||||
return;
|
||||
}
|
||||
state->plugin.OnTerminal(Terminal::Flt, flt_token);
|
||||
state->lexer.Eat();
|
||||
}
|
||||
|
||||
void ParseFloatList(ParseState* state)
|
||||
{
|
||||
ParseFloat(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (state->HasToken(TokenType::Comma))
|
||||
{
|
||||
state->lexer.Eat();
|
||||
ParseFloat(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParseDefvar(ParseState* state)
|
||||
{
|
||||
AssemblerToken tok = state->lexer.Lookahead();
|
||||
if (tok.token_type == TokenType::Identifier)
|
||||
{
|
||||
state->plugin.OnVarDecl(tok.token_val);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state->lexer.Eat();
|
||||
|
||||
state->ParseToken(TokenType::Comma);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ParseResolvedExpr(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
state->EmitErrorHere(fmt::format("Expected an identifier, but found '{}'", tok.ValStr()));
|
||||
}
|
||||
}
|
||||
|
||||
void ParseString(ParseState* state)
|
||||
{
|
||||
AssemblerToken tok = state->lexer.Lookahead();
|
||||
if (tok.token_type == TokenType::StringLit)
|
||||
{
|
||||
state->plugin.OnTerminal(Terminal::Str, tok);
|
||||
state->lexer.Eat();
|
||||
}
|
||||
else
|
||||
{
|
||||
state->EmitErrorHere(fmt::format("Expected a string literal, but found '{}'", tok.ValStr()));
|
||||
}
|
||||
}
|
||||
|
||||
void ParseDirective(ParseState* state)
|
||||
{
|
||||
// TODO: test directives
|
||||
state->lexer.SetIdentifierMatchRule(Lexer::IdentifierMatchRule::Directive);
|
||||
AssemblerToken tok = state->lexer.Lookahead();
|
||||
if (tok.token_type != TokenType::Identifier)
|
||||
{
|
||||
state->EmitErrorHere(fmt::format("Unexpected token '{}' in directive type", tok.ValStr()));
|
||||
return;
|
||||
}
|
||||
|
||||
GekkoDirective const* directive_enum = directives_map.Find(tok.token_val);
|
||||
if (directive_enum == nullptr)
|
||||
{
|
||||
state->EmitErrorHere(fmt::format("Unknown assembler directive '{}'", tok.ValStr()));
|
||||
return;
|
||||
}
|
||||
|
||||
state->plugin.OnDirectivePre(*directive_enum);
|
||||
|
||||
state->lexer.EatAndReset();
|
||||
switch (*directive_enum)
|
||||
{
|
||||
case GekkoDirective::Byte:
|
||||
case GekkoDirective::_2byte:
|
||||
case GekkoDirective::_4byte:
|
||||
case GekkoDirective::_8byte:
|
||||
ParseExpressionList(state);
|
||||
break;
|
||||
|
||||
case GekkoDirective::Float:
|
||||
case GekkoDirective::Double:
|
||||
ParseFloatList(state);
|
||||
break;
|
||||
|
||||
case GekkoDirective::Locate:
|
||||
case GekkoDirective::Zeros:
|
||||
case GekkoDirective::Skip:
|
||||
ParseResolvedExpr(state);
|
||||
break;
|
||||
|
||||
case GekkoDirective::PadAlign:
|
||||
case GekkoDirective::Align:
|
||||
ParseImm(state);
|
||||
break;
|
||||
|
||||
case GekkoDirective::DefVar:
|
||||
ParseDefvar(state);
|
||||
break;
|
||||
|
||||
case GekkoDirective::Ascii:
|
||||
case GekkoDirective::Asciz:
|
||||
ParseString(state);
|
||||
break;
|
||||
}
|
||||
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
state->plugin.OnDirectivePost(*directive_enum);
|
||||
}
|
||||
|
||||
void ParseLine(ParseState* state)
|
||||
{
|
||||
if (state->HasToken(TokenType::Dot))
|
||||
{
|
||||
state->ParseToken(TokenType::Dot);
|
||||
ParseDirective(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseInstruction(state);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseProgram(ParseState* state)
|
||||
{
|
||||
AssemblerToken tok = state->lexer.Lookahead();
|
||||
if (tok.token_type == TokenType::Eof)
|
||||
{
|
||||
state->eof = true;
|
||||
return;
|
||||
}
|
||||
ParseLabel(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ParseLine(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (!state->eof && !state->error)
|
||||
{
|
||||
tok = state->lexer.Lookahead();
|
||||
if (tok.token_type == TokenType::Eof)
|
||||
{
|
||||
state->eof = true;
|
||||
}
|
||||
else if (tok.token_type == TokenType::Eol)
|
||||
{
|
||||
state->lexer.Eat();
|
||||
ParseLabel(state);
|
||||
if (state->error)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ParseLine(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
state->EmitErrorHere(
|
||||
fmt::format("Unexpected token '{}' where line should have ended", tok.ValStr()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ParseState::ParseState(std::string_view input_str, ParsePlugin& p)
|
||||
: lexer(input_str), plugin(p), eof(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool ParseState::HasToken(TokenType tp) const
|
||||
{
|
||||
return lexer.LookaheadType() == tp;
|
||||
}
|
||||
|
||||
void ParseState::ParseToken(TokenType tp)
|
||||
{
|
||||
AssemblerToken tok = lexer.LookaheadRef();
|
||||
if (tok.token_type == tp)
|
||||
{
|
||||
lexer.Eat();
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitErrorHere(fmt::format("Expected '{}' but found '{}'", TokenTypeToStr(tp), tok.ValStr()));
|
||||
}
|
||||
}
|
||||
|
||||
void ParseState::EmitErrorHere(std::string&& message)
|
||||
{
|
||||
AssemblerToken cur_token = lexer.Lookahead();
|
||||
if (cur_token.token_type == TokenType::Invalid)
|
||||
{
|
||||
error = AssemblerError{
|
||||
std::string(cur_token.invalid_reason),
|
||||
lexer.CurrentLine(),
|
||||
lexer.LineNumber(),
|
||||
lexer.ColNumber() + cur_token.invalid_region.begin,
|
||||
cur_token.invalid_region.len,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
error = AssemblerError{
|
||||
std::move(message), lexer.CurrentLine(), lexer.LineNumber(),
|
||||
lexer.ColNumber(), cur_token.token_val.size(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void ParseWithPlugin(ParsePlugin* plugin, std::string_view input)
|
||||
{
|
||||
ParseState parse_state = ParseState(input, *plugin);
|
||||
plugin->SetOwner(&parse_state);
|
||||
ParseProgram(&parse_state);
|
||||
|
||||
if (parse_state.error)
|
||||
{
|
||||
plugin->OnError();
|
||||
plugin->ForwardError(std::move(*parse_state.error));
|
||||
}
|
||||
else
|
||||
{
|
||||
plugin->PostParseAction();
|
||||
if (parse_state.error)
|
||||
{
|
||||
plugin->OnError();
|
||||
plugin->ForwardError(std::move(*parse_state.error));
|
||||
}
|
||||
}
|
||||
|
||||
plugin->SetOwner(nullptr);
|
||||
}
|
||||
} // namespace Common::GekkoAssembler::detail
|
124
Source/Core/Common/Assembler/GekkoParser.h
Normal file
124
Source/Core/Common/Assembler/GekkoParser.h
Normal file
|
@ -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
|
||||
|
@ -220,7 +232,7 @@ if(_M_ARM_64)
|
|||
ArmFPURoundMode.cpp
|
||||
)
|
||||
else()
|
||||
if(_M_X86) #X86
|
||||
if(_M_X86_64) #X86
|
||||
target_sources(common PRIVATE
|
||||
x64ABI.cpp
|
||||
x64ABI.h
|
||||
|
@ -317,7 +329,7 @@ endif()
|
|||
if(UNIX)
|
||||
# Posix networking code needs to be fixed for Windows
|
||||
add_executable(traversal_server TraversalServer.cpp)
|
||||
target_link_libraries(traversal_server PRIVATE common)
|
||||
target_link_libraries(traversal_server PRIVATE common fmt::fmt)
|
||||
if(SYSTEMD_FOUND)
|
||||
target_link_libraries(traversal_server PRIVATE ${SYSTEMD_LIBRARIES})
|
||||
endif()
|
||||
|
|
|
@ -82,9 +82,14 @@ public:
|
|||
}
|
||||
|
||||
bool IsInSpace(const u8* ptr) const { return ptr >= region && ptr < (region + region_size); }
|
||||
// Cannot currently be undone. Will write protect the entire code region.
|
||||
// Start over if you need to change the code (call FreeCodeSpace(), AllocCodeSpace()).
|
||||
void WriteProtect() { Common::WriteProtectMemory(region, region_size, true); }
|
||||
void WriteProtect(bool allow_execute)
|
||||
{
|
||||
Common::WriteProtectMemory(region, region_size, allow_execute);
|
||||
}
|
||||
void UnWriteProtect(bool allow_execute)
|
||||
{
|
||||
Common::UnWriteProtectMemory(region, region_size, allow_execute);
|
||||
}
|
||||
void ResetCodePtr() { T::SetCodePtr(region, region + region_size); }
|
||||
size_t GetSpaceLeft() const
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
|
@ -108,7 +109,6 @@
|
|||
#define WIIPAD_CONFIG "WiimoteNew.ini"
|
||||
#define GCKEYBOARD_CONFIG "GCKeyNew.ini"
|
||||
#define GFX_CONFIG "GFX.ini"
|
||||
#define DEBUGGER_CONFIG "Debugger.ini"
|
||||
#define LOGGER_CONFIG "Logger.ini"
|
||||
#define DUALSHOCKUDPCLIENT_CONFIG "DSUClient.ini"
|
||||
#define FREELOOK_CONFIG "FreeLook.ini"
|
||||
|
|
|
@ -154,7 +154,6 @@ static const std::map<System, std::string> system_to_name = {
|
|||
{System::GCKeyboard, "GCKeyboard"},
|
||||
{System::GFX, "Graphics"},
|
||||
{System::Logger, "Logger"},
|
||||
{System::Debugger, "Debugger"},
|
||||
{System::SYSCONF, "SYSCONF"},
|
||||
{System::DualShockUDPClient, "DualShockUDPClient"},
|
||||
{System::FreeLook, "FreeLook"},
|
||||
|
|
|
@ -29,7 +29,6 @@ enum class System
|
|||
GCKeyboard,
|
||||
GFX,
|
||||
Logger,
|
||||
Debugger,
|
||||
DualShockUDPClient,
|
||||
FreeLook,
|
||||
Session,
|
||||
|
|
|
@ -250,7 +250,19 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
std::array<__m128i, NUM_ROUND_KEYS> round_keys;
|
||||
// Ensures alignment specifiers are respected.
|
||||
struct XmmReg
|
||||
{
|
||||
__m128i data;
|
||||
|
||||
XmmReg& operator=(const __m128i& m)
|
||||
{
|
||||
data = m;
|
||||
return *this;
|
||||
}
|
||||
operator __m128i() const { return data; }
|
||||
};
|
||||
std::array<XmmReg, NUM_ROUND_KEYS> round_keys;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -166,7 +166,20 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
using WorkBlock = CyclicArray<__m128i, 4>;
|
||||
struct XmmReg
|
||||
{
|
||||
// Allows aliasing attributes to be respected in the
|
||||
// face of templates.
|
||||
__m128i data;
|
||||
|
||||
XmmReg& operator=(const __m128i& d)
|
||||
{
|
||||
data = d;
|
||||
return *this;
|
||||
}
|
||||
operator __m128i() const { return data; }
|
||||
};
|
||||
using WorkBlock = CyclicArray<XmmReg, 4>;
|
||||
|
||||
ATTRIBUTE_TARGET("ssse3")
|
||||
static inline __m128i byterev_16B(__m128i x)
|
||||
|
@ -244,7 +257,7 @@ private:
|
|||
|
||||
virtual bool HwAccelerated() const override { return true; }
|
||||
|
||||
std::array<__m128i, 2> state{};
|
||||
std::array<XmmReg, 2> state{};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -43,9 +43,7 @@ const std::vector<Watch>& Watches::GetWatches() const
|
|||
|
||||
void Watches::UnsetWatch(u32 address)
|
||||
{
|
||||
m_watches.erase(std::remove_if(m_watches.begin(), m_watches.end(),
|
||||
[address](const auto& watch) { return watch.address == address; }),
|
||||
m_watches.end());
|
||||
std::erase_if(m_watches, [address](const auto& watch) { return watch.address == address; });
|
||||
}
|
||||
|
||||
void Watches::UpdateWatch(std::size_t index, u32 address, std::string name)
|
||||
|
|
|
@ -31,7 +31,7 @@ int ENET_CALLBACK InterceptCallback(ENetHost* host, ENetEvent* event)
|
|||
// wakeup packet received
|
||||
if (host->receivedDataLength == 1 && host->receivedData[0] == 0)
|
||||
{
|
||||
event->type = (ENetEventType)42;
|
||||
event->type = static_cast<ENetEventType>(SKIPPABLE_EVENT);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -21,4 +21,7 @@ using ENetHostPtr = std::unique_ptr<ENetHost, ENetHostDeleter>;
|
|||
void WakeupThread(ENetHost* host);
|
||||
int ENET_CALLBACK InterceptCallback(ENetHost* host, ENetEvent* event);
|
||||
bool SendPacket(ENetPeer* socket, const sf::Packet& packet, u8 channel_id);
|
||||
|
||||
// used for traversal packets and wake-up packets
|
||||
constexpr int SKIPPABLE_EVENT = 42;
|
||||
} // namespace Common::ENet
|
||||
|
|
|
@ -872,7 +872,6 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
|||
s_user_paths[F_WIIPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + WIIPAD_CONFIG;
|
||||
s_user_paths[F_GCKEYBOARDCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + GCKEYBOARD_CONFIG;
|
||||
s_user_paths[F_GFXCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + GFX_CONFIG;
|
||||
s_user_paths[F_DEBUGGERCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DEBUGGER_CONFIG;
|
||||
s_user_paths[F_LOGGERCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + LOGGER_CONFIG;
|
||||
s_user_paths[F_DUALSHOCKUDPCLIENTCONFIG_IDX] =
|
||||
s_user_paths[D_CONFIG_IDX] + DUALSHOCKUDPCLIENT_CONFIG;
|
||||
|
@ -897,6 +896,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);
|
||||
|
@ -908,7 +909,6 @@ static void RebuildUserDirectories(unsigned int dir_index)
|
|||
s_user_paths[F_GCKEYBOARDCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + GCKEYBOARD_CONFIG;
|
||||
s_user_paths[F_WIIPADCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + WIIPAD_CONFIG;
|
||||
s_user_paths[F_GFXCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + GFX_CONFIG;
|
||||
s_user_paths[F_DEBUGGERCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + DEBUGGER_CONFIG;
|
||||
s_user_paths[F_LOGGERCONFIG_IDX] = s_user_paths[D_CONFIG_IDX] + LOGGER_CONFIG;
|
||||
s_user_paths[F_DUALSHOCKUDPCLIENTCONFIG_IDX] =
|
||||
s_user_paths[D_CONFIG_IDX] + DUALSHOCKUDPCLIENT_CONFIG;
|
||||
|
|
|
@ -71,13 +71,13 @@ 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,
|
||||
F_WIIPADCONFIG_IDX,
|
||||
F_GCKEYBOARDCONFIG_IDX,
|
||||
F_GFXCONFIG_IDX,
|
||||
F_DEBUGGERCONFIG_IDX,
|
||||
F_LOGGERCONFIG_IDX,
|
||||
F_MAINLOG_IDX,
|
||||
F_MEM1DUMP_IDX,
|
||||
|
|
|
@ -74,14 +74,14 @@ u32 ClassifyFloat(float fvalue)
|
|||
}
|
||||
|
||||
const std::array<BaseAndDec, 32> frsqrte_expected = {{
|
||||
{0x3ffa000, 0x7a4}, {0x3c29000, 0x700}, {0x38aa000, 0x670}, {0x3572000, 0x5f2},
|
||||
{0x3279000, 0x584}, {0x2fb7000, 0x524}, {0x2d26000, 0x4cc}, {0x2ac0000, 0x47e},
|
||||
{0x2881000, 0x43a}, {0x2665000, 0x3fa}, {0x2468000, 0x3c2}, {0x2287000, 0x38e},
|
||||
{0x20c1000, 0x35e}, {0x1f12000, 0x332}, {0x1d79000, 0x30a}, {0x1bf4000, 0x2e6},
|
||||
{0x1a7e800, 0x568}, {0x17cb800, 0x4f3}, {0x1552800, 0x48d}, {0x130c000, 0x435},
|
||||
{0x10f2000, 0x3e7}, {0x0eff000, 0x3a2}, {0x0d2e000, 0x365}, {0x0b7c000, 0x32e},
|
||||
{0x09e5000, 0x2fc}, {0x0867000, 0x2d0}, {0x06ff000, 0x2a8}, {0x05ab800, 0x283},
|
||||
{0x046a000, 0x261}, {0x0339800, 0x243}, {0x0218800, 0x226}, {0x0105800, 0x20b},
|
||||
{0x1a7e800, -0x568}, {0x17cb800, -0x4f3}, {0x1552800, -0x48d}, {0x130c000, -0x435},
|
||||
{0x10f2000, -0x3e7}, {0x0eff000, -0x3a2}, {0x0d2e000, -0x365}, {0x0b7c000, -0x32e},
|
||||
{0x09e5000, -0x2fc}, {0x0867000, -0x2d0}, {0x06ff000, -0x2a8}, {0x05ab800, -0x283},
|
||||
{0x046a000, -0x261}, {0x0339800, -0x243}, {0x0218800, -0x226}, {0x0105800, -0x20b},
|
||||
{0x3ffa000, -0x7a4}, {0x3c29000, -0x700}, {0x38aa000, -0x670}, {0x3572000, -0x5f2},
|
||||
{0x3279000, -0x584}, {0x2fb7000, -0x524}, {0x2d26000, -0x4cc}, {0x2ac0000, -0x47e},
|
||||
{0x2881000, -0x43a}, {0x2665000, -0x3fa}, {0x2468000, -0x3c2}, {0x2287000, -0x38e},
|
||||
{0x20c1000, -0x35e}, {0x1f12000, -0x332}, {0x1d79000, -0x30a}, {0x1bf4000, -0x2e6},
|
||||
}};
|
||||
|
||||
double ApproximateReciprocalSquareRoot(double val)
|
||||
|
@ -128,14 +128,13 @@ double ApproximateReciprocalSquareRoot(double val)
|
|||
exponent += 1LL << 52;
|
||||
}
|
||||
|
||||
const bool odd_exponent = !(exponent & (1LL << 52));
|
||||
const s64 exponent_lsb = exponent & (1LL << 52);
|
||||
exponent = ((0x3FFLL << 52) - ((exponent - (0x3FELL << 52)) / 2)) & (0x7FFLL << 52);
|
||||
integral = sign | exponent;
|
||||
|
||||
const int i = static_cast<int>(mantissa >> 37);
|
||||
const int index = i / 2048 + (odd_exponent ? 16 : 0);
|
||||
const auto& entry = frsqrte_expected[index];
|
||||
integral |= static_cast<s64>(entry.m_base - entry.m_dec * (i % 2048)) << 26;
|
||||
const int i = static_cast<int>((exponent_lsb | mantissa) >> 37);
|
||||
const auto& entry = frsqrte_expected[i / 2048];
|
||||
integral |= static_cast<s64>(entry.m_base + entry.m_dec * (i % 2048)) << 26;
|
||||
|
||||
return BitCast<double>(integral);
|
||||
}
|
||||
|
|
|
@ -23,11 +23,15 @@ static constexpr u64 DOUBLE_SIGN = 0x8000000000000000ULL;
|
|||
static constexpr u64 DOUBLE_EXP = 0x7FF0000000000000ULL;
|
||||
static constexpr u64 DOUBLE_FRAC = 0x000FFFFFFFFFFFFFULL;
|
||||
static constexpr u64 DOUBLE_ZERO = 0x0000000000000000ULL;
|
||||
static constexpr int DOUBLE_EXP_WIDTH = 11;
|
||||
static constexpr int DOUBLE_FRAC_WIDTH = 52;
|
||||
|
||||
static constexpr u32 FLOAT_SIGN = 0x80000000;
|
||||
static constexpr u32 FLOAT_EXP = 0x7F800000;
|
||||
static constexpr u32 FLOAT_FRAC = 0x007FFFFF;
|
||||
static constexpr u32 FLOAT_ZERO = 0x00000000;
|
||||
static constexpr int FLOAT_EXP_WIDTH = 8;
|
||||
static constexpr int FLOAT_FRAC_WIDTH = 23;
|
||||
|
||||
inline bool IsQNAN(double d)
|
||||
{
|
||||
|
|
|
@ -359,30 +359,6 @@ static u64 GetHash64_SSE42_CRC32(const u8* src, u32 len, u32 samples)
|
|||
return h[0] + (h[1] << 10) + (h[2] << 21) + (h[3] << 32);
|
||||
}
|
||||
|
||||
#elif defined(_M_X86)
|
||||
|
||||
FUNCTION_TARGET_SSE42
|
||||
static u64 GetHash64_SSE42_CRC32(const u8* src, u32 len, u32 samples)
|
||||
{
|
||||
u32 h = len;
|
||||
u32 Step = (len / 4);
|
||||
const u32* data = (const u32*)src;
|
||||
const u32* end = data + Step;
|
||||
if (samples == 0)
|
||||
samples = std::max(Step, 1u);
|
||||
Step = Step / samples;
|
||||
if (Step < 1)
|
||||
Step = 1;
|
||||
while (data < end)
|
||||
{
|
||||
h = _mm_crc32_u32(h, data[0]);
|
||||
data += Step;
|
||||
}
|
||||
|
||||
const u8* data2 = (const u8*)end;
|
||||
return (u64)_mm_crc32_u32(h, u32(data2[0]));
|
||||
}
|
||||
|
||||
#elif defined(_M_ARM_64)
|
||||
|
||||
static u64 GetHash64_ARMv8_CRC32(const u8* src, u32 len, u32 samples)
|
||||
|
@ -433,7 +409,7 @@ static u64 SetHash64Function(const u8* src, u32 len, u32 samples)
|
|||
{
|
||||
if (cpu_info.bCRC32)
|
||||
{
|
||||
#if defined(_M_X86_64) || defined(_M_X86)
|
||||
#if defined(_M_X86_64)
|
||||
s_texture_hash_func = &GetHash64_SSE42_CRC32;
|
||||
#elif defined(_M_ARM_64)
|
||||
s_texture_hash_func = &GetHash64_ARMv8_CRC32;
|
||||
|
|
|
@ -33,7 +33,8 @@ public:
|
|||
void FollowRedirects(long max);
|
||||
s32 GetLastResponseCode();
|
||||
Response Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload,
|
||||
size_t size, AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
|
||||
size_t size, AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only,
|
||||
std::span<Multiform> multiform = {});
|
||||
|
||||
static int CurlProgressCallback(Impl* impl, curl_off_t dltotal, curl_off_t dlnow,
|
||||
curl_off_t ultotal, curl_off_t ulnow);
|
||||
|
@ -174,6 +175,13 @@ void HttpRequest::Impl::UseIPv4()
|
|||
curl_easy_setopt(m_curl.get(), CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
}
|
||||
|
||||
HttpRequest::Response HttpRequest::PostMultiform(const std::string& url,
|
||||
std::span<Multiform> multiform,
|
||||
const Headers& headers, AllowedReturnCodes codes)
|
||||
{
|
||||
return m_impl->Fetch(url, Impl::Method::POST, headers, nullptr, 0, codes, multiform);
|
||||
}
|
||||
|
||||
void HttpRequest::Impl::FollowRedirects(long max)
|
||||
{
|
||||
curl_easy_setopt(m_curl.get(), CURLOPT_FOLLOWLOCATION, 1);
|
||||
|
@ -225,17 +233,33 @@ static size_t header_callback(char* buffer, size_t size, size_t nitems, void* us
|
|||
|
||||
HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method,
|
||||
const Headers& headers, const u8* payload,
|
||||
size_t size, AllowedReturnCodes codes)
|
||||
size_t size, AllowedReturnCodes codes,
|
||||
std::span<Multiform> multiform)
|
||||
{
|
||||
m_response_headers.clear();
|
||||
curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST);
|
||||
curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str());
|
||||
if (method == Method::POST)
|
||||
if (method == Method::POST && multiform.empty())
|
||||
{
|
||||
curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, payload);
|
||||
curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size);
|
||||
}
|
||||
|
||||
curl_mime* form = nullptr;
|
||||
Common::ScopeGuard multiform_guard{[&form] { curl_mime_free(form); }};
|
||||
if (!multiform.empty())
|
||||
{
|
||||
form = curl_mime_init(m_curl.get());
|
||||
for (const auto& value : multiform)
|
||||
{
|
||||
curl_mimepart* part = curl_mime_addpart(form);
|
||||
curl_mime_name(part, value.name.c_str());
|
||||
curl_mime_data(part, value.data.c_str(), value.data.size());
|
||||
}
|
||||
|
||||
curl_easy_setopt(m_curl.get(), CURLOPT_MIMEPOST, form);
|
||||
}
|
||||
|
||||
curl_slist* list = nullptr;
|
||||
Common::ScopeGuard list_guard{[&list] { curl_slist_free_all(list); }};
|
||||
for (const auto& [name, value] : headers)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -35,6 +36,12 @@ public:
|
|||
using Response = std::optional<std::vector<u8>>;
|
||||
using Headers = std::map<std::string, std::optional<std::string>>;
|
||||
|
||||
struct Multiform
|
||||
{
|
||||
std::string name;
|
||||
std::string data;
|
||||
};
|
||||
|
||||
void SetCookies(const std::string& cookies);
|
||||
void UseIPv4();
|
||||
void FollowRedirects(long max = 1);
|
||||
|
@ -48,6 +55,10 @@ public:
|
|||
Response Post(const std::string& url, const std::string& payload, const Headers& headers = {},
|
||||
AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
|
||||
|
||||
Response PostMultiform(const std::string& url, std::span<Multiform> multiform,
|
||||
const Headers& headers = {},
|
||||
AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
|
|
|
@ -101,6 +101,15 @@ bool IOFile::Close()
|
|||
return m_good;
|
||||
}
|
||||
|
||||
IOFile IOFile::Duplicate(const char openmode[]) const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return IOFile(_fdopen(_dup(_fileno(m_file)), openmode));
|
||||
#else // _WIN32
|
||||
return IOFile(fdopen(dup(fileno(m_file)), openmode));
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
void IOFile::SetHandle(std::FILE* file)
|
||||
{
|
||||
Close();
|
||||
|
|
|
@ -51,6 +51,8 @@ public:
|
|||
SharedAccess sh = SharedAccess::Default);
|
||||
bool Close();
|
||||
|
||||
IOFile Duplicate(const char openmode[]) const;
|
||||
|
||||
template <typename T>
|
||||
bool ReadArray(T* elements, size_t count, size_t* num_read = nullptr)
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#if defined(_M_X86)
|
||||
#if defined(_M_X86_64)
|
||||
|
||||
/**
|
||||
* It is assumed that all compilers used to build Dolphin support intrinsics up to and including
|
||||
|
@ -49,13 +49,13 @@
|
|||
|
||||
#endif // defined(_MSC_VER) || defined(__INTEL_COMPILER)
|
||||
|
||||
#endif // _M_X86
|
||||
#endif // _M_X86_64
|
||||
|
||||
/**
|
||||
* Define the FUNCTION_TARGET macros to nothing if they are not needed, or not on an X86 platform.
|
||||
* This way when a function is defined with FUNCTION_TARGET you don't need to define a second
|
||||
* version without the macro around a #ifdef guard. Be careful when using intrinsics, as all use
|
||||
* should still be placed around a #ifdef _M_X86 if the file is compiled on all architectures.
|
||||
* should still be placed around a #ifdef _M_X86_64 if the file is compiled on all architectures.
|
||||
*/
|
||||
#ifndef FUNCTION_TARGET_SSE42
|
||||
#define FUNCTION_TARGET_SSE42
|
||||
|
|
|
@ -14,6 +14,15 @@ namespace Common
|
|||
{
|
||||
#ifdef _WIN32
|
||||
struct WindowsMemoryRegion;
|
||||
|
||||
struct WindowsMemoryFunctions
|
||||
{
|
||||
Common::DynamicLibrary m_kernel32_handle;
|
||||
Common::DynamicLibrary m_api_ms_win_core_memory_l1_1_6_handle;
|
||||
void* m_address_UnmapViewOfFileEx = nullptr;
|
||||
void* m_address_VirtualAlloc2 = nullptr;
|
||||
void* m_address_MapViewOfFile3 = nullptr;
|
||||
};
|
||||
#endif
|
||||
|
||||
// This class lets you create a block of anonymous RAM, and then arbitrarily map views into it.
|
||||
|
@ -110,11 +119,7 @@ private:
|
|||
std::vector<WindowsMemoryRegion> m_regions;
|
||||
void* m_reserved_region = nullptr;
|
||||
void* m_memory_handle = nullptr;
|
||||
Common::DynamicLibrary m_kernel32_handle;
|
||||
Common::DynamicLibrary m_api_ms_win_core_memory_l1_1_6_handle;
|
||||
void* m_address_UnmapViewOfFileEx = nullptr;
|
||||
void* m_address_VirtualAlloc2 = nullptr;
|
||||
void* m_address_MapViewOfFile3 = nullptr;
|
||||
WindowsMemoryFunctions m_memory_functions;
|
||||
#else
|
||||
int m_shm_fd = 0;
|
||||
void* m_reserved_region = nullptr;
|
||||
|
@ -155,9 +160,34 @@ public:
|
|||
///
|
||||
void Release();
|
||||
|
||||
///
|
||||
/// Ensure that the memory page at the given byte offset from the start of the memory region is
|
||||
/// writable. We use this on Windows as a workaround to only actually commit pages as they are
|
||||
/// written to. On other OSes this does nothing.
|
||||
///
|
||||
/// @param offset The offset into the memory region that should be made writable if it isn't.
|
||||
///
|
||||
void EnsureMemoryPageWritable(size_t offset)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const size_t block_index = offset / BLOCK_SIZE;
|
||||
if (m_writable_block_handles[block_index] == nullptr)
|
||||
MakeMemoryBlockWritable(block_index);
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
void* m_memory = nullptr;
|
||||
size_t m_size = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
void* m_zero_block = nullptr;
|
||||
constexpr static size_t BLOCK_SIZE = 8 * 1024 * 1024; // size of allocated memory blocks
|
||||
WindowsMemoryFunctions m_memory_functions;
|
||||
std::vector<void*> m_writable_block_handles;
|
||||
|
||||
void MakeMemoryBlockWritable(size_t offset);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <windows.h>
|
||||
|
||||
#include "Common/Align.h"
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
|
@ -49,48 +50,55 @@ struct WindowsMemoryRegion
|
|||
}
|
||||
};
|
||||
|
||||
static bool InitWindowsMemoryFunctions(WindowsMemoryFunctions* functions)
|
||||
{
|
||||
DynamicLibrary kernelBase{"KernelBase.dll"};
|
||||
if (!kernelBase.IsOpen())
|
||||
return false;
|
||||
|
||||
void* const ptr_IsApiSetImplemented = kernelBase.GetSymbolAddress("IsApiSetImplemented");
|
||||
if (!ptr_IsApiSetImplemented)
|
||||
return false;
|
||||
if (!static_cast<PIsApiSetImplemented>(ptr_IsApiSetImplemented)("api-ms-win-core-memory-l1-1-6"))
|
||||
return false;
|
||||
|
||||
functions->m_api_ms_win_core_memory_l1_1_6_handle.Open("api-ms-win-core-memory-l1-1-6.dll");
|
||||
functions->m_kernel32_handle.Open("Kernel32.dll");
|
||||
if (!functions->m_api_ms_win_core_memory_l1_1_6_handle.IsOpen() ||
|
||||
!functions->m_kernel32_handle.IsOpen())
|
||||
{
|
||||
functions->m_api_ms_win_core_memory_l1_1_6_handle.Close();
|
||||
functions->m_kernel32_handle.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
void* const address_VirtualAlloc2 =
|
||||
functions->m_api_ms_win_core_memory_l1_1_6_handle.GetSymbolAddress("VirtualAlloc2FromApp");
|
||||
void* const address_MapViewOfFile3 =
|
||||
functions->m_api_ms_win_core_memory_l1_1_6_handle.GetSymbolAddress("MapViewOfFile3FromApp");
|
||||
void* const address_UnmapViewOfFileEx =
|
||||
functions->m_kernel32_handle.GetSymbolAddress("UnmapViewOfFileEx");
|
||||
if (address_VirtualAlloc2 && address_MapViewOfFile3 && address_UnmapViewOfFileEx)
|
||||
{
|
||||
functions->m_address_VirtualAlloc2 = address_VirtualAlloc2;
|
||||
functions->m_address_MapViewOfFile3 = address_MapViewOfFile3;
|
||||
functions->m_address_UnmapViewOfFileEx = address_UnmapViewOfFileEx;
|
||||
return true;
|
||||
}
|
||||
|
||||
// at least one function is not available, use legacy logic
|
||||
functions->m_api_ms_win_core_memory_l1_1_6_handle.Close();
|
||||
functions->m_kernel32_handle.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
MemArena::MemArena()
|
||||
{
|
||||
// Check if VirtualAlloc2 and MapViewOfFile3 are available, which provide functionality to reserve
|
||||
// a memory region no other allocation may occupy while still allowing us to allocate and map
|
||||
// stuff within it. If they're not available we'll instead fall back to the 'legacy' logic and
|
||||
// just hope that nothing allocates in our address range.
|
||||
DynamicLibrary kernelBase{"KernelBase.dll"};
|
||||
if (!kernelBase.IsOpen())
|
||||
return;
|
||||
|
||||
void* const ptr_IsApiSetImplemented = kernelBase.GetSymbolAddress("IsApiSetImplemented");
|
||||
if (!ptr_IsApiSetImplemented)
|
||||
return;
|
||||
if (!static_cast<PIsApiSetImplemented>(ptr_IsApiSetImplemented)("api-ms-win-core-memory-l1-1-6"))
|
||||
return;
|
||||
|
||||
m_api_ms_win_core_memory_l1_1_6_handle.Open("api-ms-win-core-memory-l1-1-6.dll");
|
||||
m_kernel32_handle.Open("Kernel32.dll");
|
||||
if (!m_api_ms_win_core_memory_l1_1_6_handle.IsOpen() || !m_kernel32_handle.IsOpen())
|
||||
{
|
||||
m_api_ms_win_core_memory_l1_1_6_handle.Close();
|
||||
m_kernel32_handle.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
void* const address_VirtualAlloc2 =
|
||||
m_api_ms_win_core_memory_l1_1_6_handle.GetSymbolAddress("VirtualAlloc2FromApp");
|
||||
void* const address_MapViewOfFile3 =
|
||||
m_api_ms_win_core_memory_l1_1_6_handle.GetSymbolAddress("MapViewOfFile3FromApp");
|
||||
void* const address_UnmapViewOfFileEx = m_kernel32_handle.GetSymbolAddress("UnmapViewOfFileEx");
|
||||
if (address_VirtualAlloc2 && address_MapViewOfFile3 && address_UnmapViewOfFileEx)
|
||||
{
|
||||
m_address_VirtualAlloc2 = address_VirtualAlloc2;
|
||||
m_address_MapViewOfFile3 = address_MapViewOfFile3;
|
||||
m_address_UnmapViewOfFileEx = address_UnmapViewOfFileEx;
|
||||
}
|
||||
else
|
||||
{
|
||||
// at least one function is not available, use legacy logic
|
||||
m_api_ms_win_core_memory_l1_1_6_handle.Close();
|
||||
m_kernel32_handle.Close();
|
||||
}
|
||||
InitWindowsMemoryFunctions(&m_memory_functions);
|
||||
}
|
||||
|
||||
MemArena::~MemArena()
|
||||
|
@ -146,9 +154,9 @@ u8* MemArena::ReserveMemoryRegion(size_t memory_size)
|
|||
}
|
||||
|
||||
u8* base;
|
||||
if (m_api_ms_win_core_memory_l1_1_6_handle.IsOpen())
|
||||
if (m_memory_functions.m_api_ms_win_core_memory_l1_1_6_handle.IsOpen())
|
||||
{
|
||||
base = static_cast<u8*>(static_cast<PVirtualAlloc2>(m_address_VirtualAlloc2)(
|
||||
base = static_cast<u8*>(static_cast<PVirtualAlloc2>(m_memory_functions.m_address_VirtualAlloc2)(
|
||||
nullptr, nullptr, memory_size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS,
|
||||
nullptr, 0));
|
||||
if (base)
|
||||
|
@ -177,7 +185,7 @@ u8* MemArena::ReserveMemoryRegion(size_t memory_size)
|
|||
|
||||
void MemArena::ReleaseMemoryRegion()
|
||||
{
|
||||
if (m_api_ms_win_core_memory_l1_1_6_handle.IsOpen() && m_reserved_region)
|
||||
if (m_memory_functions.m_api_ms_win_core_memory_l1_1_6_handle.IsOpen() && m_reserved_region)
|
||||
{
|
||||
// user should have unmapped everything by this point, check if that's true and yell if not
|
||||
// (it indicates a bug in the emulated memory mapping logic)
|
||||
|
@ -314,7 +322,7 @@ WindowsMemoryRegion* MemArena::EnsureSplitRegionForMapping(void* start_address,
|
|||
|
||||
void* MemArena::MapInMemoryRegion(s64 offset, size_t size, void* base)
|
||||
{
|
||||
if (m_api_ms_win_core_memory_l1_1_6_handle.IsOpen())
|
||||
if (m_memory_functions.m_api_ms_win_core_memory_l1_1_6_handle.IsOpen())
|
||||
{
|
||||
WindowsMemoryRegion* const region = EnsureSplitRegionForMapping(base, size);
|
||||
if (!region)
|
||||
|
@ -323,7 +331,7 @@ void* MemArena::MapInMemoryRegion(s64 offset, size_t size, void* base)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void* rv = static_cast<PMapViewOfFile3>(m_address_MapViewOfFile3)(
|
||||
void* rv = static_cast<PMapViewOfFile3>(m_memory_functions.m_address_MapViewOfFile3)(
|
||||
m_memory_handle, nullptr, base, offset, size, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE,
|
||||
nullptr, 0);
|
||||
if (rv)
|
||||
|
@ -416,10 +424,10 @@ bool MemArena::JoinRegionsAfterUnmap(void* start_address, size_t size)
|
|||
|
||||
void MemArena::UnmapFromMemoryRegion(void* view, size_t size)
|
||||
{
|
||||
if (m_api_ms_win_core_memory_l1_1_6_handle.IsOpen())
|
||||
if (m_memory_functions.m_api_ms_win_core_memory_l1_1_6_handle.IsOpen())
|
||||
{
|
||||
if (static_cast<PUnmapViewOfFileEx>(m_address_UnmapViewOfFileEx)(view,
|
||||
MEM_PRESERVE_PLACEHOLDER))
|
||||
if (static_cast<PUnmapViewOfFileEx>(m_memory_functions.m_address_UnmapViewOfFileEx)(
|
||||
view, MEM_PRESERVE_PLACEHOLDER))
|
||||
{
|
||||
if (!JoinRegionsAfterUnmap(view, size))
|
||||
PanicAlertFmt("Joining memory region failed.");
|
||||
|
@ -434,7 +442,10 @@ void MemArena::UnmapFromMemoryRegion(void* view, size_t size)
|
|||
UnmapViewOfFile(view);
|
||||
}
|
||||
|
||||
LazyMemoryRegion::LazyMemoryRegion() = default;
|
||||
LazyMemoryRegion::LazyMemoryRegion()
|
||||
{
|
||||
InitWindowsMemoryFunctions(&m_memory_functions);
|
||||
}
|
||||
|
||||
LazyMemoryRegion::~LazyMemoryRegion()
|
||||
{
|
||||
|
@ -448,15 +459,67 @@ void* LazyMemoryRegion::Create(size_t size)
|
|||
if (size == 0)
|
||||
return nullptr;
|
||||
|
||||
void* memory = VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
if (!m_memory_functions.m_api_ms_win_core_memory_l1_1_6_handle.IsOpen())
|
||||
return nullptr;
|
||||
|
||||
// reserve block of memory
|
||||
const size_t memory_size = Common::AlignUp(size, BLOCK_SIZE);
|
||||
const size_t block_count = memory_size / BLOCK_SIZE;
|
||||
u8* memory =
|
||||
static_cast<u8*>(static_cast<PVirtualAlloc2>(m_memory_functions.m_address_VirtualAlloc2)(
|
||||
nullptr, nullptr, memory_size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS,
|
||||
nullptr, 0));
|
||||
if (!memory)
|
||||
{
|
||||
NOTICE_LOG_FMT(MEMMAP, "Memory allocation of {} bytes failed.", size);
|
||||
NOTICE_LOG_FMT(MEMMAP, "Memory reservation of {} bytes failed.", size);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// split into individual block-sized regions
|
||||
for (size_t i = 0; i < block_count - 1; ++i)
|
||||
{
|
||||
if (!VirtualFree(memory + i * BLOCK_SIZE, BLOCK_SIZE, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER))
|
||||
{
|
||||
NOTICE_LOG_FMT(MEMMAP, "Region splitting failed: {}", GetLastErrorString());
|
||||
|
||||
// release every split block as well as the remaining unsplit one
|
||||
for (size_t j = 0; j < i + 1; ++j)
|
||||
VirtualFree(memory + j * BLOCK_SIZE, 0, MEM_RELEASE);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
m_memory = memory;
|
||||
m_size = size;
|
||||
m_size = memory_size;
|
||||
|
||||
// allocate a single block of real memory in the page file
|
||||
HANDLE zero_block = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READONLY,
|
||||
GetHighDWORD(BLOCK_SIZE), GetLowDWORD(BLOCK_SIZE), nullptr);
|
||||
if (zero_block == nullptr)
|
||||
{
|
||||
NOTICE_LOG_FMT(MEMMAP, "CreateFileMapping() failed for zero block: {}", GetLastErrorString());
|
||||
Release();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_zero_block = zero_block;
|
||||
|
||||
// map the zero page into every block
|
||||
for (size_t i = 0; i < block_count; ++i)
|
||||
{
|
||||
void* result = static_cast<PMapViewOfFile3>(m_memory_functions.m_address_MapViewOfFile3)(
|
||||
zero_block, nullptr, memory + i * BLOCK_SIZE, 0, BLOCK_SIZE, MEM_REPLACE_PLACEHOLDER,
|
||||
PAGE_READONLY, nullptr, 0);
|
||||
if (!result)
|
||||
{
|
||||
NOTICE_LOG_FMT(MEMMAP, "Mapping the zero block failed: {}", GetLastErrorString());
|
||||
Release();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
m_writable_block_handles.resize(block_count, nullptr);
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
@ -464,19 +527,105 @@ void* LazyMemoryRegion::Create(size_t size)
|
|||
void LazyMemoryRegion::Clear()
|
||||
{
|
||||
ASSERT(m_memory);
|
||||
u8* const memory = static_cast<u8*>(m_memory);
|
||||
|
||||
VirtualFree(m_memory, m_size, MEM_DECOMMIT);
|
||||
VirtualAlloc(m_memory, m_size, MEM_COMMIT, PAGE_READWRITE);
|
||||
// reset every writable block back to the zero block
|
||||
for (size_t i = 0; i < m_writable_block_handles.size(); ++i)
|
||||
{
|
||||
if (m_writable_block_handles[i] == nullptr)
|
||||
continue;
|
||||
|
||||
// unmap the writable block
|
||||
if (!static_cast<PUnmapViewOfFileEx>(m_memory_functions.m_address_UnmapViewOfFileEx)(
|
||||
memory + i * BLOCK_SIZE, MEM_PRESERVE_PLACEHOLDER))
|
||||
{
|
||||
PanicAlertFmt("Failed to unmap the writable block: {}", GetLastErrorString());
|
||||
}
|
||||
|
||||
// free the writable block
|
||||
if (!CloseHandle(m_writable_block_handles[i]))
|
||||
{
|
||||
PanicAlertFmt("Failed to free the writable block: {}", GetLastErrorString());
|
||||
}
|
||||
m_writable_block_handles[i] = nullptr;
|
||||
|
||||
// map the zero block
|
||||
void* map_result = static_cast<PMapViewOfFile3>(m_memory_functions.m_address_MapViewOfFile3)(
|
||||
m_zero_block, nullptr, memory + i * BLOCK_SIZE, 0, BLOCK_SIZE, MEM_REPLACE_PLACEHOLDER,
|
||||
PAGE_READONLY, nullptr, 0);
|
||||
if (!map_result)
|
||||
{
|
||||
PanicAlertFmt("Failed to re-map the zero block: {}", GetLastErrorString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LazyMemoryRegion::Release()
|
||||
{
|
||||
if (m_memory)
|
||||
{
|
||||
VirtualFree(m_memory, 0, MEM_RELEASE);
|
||||
// unmap all pages and release the not-zero block handles
|
||||
u8* const memory = static_cast<u8*>(m_memory);
|
||||
for (size_t i = 0; i < m_writable_block_handles.size(); ++i)
|
||||
{
|
||||
static_cast<PUnmapViewOfFileEx>(m_memory_functions.m_address_UnmapViewOfFileEx)(
|
||||
memory + i * BLOCK_SIZE, MEM_PRESERVE_PLACEHOLDER);
|
||||
if (m_writable_block_handles[i])
|
||||
{
|
||||
CloseHandle(m_writable_block_handles[i]);
|
||||
m_writable_block_handles[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_zero_block)
|
||||
{
|
||||
CloseHandle(m_zero_block);
|
||||
m_zero_block = nullptr;
|
||||
}
|
||||
if (m_memory)
|
||||
{
|
||||
u8* const memory = static_cast<u8*>(m_memory);
|
||||
const size_t block_count = m_size / BLOCK_SIZE;
|
||||
for (size_t i = 0; i < block_count; ++i)
|
||||
VirtualFree(memory + i * BLOCK_SIZE, 0, MEM_RELEASE);
|
||||
m_memory = nullptr;
|
||||
m_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void LazyMemoryRegion::MakeMemoryBlockWritable(size_t block_index)
|
||||
{
|
||||
u8* const memory = static_cast<u8*>(m_memory);
|
||||
|
||||
// unmap the zero block
|
||||
if (!static_cast<PUnmapViewOfFileEx>(m_memory_functions.m_address_UnmapViewOfFileEx)(
|
||||
memory + block_index * BLOCK_SIZE, MEM_PRESERVE_PLACEHOLDER))
|
||||
{
|
||||
PanicAlertFmt("Failed to unmap the zero block: {}", GetLastErrorString());
|
||||
return;
|
||||
}
|
||||
|
||||
// allocate a fresh block to map
|
||||
HANDLE block = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE,
|
||||
GetHighDWORD(BLOCK_SIZE), GetLowDWORD(BLOCK_SIZE), nullptr);
|
||||
if (block == nullptr)
|
||||
{
|
||||
PanicAlertFmt("CreateFileMapping() failed for writable block: {}", GetLastErrorString());
|
||||
return;
|
||||
}
|
||||
|
||||
// map the new block
|
||||
void* map_result = static_cast<PMapViewOfFile3>(m_memory_functions.m_address_MapViewOfFile3)(
|
||||
block, nullptr, memory + block_index * BLOCK_SIZE, 0, BLOCK_SIZE, MEM_REPLACE_PLACEHOLDER,
|
||||
PAGE_READWRITE, nullptr, 0);
|
||||
if (!map_result)
|
||||
{
|
||||
PanicAlertFmt("Failed to map the writable block: {}", GetLastErrorString());
|
||||
CloseHandle(block);
|
||||
return;
|
||||
}
|
||||
|
||||
m_writable_block_handles[block_index] = block;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -49,6 +49,7 @@ void PCAP::AddHeader(u32 link_type)
|
|||
m_fp->WriteBytes(&hdr, sizeof(hdr));
|
||||
}
|
||||
|
||||
// Not thread-safe, concurrency between multiple calls to IOFile::WriteBytes.
|
||||
void PCAP::AddPacket(const u8* bytes, size_t size)
|
||||
{
|
||||
std::chrono::system_clock::time_point now(std::chrono::system_clock::now());
|
||||
|
|
|
@ -81,7 +81,7 @@ void SettingsHandler::Decrypt()
|
|||
// (see the comment in WriteLine), lines can be separated by CRLFLF.
|
||||
// To handle this, we remove every CR and treat LF as the line ending.
|
||||
// (We ignore empty lines.)
|
||||
decoded.erase(std::remove(decoded.begin(), decoded.end(), '\x0d'), decoded.end());
|
||||
std::erase(decoded, '\x0d');
|
||||
}
|
||||
|
||||
void SettingsHandler::Reset()
|
||||
|
@ -92,12 +92,12 @@ void SettingsHandler::Reset()
|
|||
m_buffer = {};
|
||||
}
|
||||
|
||||
void SettingsHandler::AddSetting(const std::string& key, const std::string& value)
|
||||
void SettingsHandler::AddSetting(std::string_view key, std::string_view value)
|
||||
{
|
||||
WriteLine(key + '=' + value + "\r\n");
|
||||
WriteLine(fmt::format("{}={}\r\n", key, value));
|
||||
}
|
||||
|
||||
void SettingsHandler::WriteLine(const std::string& str)
|
||||
void SettingsHandler::WriteLine(std::string_view str)
|
||||
{
|
||||
const u32 old_position = m_position;
|
||||
const u32 old_key = m_key;
|
||||
|
|
|
@ -27,7 +27,7 @@ public:
|
|||
SettingsHandler();
|
||||
explicit SettingsHandler(Buffer&& buffer);
|
||||
|
||||
void AddSetting(const std::string& key, const std::string& value);
|
||||
void AddSetting(std::string_view key, std::string_view value);
|
||||
|
||||
const Buffer& GetBytes() const;
|
||||
void SetBytes(Buffer&& buffer);
|
||||
|
@ -38,7 +38,7 @@ public:
|
|||
static std::string GenerateSerialNumber();
|
||||
|
||||
private:
|
||||
void WriteLine(const std::string& str);
|
||||
void WriteLine(std::string_view str);
|
||||
void WriteByte(u8 b);
|
||||
|
||||
std::array<u8, SETTINGS_SIZE> m_buffer;
|
||||
|
|
|
@ -29,9 +29,11 @@ public:
|
|||
T& operator[](size_t i) { return m_array[i]; }
|
||||
const T& operator[](size_t i) const { return m_array[i]; }
|
||||
|
||||
auto data() { return m_array.data(); }
|
||||
auto begin() { return m_array.begin(); }
|
||||
auto end() { return m_array.begin() + m_size; }
|
||||
|
||||
auto data() const { return m_array.data(); }
|
||||
auto begin() const { return m_array.begin(); }
|
||||
auto end() const { return m_array.begin() + m_size; }
|
||||
|
||||
|
|
|
@ -14,8 +14,9 @@
|
|||
|
||||
namespace Common
|
||||
{
|
||||
TraversalClient::TraversalClient(ENetHost* netHost, const std::string& server, const u16 port)
|
||||
: m_NetHost(netHost), m_Server(server), m_port(port)
|
||||
TraversalClient::TraversalClient(ENetHost* netHost, const std::string& server, const u16 port,
|
||||
const u16 port_alt)
|
||||
: m_NetHost(netHost), m_Server(server), m_port(port), m_portAlt(port_alt)
|
||||
{
|
||||
netHost->intercept = TraversalClient::InterceptCallback;
|
||||
|
||||
|
@ -146,6 +147,8 @@ void TraversalClient::HandleServerPacket(TraversalPacket* packet)
|
|||
{
|
||||
if (it->packet.requestId == packet->requestId)
|
||||
{
|
||||
if (packet->requestId == m_TestRequestId)
|
||||
HandleTraversalTest();
|
||||
m_OutgoingTraversalPackets.erase(it);
|
||||
break;
|
||||
}
|
||||
|
@ -161,6 +164,7 @@ void TraversalClient::HandleServerPacket(TraversalPacket* packet)
|
|||
}
|
||||
m_HostId = packet->helloFromServer.yourHostId;
|
||||
m_external_address = packet->helloFromServer.yourAddress;
|
||||
NewTraversalTest();
|
||||
m_State = State::Connected;
|
||||
if (m_Client)
|
||||
m_Client->OnTraversalStateChanged();
|
||||
|
@ -175,7 +179,18 @@ void TraversalClient::HandleServerPacket(TraversalPacket* packet)
|
|||
ENetBuffer buf;
|
||||
buf.data = message;
|
||||
buf.dataLength = sizeof(message) - 1;
|
||||
enet_socket_send(m_NetHost->socket, &addr, &buf, 1);
|
||||
if (m_ttlReady)
|
||||
{
|
||||
int oldttl;
|
||||
enet_socket_get_option(m_NetHost->socket, ENET_SOCKOPT_TTL, &oldttl);
|
||||
enet_socket_set_option(m_NetHost->socket, ENET_SOCKOPT_TTL, m_ttl);
|
||||
enet_socket_send(m_NetHost->socket, &addr, &buf, 1);
|
||||
enet_socket_set_option(m_NetHost->socket, ENET_SOCKOPT_TTL, oldttl);
|
||||
}
|
||||
else
|
||||
{
|
||||
enet_socket_send(m_NetHost->socket, &addr, &buf, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -231,12 +246,15 @@ void TraversalClient::OnFailure(FailureReason reason)
|
|||
|
||||
void TraversalClient::ResendPacket(OutgoingTraversalPacketInfo* info)
|
||||
{
|
||||
bool testPacket =
|
||||
m_TestSocket != ENET_SOCKET_NULL && info->packet.type == TraversalPacketType::TestPlease;
|
||||
info->sendTime = enet_time_get();
|
||||
info->tries++;
|
||||
ENetBuffer buf;
|
||||
buf.data = &info->packet;
|
||||
buf.dataLength = sizeof(info->packet);
|
||||
if (enet_socket_send(m_NetHost->socket, &m_ServerAddress, &buf, 1) == -1)
|
||||
if (enet_socket_send(testPacket ? m_TestSocket : m_NetHost->socket, &m_ServerAddress, &buf, 1) ==
|
||||
-1)
|
||||
OnFailure(FailureReason::SocketSendError);
|
||||
}
|
||||
|
||||
|
@ -275,6 +293,112 @@ void TraversalClient::HandlePing()
|
|||
}
|
||||
}
|
||||
|
||||
void TraversalClient::NewTraversalTest()
|
||||
{
|
||||
// create test socket
|
||||
if (m_TestSocket != ENET_SOCKET_NULL)
|
||||
enet_socket_destroy(m_TestSocket);
|
||||
m_TestSocket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
|
||||
ENetAddress addr = {ENET_HOST_ANY, 0};
|
||||
if (m_TestSocket == ENET_SOCKET_NULL || enet_socket_bind(m_TestSocket, &addr) < 0)
|
||||
{
|
||||
// error, abort
|
||||
if (m_TestSocket != ENET_SOCKET_NULL)
|
||||
{
|
||||
enet_socket_destroy(m_TestSocket);
|
||||
m_TestSocket = ENET_SOCKET_NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
enet_socket_set_option(m_TestSocket, ENET_SOCKOPT_NONBLOCK, 1);
|
||||
// create holepunch packet
|
||||
TraversalPacket packet = {};
|
||||
packet.type = TraversalPacketType::Ping;
|
||||
packet.ping.hostId = m_HostId;
|
||||
packet.requestId = Common::Random::GenerateValue<TraversalRequestId>();
|
||||
// create buffer
|
||||
ENetBuffer buf;
|
||||
buf.data = &packet;
|
||||
buf.dataLength = sizeof(packet);
|
||||
// send to alt port
|
||||
ENetAddress altAddress = m_ServerAddress;
|
||||
altAddress.port = m_portAlt;
|
||||
// set up ttl and send
|
||||
int oldttl;
|
||||
enet_socket_get_option(m_TestSocket, ENET_SOCKOPT_TTL, &oldttl);
|
||||
enet_socket_set_option(m_TestSocket, ENET_SOCKOPT_TTL, m_ttl);
|
||||
if (enet_socket_send(m_TestSocket, &altAddress, &buf, 1) == -1)
|
||||
{
|
||||
// error, abort
|
||||
enet_socket_destroy(m_TestSocket);
|
||||
m_TestSocket = ENET_SOCKET_NULL;
|
||||
return;
|
||||
}
|
||||
enet_socket_set_option(m_TestSocket, ENET_SOCKOPT_TTL, oldttl);
|
||||
// send the test request
|
||||
packet.type = TraversalPacketType::TestPlease;
|
||||
m_TestRequestId = SendTraversalPacket(packet);
|
||||
}
|
||||
|
||||
void TraversalClient::HandleTraversalTest()
|
||||
{
|
||||
if (m_TestSocket != ENET_SOCKET_NULL)
|
||||
{
|
||||
// check for packet on test socket (with timeout)
|
||||
u32 deadline = enet_time_get() + 50;
|
||||
u32 waitCondition;
|
||||
do
|
||||
{
|
||||
waitCondition = ENET_SOCKET_WAIT_RECEIVE | ENET_SOCKET_WAIT_INTERRUPT;
|
||||
u32 currentTime = enet_time_get();
|
||||
if (currentTime > deadline ||
|
||||
enet_socket_wait(m_TestSocket, &waitCondition, deadline - currentTime) != 0)
|
||||
{
|
||||
// error or timeout, exit the loop and assume test failure
|
||||
waitCondition = 0;
|
||||
break;
|
||||
}
|
||||
else if (waitCondition & ENET_SOCKET_WAIT_RECEIVE)
|
||||
{
|
||||
// try reading the packet and see if it's relevant
|
||||
ENetAddress raddr;
|
||||
TraversalPacket packet;
|
||||
ENetBuffer buf;
|
||||
buf.data = &packet;
|
||||
buf.dataLength = sizeof(packet);
|
||||
int rv = enet_socket_receive(m_TestSocket, &raddr, &buf, 1);
|
||||
if (rv < 0)
|
||||
{
|
||||
// error, exit the loop and assume test failure
|
||||
waitCondition = 0;
|
||||
break;
|
||||
}
|
||||
else if (rv < int(sizeof(packet)) || raddr.host != m_ServerAddress.host ||
|
||||
raddr.host != m_portAlt || packet.requestId != m_TestRequestId)
|
||||
{
|
||||
// irrelevant packet, ignore
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} while (waitCondition & ENET_SOCKET_WAIT_INTERRUPT);
|
||||
// regardless of what happens next, we can throw out the socket
|
||||
enet_socket_destroy(m_TestSocket);
|
||||
m_TestSocket = ENET_SOCKET_NULL;
|
||||
if (waitCondition & ENET_SOCKET_WAIT_RECEIVE)
|
||||
{
|
||||
// success, we can stop now
|
||||
m_ttlReady = true;
|
||||
m_Client->OnTtlDetermined(m_ttl);
|
||||
}
|
||||
else
|
||||
{
|
||||
// fail, increment and retry
|
||||
if (++m_ttl < 32)
|
||||
NewTraversalTest();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TraversalRequestId TraversalClient::SendTraversalPacket(const TraversalPacket& packet)
|
||||
{
|
||||
OutgoingTraversalPacketInfo info;
|
||||
|
@ -299,7 +423,7 @@ int ENET_CALLBACK TraversalClient::InterceptCallback(ENetHost* host, ENetEvent*
|
|||
&host->receivedAddress) ||
|
||||
(host->receivedDataLength == 1 && host->receivedData[0] == 0))
|
||||
{
|
||||
event->type = (ENetEventType)42;
|
||||
event->type = static_cast<ENetEventType>(Common::ENet::SKIPPABLE_EVENT);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
@ -313,15 +437,19 @@ ENet::ENetHostPtr g_MainNetHost;
|
|||
// explicitly requested.
|
||||
static std::string g_OldServer;
|
||||
static u16 g_OldServerPort;
|
||||
static u16 g_OldServerPortAlt;
|
||||
static u16 g_OldListenPort;
|
||||
|
||||
bool EnsureTraversalClient(const std::string& server, u16 server_port, u16 listen_port)
|
||||
bool EnsureTraversalClient(const std::string& server, u16 server_port, u16 server_port_alt,
|
||||
u16 listen_port)
|
||||
{
|
||||
if (!g_MainNetHost || !g_TraversalClient || server != g_OldServer ||
|
||||
server_port != g_OldServerPort || listen_port != g_OldListenPort)
|
||||
server_port != g_OldServerPort || server_port_alt != g_OldServerPortAlt ||
|
||||
listen_port != g_OldListenPort)
|
||||
{
|
||||
g_OldServer = server;
|
||||
g_OldServerPort = server_port;
|
||||
g_OldServerPortAlt = server_port_alt;
|
||||
g_OldListenPort = listen_port;
|
||||
|
||||
ENetAddress addr = {ENET_HOST_ANY, listen_port};
|
||||
|
@ -337,7 +465,8 @@ bool EnsureTraversalClient(const std::string& server, u16 server_port, u16 liste
|
|||
}
|
||||
host->mtu = std::min(host->mtu, NetPlay::MAX_ENET_MTU);
|
||||
g_MainNetHost = std::move(host);
|
||||
g_TraversalClient.reset(new TraversalClient(g_MainNetHost.get(), server, server_port));
|
||||
g_TraversalClient.reset(
|
||||
new TraversalClient(g_MainNetHost.get(), server, server_port, server_port_alt));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ public:
|
|||
virtual void OnTraversalStateChanged() = 0;
|
||||
virtual void OnConnectReady(ENetAddress addr) = 0;
|
||||
virtual void OnConnectFailed(TraversalConnectFailedReason reason) = 0;
|
||||
virtual void OnTtlDetermined(u8 ttl) = 0;
|
||||
};
|
||||
|
||||
class TraversalClient
|
||||
|
@ -43,7 +44,8 @@ public:
|
|||
SocketSendError,
|
||||
ResendTimeout,
|
||||
};
|
||||
TraversalClient(ENetHost* netHost, const std::string& server, const u16 port);
|
||||
TraversalClient(ENetHost* netHost, const std::string& server, const u16 port,
|
||||
const u16 port_alt = 0);
|
||||
~TraversalClient();
|
||||
|
||||
TraversalHostId GetHostID() const;
|
||||
|
@ -79,6 +81,9 @@ private:
|
|||
void HandlePing();
|
||||
static int ENET_CALLBACK InterceptCallback(ENetHost* host, ENetEvent* event);
|
||||
|
||||
void NewTraversalTest();
|
||||
void HandleTraversalTest();
|
||||
|
||||
ENetHost* m_NetHost;
|
||||
TraversalHostId m_HostId{};
|
||||
TraversalInetAddress m_external_address{};
|
||||
|
@ -90,7 +95,13 @@ private:
|
|||
ENetAddress m_ServerAddress{};
|
||||
std::string m_Server;
|
||||
u16 m_port;
|
||||
u16 m_portAlt;
|
||||
u32 m_PingTime = 0;
|
||||
|
||||
ENetSocket m_TestSocket = ENET_SOCKET_NULL;
|
||||
TraversalRequestId m_TestRequestId = 0;
|
||||
u8 m_ttl = 2;
|
||||
bool m_ttlReady = false;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<TraversalClient> g_TraversalClient;
|
||||
|
@ -98,6 +109,7 @@ extern std::unique_ptr<TraversalClient> g_TraversalClient;
|
|||
extern ENet::ENetHostPtr g_MainNetHost;
|
||||
|
||||
// Create g_TraversalClient and g_MainNetHost if necessary.
|
||||
bool EnsureTraversalClient(const std::string& server, u16 server_port, u16 listen_port = 0);
|
||||
bool EnsureTraversalClient(const std::string& server, u16 server_port, u16 server_port_alt = 0,
|
||||
u16 listen_port = 0);
|
||||
void ReleaseTraversalClient();
|
||||
} // namespace Common
|
||||
|
|
|
@ -31,6 +31,10 @@ enum class TraversalPacketType : u8
|
|||
ConnectReady = 6,
|
||||
// [s->c] Alternately, the server might not have heard of this host.
|
||||
ConnectFailed = 7,
|
||||
// [c->s] Perform a traveral test. This will send two acks:
|
||||
// one via the server's alt port, and one to the address corresponding to
|
||||
// the given host ID.
|
||||
TestPlease = 8,
|
||||
};
|
||||
|
||||
constexpr u8 TraversalProtoVersion = 0;
|
||||
|
@ -91,6 +95,10 @@ struct TraversalPacket
|
|||
TraversalRequestId requestId;
|
||||
TraversalConnectFailedReason reason;
|
||||
} connectFailed;
|
||||
struct
|
||||
{
|
||||
TraversalHostId hostId;
|
||||
} testPlease;
|
||||
};
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
|
|
@ -9,13 +9,17 @@
|
|||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <tuple>
|
||||
#include <unistd.h>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#ifdef HAVE_LIBSYSTEMD
|
||||
#include <systemd/sd-daemon.h>
|
||||
#endif
|
||||
|
@ -26,6 +30,7 @@
|
|||
#define DEBUG 0
|
||||
#define NUMBER_OF_TRIES 5
|
||||
#define PORT 6262
|
||||
#define PORT_ALT 6226
|
||||
|
||||
static u64 currentTime;
|
||||
|
||||
|
@ -33,6 +38,7 @@ struct OutgoingPacketInfo
|
|||
{
|
||||
Common::TraversalPacket packet;
|
||||
Common::TraversalRequestId misc;
|
||||
bool fromAlt;
|
||||
sockaddr_in6 dest;
|
||||
int tries;
|
||||
u64 sendTime;
|
||||
|
@ -81,12 +87,12 @@ retry:
|
|||
}
|
||||
}
|
||||
#if DEBUG
|
||||
printf("failed to find key '");
|
||||
fmt::print("failed to find key '");
|
||||
for (size_t i = 0; i < sizeof(key); i++)
|
||||
{
|
||||
printf("%02x", ((u8*)&key)[i]);
|
||||
fmt::print("{:02x}", ((u8*)&key)[i]);
|
||||
}
|
||||
printf("'\n");
|
||||
fmt::print("'\n");
|
||||
#endif
|
||||
result.found = false;
|
||||
return result;
|
||||
|
@ -119,6 +125,7 @@ using ConnectedClients =
|
|||
using OutgoingPackets = std::unordered_map<Common::TraversalRequestId, OutgoingPacketInfo>;
|
||||
|
||||
static int sock;
|
||||
static int sockAlt;
|
||||
static OutgoingPackets outgoingPackets;
|
||||
static ConnectedClients connectedClients;
|
||||
|
||||
|
@ -126,7 +133,7 @@ static Common::TraversalInetAddress MakeInetAddress(const sockaddr_in6& addr)
|
|||
{
|
||||
if (addr.sin6_family != AF_INET6)
|
||||
{
|
||||
fprintf(stderr, "bad sockaddr_in6\n");
|
||||
fmt::print(stderr, "bad sockaddr_in6\n");
|
||||
exit(1);
|
||||
}
|
||||
u32* words = (u32*)addr.sin6_addr.s6_addr;
|
||||
|
@ -172,39 +179,41 @@ static sockaddr_in6 MakeSinAddr(const Common::TraversalInetAddress& addr)
|
|||
|
||||
static void GetRandomHostId(Common::TraversalHostId* hostId)
|
||||
{
|
||||
char buf[9];
|
||||
char buf[9]{};
|
||||
const u32 num = Common::Random::GenerateValue<u32>();
|
||||
sprintf(buf, "%08x", num);
|
||||
fmt::format_to_n(buf, sizeof(buf) - 1, "{:08x}", num);
|
||||
memcpy(hostId->data(), buf, 8);
|
||||
}
|
||||
|
||||
static const char* SenderName(sockaddr_in6* addr)
|
||||
{
|
||||
static char buf[INET6_ADDRSTRLEN + 10];
|
||||
static char buf[INET6_ADDRSTRLEN + 10]{};
|
||||
inet_ntop(PF_INET6, &addr->sin6_addr, buf, sizeof(buf));
|
||||
sprintf(buf + strlen(buf), ":%d", ntohs(addr->sin6_port));
|
||||
fmt::format_to(buf + strlen(buf), ":{}", ntohs(addr->sin6_port));
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void TrySend(const void* buffer, size_t size, sockaddr_in6* addr)
|
||||
static void TrySend(const void* buffer, size_t size, sockaddr_in6* addr, bool fromAlt)
|
||||
{
|
||||
#if DEBUG
|
||||
const auto* packet = static_cast<const Common::TraversalPacket*>(buffer);
|
||||
printf("-> %d %llu %s\n", static_cast<int>(packet->type),
|
||||
static_cast<long long>(packet->requestId), SenderName(addr));
|
||||
fmt::print("{}-> {} {} {}\n", fromAlt ? "alt " : "", static_cast<int>(packet->type),
|
||||
static_cast<long long>(packet->requestId), SenderName(addr));
|
||||
#endif
|
||||
if ((size_t)sendto(sock, buffer, size, 0, (sockaddr*)addr, sizeof(*addr)) != size)
|
||||
if ((size_t)sendto(fromAlt ? sockAlt : sock, buffer, size, 0, (sockaddr*)addr, sizeof(*addr)) !=
|
||||
size)
|
||||
{
|
||||
perror("sendto");
|
||||
}
|
||||
}
|
||||
|
||||
static Common::TraversalPacket* AllocPacket(const sockaddr_in6& dest,
|
||||
static Common::TraversalPacket* AllocPacket(const sockaddr_in6& dest, bool fromAlt,
|
||||
Common::TraversalRequestId misc = 0)
|
||||
{
|
||||
Common::TraversalRequestId requestId{};
|
||||
Common::Random::Generate(&requestId, sizeof(requestId));
|
||||
OutgoingPacketInfo* info = &outgoingPackets[requestId];
|
||||
info->fromAlt = fromAlt;
|
||||
info->dest = dest;
|
||||
info->misc = misc;
|
||||
info->tries = 0;
|
||||
|
@ -219,12 +228,13 @@ static void SendPacket(OutgoingPacketInfo* info)
|
|||
{
|
||||
info->tries++;
|
||||
info->sendTime = currentTime;
|
||||
TrySend(&info->packet, sizeof(info->packet), &info->dest);
|
||||
TrySend(&info->packet, sizeof(info->packet), &info->dest, info->fromAlt);
|
||||
}
|
||||
|
||||
static void ResendPackets()
|
||||
{
|
||||
std::vector<std::pair<Common::TraversalInetAddress, Common::TraversalRequestId>> todoFailures;
|
||||
std::vector<std::tuple<Common::TraversalInetAddress, bool, Common::TraversalRequestId>>
|
||||
todoFailures;
|
||||
todoFailures.clear();
|
||||
for (auto it = outgoingPackets.begin(); it != outgoingPackets.end();)
|
||||
{
|
||||
|
@ -235,7 +245,8 @@ static void ResendPackets()
|
|||
{
|
||||
if (info->packet.type == Common::TraversalPacketType::PleaseSendPacket)
|
||||
{
|
||||
todoFailures.push_back(std::make_pair(info->packet.pleaseSendPacket.address, info->misc));
|
||||
todoFailures.push_back(
|
||||
std::make_tuple(info->packet.pleaseSendPacket.address, info->fromAlt, info->misc));
|
||||
}
|
||||
it = outgoingPackets.erase(it);
|
||||
continue;
|
||||
|
@ -250,18 +261,18 @@ static void ResendPackets()
|
|||
|
||||
for (const auto& p : todoFailures)
|
||||
{
|
||||
Common::TraversalPacket* fail = AllocPacket(MakeSinAddr(p.first));
|
||||
Common::TraversalPacket* fail = AllocPacket(MakeSinAddr(std::get<0>(p)), std::get<1>(p));
|
||||
fail->type = Common::TraversalPacketType::ConnectFailed;
|
||||
fail->connectFailed.requestId = p.second;
|
||||
fail->connectFailed.requestId = std::get<2>(p);
|
||||
fail->connectFailed.reason = Common::TraversalConnectFailedReason::ClientDidntRespond;
|
||||
}
|
||||
}
|
||||
|
||||
static void HandlePacket(Common::TraversalPacket* packet, sockaddr_in6* addr)
|
||||
static void HandlePacket(Common::TraversalPacket* packet, sockaddr_in6* addr, bool toAlt)
|
||||
{
|
||||
#if DEBUG
|
||||
printf("<- %d %llu %s\n", static_cast<int>(packet->type),
|
||||
static_cast<long long>(packet->requestId), SenderName(addr));
|
||||
fmt::print("<- {} {} {}\n", static_cast<int>(packet->type),
|
||||
static_cast<long long>(packet->requestId), SenderName(addr));
|
||||
#endif
|
||||
bool packetOk = true;
|
||||
switch (packet->type)
|
||||
|
@ -276,7 +287,7 @@ static void HandlePacket(Common::TraversalPacket* packet, sockaddr_in6* addr)
|
|||
|
||||
if (info->packet.type == Common::TraversalPacketType::PleaseSendPacket)
|
||||
{
|
||||
auto* ready = AllocPacket(MakeSinAddr(info->packet.pleaseSendPacket.address));
|
||||
auto* ready = AllocPacket(MakeSinAddr(info->packet.pleaseSendPacket.address), toAlt);
|
||||
if (packet->ack.ok)
|
||||
{
|
||||
ready->type = Common::TraversalPacketType::ConnectReady;
|
||||
|
@ -303,7 +314,7 @@ static void HandlePacket(Common::TraversalPacket* packet, sockaddr_in6* addr)
|
|||
case Common::TraversalPacketType::HelloFromClient:
|
||||
{
|
||||
u8 ok = packet->helloFromClient.protoVersion <= Common::TraversalProtoVersion;
|
||||
Common::TraversalPacket* reply = AllocPacket(*addr);
|
||||
Common::TraversalPacket* reply = AllocPacket(*addr, toAlt);
|
||||
reply->type = Common::TraversalPacketType::HelloFromServer;
|
||||
reply->helloFromServer.ok = ok;
|
||||
if (ok)
|
||||
|
@ -336,22 +347,38 @@ static void HandlePacket(Common::TraversalPacket* packet, sockaddr_in6* addr)
|
|||
auto r = EvictFind(connectedClients, hostId);
|
||||
if (!r.found)
|
||||
{
|
||||
Common::TraversalPacket* reply = AllocPacket(*addr);
|
||||
Common::TraversalPacket* reply = AllocPacket(*addr, toAlt);
|
||||
reply->type = Common::TraversalPacketType::ConnectFailed;
|
||||
reply->connectFailed.requestId = packet->requestId;
|
||||
reply->connectFailed.reason = Common::TraversalConnectFailedReason::NoSuchClient;
|
||||
}
|
||||
else
|
||||
{
|
||||
Common::TraversalPacket* please = AllocPacket(MakeSinAddr(*r.value), packet->requestId);
|
||||
Common::TraversalPacket* please =
|
||||
AllocPacket(MakeSinAddr(*r.value), toAlt, packet->requestId);
|
||||
please->type = Common::TraversalPacketType::PleaseSendPacket;
|
||||
please->pleaseSendPacket.address = MakeInetAddress(*addr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Common::TraversalPacketType::TestPlease:
|
||||
{
|
||||
Common::TraversalHostId& hostId = packet->testPlease.hostId;
|
||||
auto r = EvictFind(connectedClients, hostId);
|
||||
if (r.found)
|
||||
{
|
||||
Common::TraversalPacket ack = {};
|
||||
ack.type = Common::TraversalPacketType::Ack;
|
||||
ack.requestId = packet->requestId;
|
||||
ack.ack.ok = true;
|
||||
sockaddr_in6 mainAddr = MakeSinAddr(*r.value);
|
||||
TrySend(&ack, sizeof(ack), &mainAddr, toAlt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
fprintf(stderr, "received unknown packet type %d from %s\n", static_cast<int>(packet->type),
|
||||
SenderName(addr));
|
||||
fmt::print(stderr, "received unknown packet type {} from {}\n", static_cast<int>(packet->type),
|
||||
SenderName(addr));
|
||||
break;
|
||||
}
|
||||
if (packet->type != Common::TraversalPacketType::Ack)
|
||||
|
@ -360,7 +387,8 @@ static void HandlePacket(Common::TraversalPacket* packet, sockaddr_in6* addr)
|
|||
ack.type = Common::TraversalPacketType::Ack;
|
||||
ack.requestId = packet->requestId;
|
||||
ack.ack.ok = packetOk;
|
||||
TrySend(&ack, sizeof(ack), addr);
|
||||
TrySend(&ack, sizeof(ack), addr,
|
||||
packet->type != Common::TraversalPacketType::TestPlease ? toAlt : !toAlt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -373,6 +401,12 @@ int main()
|
|||
perror("socket");
|
||||
return 1;
|
||||
}
|
||||
sockAlt = socket(PF_INET6, SOCK_DGRAM, 0);
|
||||
if (sockAlt == -1)
|
||||
{
|
||||
perror("socket alt");
|
||||
return 1;
|
||||
}
|
||||
int no = 0;
|
||||
rv = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &no, sizeof(no));
|
||||
if (rv < 0)
|
||||
|
@ -380,6 +414,12 @@ int main()
|
|||
perror("setsockopt IPV6_V6ONLY");
|
||||
return 1;
|
||||
}
|
||||
rv = setsockopt(sockAlt, IPPROTO_IPV6, IPV6_V6ONLY, &no, sizeof(no));
|
||||
if (rv < 0)
|
||||
{
|
||||
perror("setsockopt IPV6_V6ONLY alt");
|
||||
return 1;
|
||||
}
|
||||
in6_addr any = IN6ADDR_ANY_INIT;
|
||||
sockaddr_in6 addr;
|
||||
#ifdef SIN6_LEN
|
||||
|
@ -397,6 +437,13 @@ int main()
|
|||
perror("bind");
|
||||
return 1;
|
||||
}
|
||||
addr.sin6_port = htons(PORT_ALT);
|
||||
rv = bind(sockAlt, (sockaddr*)&addr, sizeof(addr));
|
||||
if (rv < 0)
|
||||
{
|
||||
perror("bind alt");
|
||||
return 1;
|
||||
}
|
||||
|
||||
timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
|
@ -407,19 +454,55 @@ int main()
|
|||
perror("setsockopt SO_RCVTIMEO");
|
||||
return 1;
|
||||
}
|
||||
rv = setsockopt(sockAlt, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
if (rv < 0)
|
||||
{
|
||||
perror("setsockopt SO_RCVTIMEO alt");
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBSYSTEMD
|
||||
sd_notifyf(0, "READY=1\nSTATUS=Listening on port %d", PORT);
|
||||
sd_notifyf(0, "READY=1\nSTATUS=Listening on port %d (alt port: %d)", PORT, PORT_ALT);
|
||||
#endif
|
||||
|
||||
while (true)
|
||||
{
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 300000;
|
||||
fd_set readSet;
|
||||
FD_ZERO(&readSet);
|
||||
FD_SET(sock, &readSet);
|
||||
FD_SET(sockAlt, &readSet);
|
||||
rv = select(std::max(sock, sockAlt) + 1, &readSet, nullptr, nullptr, &tv);
|
||||
if (rv < 0)
|
||||
{
|
||||
if (errno != EINTR && errno != EAGAIN)
|
||||
{
|
||||
perror("recvfrom");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int recvsock;
|
||||
if (FD_ISSET(sock, &readSet))
|
||||
{
|
||||
recvsock = sock;
|
||||
}
|
||||
else if (FD_ISSET(sockAlt, &readSet))
|
||||
{
|
||||
recvsock = sockAlt;
|
||||
}
|
||||
else
|
||||
{
|
||||
ResendPackets();
|
||||
continue;
|
||||
}
|
||||
sockaddr_in6 raddr;
|
||||
socklen_t addrLen = sizeof(raddr);
|
||||
Common::TraversalPacket packet{};
|
||||
// note: switch to recvmmsg (yes, mmsg) if this becomes
|
||||
// expensive
|
||||
rv = recvfrom(sock, &packet, sizeof(packet), 0, (sockaddr*)&raddr, &addrLen);
|
||||
rv = recvfrom(recvsock, &packet, sizeof(packet), 0, (sockaddr*)&raddr, &addrLen);
|
||||
currentTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
@ -433,11 +516,11 @@ int main()
|
|||
}
|
||||
else if ((size_t)rv < sizeof(packet))
|
||||
{
|
||||
fprintf(stderr, "received short packet from %s\n", SenderName(&raddr));
|
||||
fmt::print(stderr, "received short packet from {}\n", SenderName(&raddr));
|
||||
}
|
||||
else
|
||||
{
|
||||
HandlePacket(&packet, &raddr);
|
||||
HandlePacket(&packet, &raddr, recvsock == sockAlt);
|
||||
}
|
||||
ResendPackets();
|
||||
#ifdef HAVE_LIBSYSTEMD
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue