use templates to only execute GDB stub related code if enabled
This commit is contained in:
parent
76c2723f5c
commit
dd386d12a9
266
src/ARM.cpp
266
src/ARM.cpp
|
@ -582,8 +582,10 @@ void ARM::CheckGdbIncoming()
|
||||||
GdbCheckA();
|
GdbCheckA();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <CPUExecuteMode mode>
|
||||||
void ARMv5::Execute()
|
void ARMv5::Execute()
|
||||||
{
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::InterpreterGDB)
|
||||||
GdbCheckB();
|
GdbCheckB();
|
||||||
|
|
||||||
if (Halted)
|
if (Halted)
|
||||||
|
@ -606,9 +608,49 @@ void ARMv5::Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
while (NDS.ARM9Timestamp < NDS.ARM9Target)
|
while (NDS.ARM9Timestamp < NDS.ARM9Target)
|
||||||
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::JIT)
|
||||||
|
{
|
||||||
|
u32 instrAddr = R[15] - ((CPSR&0x20)?2:4);
|
||||||
|
|
||||||
|
if ((instrAddr < FastBlockLookupStart || instrAddr >= (FastBlockLookupStart + FastBlockLookupSize))
|
||||||
|
&& !NDS.JIT.SetupExecutableRegion(0, instrAddr, FastBlockLookup, FastBlockLookupStart, FastBlockLookupSize))
|
||||||
|
{
|
||||||
|
NDS.ARM9Timestamp = NDS.ARM9Target;
|
||||||
|
Log(LogLevel::Error, "ARMv5 PC in non executable region %08X\n", R[15]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JitBlockEntry block = NDS.JIT.LookUpBlock(0, FastBlockLookup,
|
||||||
|
instrAddr - FastBlockLookupStart, instrAddr);
|
||||||
|
if (block)
|
||||||
|
ARM_Dispatch(this, block);
|
||||||
|
else
|
||||||
|
NDS.JIT.CompileBlock(this);
|
||||||
|
|
||||||
|
if (StopExecution)
|
||||||
|
{
|
||||||
|
// this order is crucial otherwise idle loops waiting for an IRQ won't function
|
||||||
|
if (IRQ)
|
||||||
|
TriggerIRQ();
|
||||||
|
|
||||||
|
if (Halted || IdleLoop)
|
||||||
|
{
|
||||||
|
if ((Halted == 1 || IdleLoop) && NDS.ARM9Timestamp < NDS.ARM9Target)
|
||||||
|
{
|
||||||
|
Cycles = 0;
|
||||||
|
NDS.ARM9Timestamp = NDS.ARM9Target;
|
||||||
|
}
|
||||||
|
IdleLoop = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (CPSR & 0x20) // THUMB
|
if (CPSR & 0x20) // THUMB
|
||||||
{
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::InterpreterGDB)
|
||||||
GdbCheckC();
|
GdbCheckC();
|
||||||
|
|
||||||
// prefetch
|
// prefetch
|
||||||
|
@ -624,6 +666,7 @@ void ARMv5::Execute()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::InterpreterGDB)
|
||||||
GdbCheckC();
|
GdbCheckC();
|
||||||
|
|
||||||
// prefetch
|
// prefetch
|
||||||
|
@ -662,6 +705,8 @@ void ARMv5::Execute()
|
||||||
}*/
|
}*/
|
||||||
if (IRQ) TriggerIRQ();
|
if (IRQ) TriggerIRQ();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
NDS.ARM9Timestamp += Cycles;
|
NDS.ARM9Timestamp += Cycles;
|
||||||
Cycles = 0;
|
Cycles = 0;
|
||||||
}
|
}
|
||||||
|
@ -669,77 +714,16 @@ void ARMv5::Execute()
|
||||||
if (Halted == 2)
|
if (Halted == 2)
|
||||||
Halted = 0;
|
Halted = 0;
|
||||||
}
|
}
|
||||||
|
template void ARMv5::Execute<CPUExecuteMode::Interpreter>();
|
||||||
|
template void ARMv5::Execute<CPUExecuteMode::InterpreterGDB>();
|
||||||
#ifdef JIT_ENABLED
|
#ifdef JIT_ENABLED
|
||||||
void ARMv5::ExecuteJIT()
|
template void ARMv5::Execute<CPUExecuteMode::JIT>();
|
||||||
{
|
|
||||||
if (Halted)
|
|
||||||
{
|
|
||||||
if (Halted == 2)
|
|
||||||
{
|
|
||||||
Halted = 0;
|
|
||||||
}
|
|
||||||
else if (NDS.HaltInterrupted(0))
|
|
||||||
{
|
|
||||||
Halted = 0;
|
|
||||||
if (NDS.IME[0] & 0x1)
|
|
||||||
TriggerIRQ();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
NDS.ARM9Timestamp = NDS.ARM9Target;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (NDS.ARM9Timestamp < NDS.ARM9Target)
|
|
||||||
{
|
|
||||||
u32 instrAddr = R[15] - ((CPSR&0x20)?2:4);
|
|
||||||
|
|
||||||
if ((instrAddr < FastBlockLookupStart || instrAddr >= (FastBlockLookupStart + FastBlockLookupSize))
|
|
||||||
&& !NDS.JIT.SetupExecutableRegion(0, instrAddr, FastBlockLookup, FastBlockLookupStart, FastBlockLookupSize))
|
|
||||||
{
|
|
||||||
NDS.ARM9Timestamp = NDS.ARM9Target;
|
|
||||||
Log(LogLevel::Error, "ARMv5 PC in non executable region %08X\n", R[15]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
JitBlockEntry block = NDS.JIT.LookUpBlock(0, FastBlockLookup,
|
|
||||||
instrAddr - FastBlockLookupStart, instrAddr);
|
|
||||||
if (block)
|
|
||||||
ARM_Dispatch(this, block);
|
|
||||||
else
|
|
||||||
NDS.JIT.CompileBlock(this);
|
|
||||||
|
|
||||||
if (StopExecution)
|
|
||||||
{
|
|
||||||
// this order is crucial otherwise idle loops waiting for an IRQ won't function
|
|
||||||
if (IRQ)
|
|
||||||
TriggerIRQ();
|
|
||||||
|
|
||||||
if (Halted || IdleLoop)
|
|
||||||
{
|
|
||||||
if ((Halted == 1 || IdleLoop) && NDS.ARM9Timestamp < NDS.ARM9Target)
|
|
||||||
{
|
|
||||||
Cycles = 0;
|
|
||||||
NDS.ARM9Timestamp = NDS.ARM9Target;
|
|
||||||
}
|
|
||||||
IdleLoop = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NDS.ARM9Timestamp += Cycles;
|
|
||||||
Cycles = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Halted == 2)
|
|
||||||
Halted = 0;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
template <CPUExecuteMode mode>
|
||||||
void ARMv4::Execute()
|
void ARMv4::Execute()
|
||||||
{
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::InterpreterGDB)
|
||||||
GdbCheckB();
|
GdbCheckB();
|
||||||
|
|
||||||
if (Halted)
|
if (Halted)
|
||||||
|
@ -763,95 +747,7 @@ void ARMv4::Execute()
|
||||||
|
|
||||||
while (NDS.ARM7Timestamp < NDS.ARM7Target)
|
while (NDS.ARM7Timestamp < NDS.ARM7Target)
|
||||||
{
|
{
|
||||||
if (CPSR & 0x20) // THUMB
|
if constexpr (mode == CPUExecuteMode::JIT)
|
||||||
{
|
|
||||||
GdbCheckC();
|
|
||||||
|
|
||||||
// prefetch
|
|
||||||
R[15] += 2;
|
|
||||||
CurInstr = NextInstr[0];
|
|
||||||
NextInstr[0] = NextInstr[1];
|
|
||||||
NextInstr[1] = CodeRead16(R[15]);
|
|
||||||
|
|
||||||
// actually execute
|
|
||||||
u32 icode = (CurInstr >> 6);
|
|
||||||
ARMInterpreter::THUMBInstrTable[icode](this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GdbCheckC();
|
|
||||||
|
|
||||||
// prefetch
|
|
||||||
R[15] += 4;
|
|
||||||
CurInstr = NextInstr[0];
|
|
||||||
NextInstr[0] = NextInstr[1];
|
|
||||||
NextInstr[1] = CodeRead32(R[15]);
|
|
||||||
|
|
||||||
// actually execute
|
|
||||||
if (CheckCondition(CurInstr >> 28))
|
|
||||||
{
|
|
||||||
u32 icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0);
|
|
||||||
ARMInterpreter::ARMInstrTable[icode](this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
AddCycles_C();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO optimize this shit!!!
|
|
||||||
if (Halted)
|
|
||||||
{
|
|
||||||
if (Halted == 1 && NDS.ARM7Timestamp < NDS.ARM7Target)
|
|
||||||
{
|
|
||||||
NDS.ARM7Timestamp = NDS.ARM7Target;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/*if (NDS::IF[1] & NDS::IE[1])
|
|
||||||
{
|
|
||||||
if (NDS::IME[1] & 0x1)
|
|
||||||
TriggerIRQ();
|
|
||||||
}*/
|
|
||||||
if (IRQ) TriggerIRQ();
|
|
||||||
|
|
||||||
NDS.ARM7Timestamp += Cycles;
|
|
||||||
Cycles = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Halted == 2)
|
|
||||||
Halted = 0;
|
|
||||||
|
|
||||||
if (Halted == 4)
|
|
||||||
{
|
|
||||||
assert(NDS.ConsoleType == 1);
|
|
||||||
auto& dsi = dynamic_cast<melonDS::DSi&>(NDS);
|
|
||||||
dsi.SoftReset();
|
|
||||||
Halted = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef JIT_ENABLED
|
|
||||||
void ARMv4::ExecuteJIT()
|
|
||||||
{
|
|
||||||
if (Halted)
|
|
||||||
{
|
|
||||||
if (Halted == 2)
|
|
||||||
{
|
|
||||||
Halted = 0;
|
|
||||||
}
|
|
||||||
else if (NDS.HaltInterrupted(1))
|
|
||||||
{
|
|
||||||
Halted = 0;
|
|
||||||
if (NDS.IME[1] & 0x1)
|
|
||||||
TriggerIRQ();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
NDS.ARM7Timestamp = NDS.ARM7Target;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (NDS.ARM7Timestamp < NDS.ARM7Target)
|
|
||||||
{
|
{
|
||||||
u32 instrAddr = R[15] - ((CPSR&0x20)?2:4);
|
u32 instrAddr = R[15] - ((CPSR&0x20)?2:4);
|
||||||
|
|
||||||
|
@ -886,6 +782,61 @@ void ARMv4::ExecuteJIT()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (CPSR & 0x20) // THUMB
|
||||||
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::InterpreterGDB)
|
||||||
|
GdbCheckC();
|
||||||
|
|
||||||
|
// prefetch
|
||||||
|
R[15] += 2;
|
||||||
|
CurInstr = NextInstr[0];
|
||||||
|
NextInstr[0] = NextInstr[1];
|
||||||
|
NextInstr[1] = CodeRead16(R[15]);
|
||||||
|
|
||||||
|
// actually execute
|
||||||
|
u32 icode = (CurInstr >> 6);
|
||||||
|
ARMInterpreter::THUMBInstrTable[icode](this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if constexpr (mode == CPUExecuteMode::InterpreterGDB)
|
||||||
|
GdbCheckC();
|
||||||
|
|
||||||
|
// prefetch
|
||||||
|
R[15] += 4;
|
||||||
|
CurInstr = NextInstr[0];
|
||||||
|
NextInstr[0] = NextInstr[1];
|
||||||
|
NextInstr[1] = CodeRead32(R[15]);
|
||||||
|
|
||||||
|
// actually execute
|
||||||
|
if (CheckCondition(CurInstr >> 28))
|
||||||
|
{
|
||||||
|
u32 icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0);
|
||||||
|
ARMInterpreter::ARMInstrTable[icode](this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
AddCycles_C();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO optimize this shit!!!
|
||||||
|
if (Halted)
|
||||||
|
{
|
||||||
|
if (Halted == 1 && NDS.ARM7Timestamp < NDS.ARM7Target)
|
||||||
|
{
|
||||||
|
NDS.ARM7Timestamp = NDS.ARM7Target;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/*if (NDS::IF[1] & NDS::IE[1])
|
||||||
|
{
|
||||||
|
if (NDS::IME[1] & 0x1)
|
||||||
|
TriggerIRQ();
|
||||||
|
}*/
|
||||||
|
if (IRQ) TriggerIRQ();
|
||||||
|
}
|
||||||
|
|
||||||
NDS.ARM7Timestamp += Cycles;
|
NDS.ARM7Timestamp += Cycles;
|
||||||
Cycles = 0;
|
Cycles = 0;
|
||||||
|
@ -902,6 +853,11 @@ void ARMv4::ExecuteJIT()
|
||||||
Halted = 2;
|
Halted = 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template void ARMv4::Execute<CPUExecuteMode::Interpreter>();
|
||||||
|
template void ARMv4::Execute<CPUExecuteMode::InterpreterGDB>();
|
||||||
|
#ifdef JIT_ENABLED
|
||||||
|
template void ARMv4::Execute<CPUExecuteMode::JIT>();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void ARMv5::FillPipeline()
|
void ARMv5::FillPipeline()
|
||||||
|
|
25
src/ARM.h
25
src/ARM.h
|
@ -43,6 +43,15 @@ enum
|
||||||
RWFlags_ForceUser = (1<<21),
|
RWFlags_ForceUser = (1<<21),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class CPUExecuteMode : u32
|
||||||
|
{
|
||||||
|
Interpreter,
|
||||||
|
InterpreterGDB,
|
||||||
|
#ifdef JIT_ENABLED
|
||||||
|
JIT
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
struct GDBArgs;
|
struct GDBArgs;
|
||||||
class ARMJIT;
|
class ARMJIT;
|
||||||
class GPU;
|
class GPU;
|
||||||
|
@ -75,10 +84,6 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void NocashPrint(u32 addr) noexcept;
|
void NocashPrint(u32 addr) noexcept;
|
||||||
virtual void Execute() = 0;
|
|
||||||
#ifdef JIT_ENABLED
|
|
||||||
virtual void ExecuteJIT() = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool CheckCondition(u32 code) const
|
bool CheckCondition(u32 code) const
|
||||||
{
|
{
|
||||||
|
@ -241,10 +246,8 @@ public:
|
||||||
void PrefetchAbort();
|
void PrefetchAbort();
|
||||||
void DataAbort();
|
void DataAbort();
|
||||||
|
|
||||||
void Execute() override;
|
template <CPUExecuteMode mode>
|
||||||
#ifdef JIT_ENABLED
|
void Execute();
|
||||||
void ExecuteJIT() override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// all code accesses are forced nonseq 32bit
|
// all code accesses are forced nonseq 32bit
|
||||||
u32 CodeRead32(u32 addr, bool branch);
|
u32 CodeRead32(u32 addr, bool branch);
|
||||||
|
@ -383,10 +386,8 @@ public:
|
||||||
|
|
||||||
void JumpTo(u32 addr, bool restorecpsr = false) override;
|
void JumpTo(u32 addr, bool restorecpsr = false) override;
|
||||||
|
|
||||||
void Execute() override;
|
template <CPUExecuteMode mode>
|
||||||
#ifdef JIT_ENABLED
|
void Execute();
|
||||||
void ExecuteJIT() override;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
u16 CodeRead16(u32 addr)
|
u16 CodeRead16(u32 addr)
|
||||||
{
|
{
|
||||||
|
|
34
src/NDS.cpp
34
src/NDS.cpp
|
@ -107,6 +107,9 @@ NDS::NDS(NDSArgs&& args, int type, void* userdata) noexcept :
|
||||||
AREngine(*this),
|
AREngine(*this),
|
||||||
ARM9(*this, args.GDB, args.JIT.has_value()),
|
ARM9(*this, args.GDB, args.JIT.has_value()),
|
||||||
ARM7(*this, args.GDB, args.JIT.has_value()),
|
ARM7(*this, args.GDB, args.JIT.has_value()),
|
||||||
|
#ifdef GDBSTUB_ENABLED
|
||||||
|
EnableGDBStub(args.GDB.has_value()),
|
||||||
|
#endif
|
||||||
#ifdef JIT_ENABLED
|
#ifdef JIT_ENABLED
|
||||||
EnableJIT(args.JIT.has_value()),
|
EnableJIT(args.JIT.has_value()),
|
||||||
#endif
|
#endif
|
||||||
|
@ -886,7 +889,7 @@ void NDS::RunSystemSleep(u64 timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool EnableJIT>
|
template <CPUExecuteMode cpuMode>
|
||||||
u32 NDS::RunFrame()
|
u32 NDS::RunFrame()
|
||||||
{
|
{
|
||||||
FrameStartTimestamp = SysTimestamp;
|
FrameStartTimestamp = SysTimestamp;
|
||||||
|
@ -926,9 +929,12 @@ u32 NDS::RunFrame()
|
||||||
GPU.BlankFrame();
|
GPU.BlankFrame();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (cpuMode == CPUExecuteMode::InterpreterGDB)
|
||||||
{
|
{
|
||||||
ARM9.CheckGdbIncoming();
|
ARM9.CheckGdbIncoming();
|
||||||
ARM7.CheckGdbIncoming();
|
ARM7.CheckGdbIncoming();
|
||||||
|
}
|
||||||
|
|
||||||
if (!(CPUStop & CPUStop_Wakeup))
|
if (!(CPUStop & CPUStop_Wakeup))
|
||||||
{
|
{
|
||||||
|
@ -963,12 +969,7 @@ u32 NDS::RunFrame()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef JIT_ENABLED
|
ARM9.Execute<cpuMode>();
|
||||||
if (EnableJIT)
|
|
||||||
ARM9.ExecuteJIT();
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
ARM9.Execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RunTimers(0);
|
RunTimers(0);
|
||||||
|
@ -995,12 +996,7 @@ u32 NDS::RunFrame()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef JIT_ENABLED
|
ARM7.Execute<cpuMode>();
|
||||||
if (EnableJIT)
|
|
||||||
ARM7.ExecuteJIT();
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
ARM7.Execute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RunTimers(1);
|
RunTimers(1);
|
||||||
|
@ -1045,10 +1041,18 @@ u32 NDS::RunFrame()
|
||||||
{
|
{
|
||||||
#ifdef JIT_ENABLED
|
#ifdef JIT_ENABLED
|
||||||
if (EnableJIT)
|
if (EnableJIT)
|
||||||
return RunFrame<true>();
|
return RunFrame<CPUExecuteMode::JIT>();
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
return RunFrame<false>();
|
#ifdef GDBSTUB_ENABLED
|
||||||
|
if (EnableGDBStub)
|
||||||
|
{
|
||||||
|
return RunFrame<CPUExecuteMode::InterpreterGDB>();
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
return RunFrame<CPUExecuteMode::Interpreter>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NDS::Reschedule(u64 target)
|
void NDS::Reschedule(u64 target)
|
||||||
|
|
|
@ -228,6 +228,9 @@ private:
|
||||||
#ifdef JIT_ENABLED
|
#ifdef JIT_ENABLED
|
||||||
bool EnableJIT;
|
bool EnableJIT;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef GDBSTUB_ENABLED
|
||||||
|
bool EnableGDBStub = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
public: // TODO: Encapsulate the rest of these members
|
public: // TODO: Encapsulate the rest of these members
|
||||||
void* UserData;
|
void* UserData;
|
||||||
|
@ -522,8 +525,9 @@ private:
|
||||||
void SetWifiWaitCnt(u16 val);
|
void SetWifiWaitCnt(u16 val);
|
||||||
void SetGBASlotTimings();
|
void SetGBASlotTimings();
|
||||||
void EnterSleepMode();
|
void EnterSleepMode();
|
||||||
template <bool EnableJIT>
|
template <CPUExecuteMode cpuMode>
|
||||||
u32 RunFrame();
|
u32 RunFrame();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NDS(NDSArgs&& args, void* userdata = nullptr) noexcept : NDS(std::move(args), 0, userdata) {}
|
NDS(NDSArgs&& args, void* userdata = nullptr) noexcept : NDS(std::move(args), 0, userdata) {}
|
||||||
NDS() noexcept;
|
NDS() noexcept;
|
||||||
|
|
Loading…
Reference in New Issue