mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-14 21:37:52 -07:00
6f473b96d0
Makes the enum values strongly-typed and prevents the identifiers from polluting the PowerPC namespace. This also cleans up the parameters of some functions where we were accepting an ambiguous int type and expecting the correct values to be passed in. Now those parameters accept a PowerPC::CPUCore type only, making it immediately obvious which values should be passed in. It also turns out we were storing these core types into other structures as plain ints, which have also been corrected. As this type is used directly with the configuration code, we need to provide our own overloaded insertion (<<) and extraction (>>) operators in order to make it compatible with it. These are fairly trivial to implement, so there's no issue here. A minor adjustment to TryParse() was required, as our generic function was doing the following: N tmp = 0; which is problematic, as custom types may not be able to have that assignment performed (e.g. strongly-typed enums), so we change this to: N tmp; which is sufficient, as the value is attempted to be initialized immediately under that statement.
350 lines
11 KiB
C++
350 lines
11 KiB
C++
// Copyright 2016 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <array>
|
|
#include <bitset>
|
|
#include <string>
|
|
|
|
#include "Common/Config/Config.h"
|
|
#include "Common/FileUtil.h"
|
|
#include "Core/ConfigManager.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/PowerPC/PowerPC.h"
|
|
#include "UICommon/UICommon.h"
|
|
|
|
// Numbers are chosen randomly to make sure the correct one is given.
|
|
static constexpr std::array<u64, 5> CB_IDS{{42, 144, 93, 1026, UINT64_C(0xFFFF7FFFF7FFFF)}};
|
|
static constexpr int MAX_SLICE_LENGTH = 20000; // Copied from CoreTiming internals
|
|
|
|
static std::bitset<CB_IDS.size()> s_callbacks_ran_flags;
|
|
static u64 s_expected_callback = 0;
|
|
static s64 s_lateness = 0;
|
|
|
|
template <unsigned int IDX>
|
|
void CallbackTemplate(u64 userdata, s64 lateness)
|
|
{
|
|
static_assert(IDX < CB_IDS.size(), "IDX out of range");
|
|
s_callbacks_ran_flags.set(IDX);
|
|
EXPECT_EQ(CB_IDS[IDX], userdata);
|
|
EXPECT_EQ(CB_IDS[IDX], s_expected_callback);
|
|
EXPECT_EQ(s_lateness, lateness);
|
|
}
|
|
|
|
class ScopeInit final
|
|
{
|
|
public:
|
|
ScopeInit() : m_profile_path(File::CreateTempDir())
|
|
{
|
|
Core::DeclareAsCPUThread();
|
|
UICommon::SetUserDirectory(m_profile_path);
|
|
Config::Init();
|
|
SConfig::Init();
|
|
PowerPC::Init(PowerPC::CPUCore::Interpreter);
|
|
CoreTiming::Init();
|
|
}
|
|
~ScopeInit()
|
|
{
|
|
CoreTiming::Shutdown();
|
|
PowerPC::Shutdown();
|
|
SConfig::Shutdown();
|
|
Config::Shutdown();
|
|
Core::UndeclareAsCPUThread();
|
|
File::DeleteDirRecursively(m_profile_path);
|
|
}
|
|
|
|
private:
|
|
std::string m_profile_path;
|
|
};
|
|
|
|
static void AdvanceAndCheck(u32 idx, int downcount, int expected_lateness = 0,
|
|
int cpu_downcount = 0)
|
|
{
|
|
s_callbacks_ran_flags = 0;
|
|
s_expected_callback = CB_IDS[idx];
|
|
s_lateness = expected_lateness;
|
|
|
|
PowerPC::ppcState.downcount = cpu_downcount; // Pretend we executed X cycles of instructions.
|
|
CoreTiming::Advance();
|
|
|
|
EXPECT_EQ(decltype(s_callbacks_ran_flags)().set(idx), s_callbacks_ran_flags);
|
|
EXPECT_EQ(downcount, PowerPC::ppcState.downcount);
|
|
}
|
|
|
|
TEST(CoreTiming, BasicOrder)
|
|
{
|
|
ScopeInit guard;
|
|
|
|
CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
|
|
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
|
|
CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", CallbackTemplate<2>);
|
|
CoreTiming::EventType* cb_d = CoreTiming::RegisterEvent("callbackD", CallbackTemplate<3>);
|
|
CoreTiming::EventType* cb_e = CoreTiming::RegisterEvent("callbackE", CallbackTemplate<4>);
|
|
|
|
// Enter slice 0
|
|
CoreTiming::Advance();
|
|
|
|
// D -> B -> C -> A -> E
|
|
CoreTiming::ScheduleEvent(1000, cb_a, CB_IDS[0]);
|
|
EXPECT_EQ(1000, PowerPC::ppcState.downcount);
|
|
CoreTiming::ScheduleEvent(500, cb_b, CB_IDS[1]);
|
|
EXPECT_EQ(500, PowerPC::ppcState.downcount);
|
|
CoreTiming::ScheduleEvent(800, cb_c, CB_IDS[2]);
|
|
EXPECT_EQ(500, PowerPC::ppcState.downcount);
|
|
CoreTiming::ScheduleEvent(100, cb_d, CB_IDS[3]);
|
|
EXPECT_EQ(100, PowerPC::ppcState.downcount);
|
|
CoreTiming::ScheduleEvent(1200, cb_e, CB_IDS[4]);
|
|
EXPECT_EQ(100, PowerPC::ppcState.downcount);
|
|
|
|
AdvanceAndCheck(3, 400);
|
|
AdvanceAndCheck(1, 300);
|
|
AdvanceAndCheck(2, 200);
|
|
AdvanceAndCheck(0, 200);
|
|
AdvanceAndCheck(4, MAX_SLICE_LENGTH);
|
|
}
|
|
|
|
namespace SharedSlotTest
|
|
{
|
|
static unsigned int s_counter = 0;
|
|
|
|
template <unsigned int ID>
|
|
void FifoCallback(u64 userdata, s64 lateness)
|
|
{
|
|
static_assert(ID < CB_IDS.size(), "ID out of range");
|
|
s_callbacks_ran_flags.set(ID);
|
|
EXPECT_EQ(CB_IDS[ID], userdata);
|
|
EXPECT_EQ(ID, s_counter);
|
|
EXPECT_EQ(s_lateness, lateness);
|
|
++s_counter;
|
|
}
|
|
}
|
|
|
|
TEST(CoreTiming, SharedSlot)
|
|
{
|
|
using namespace SharedSlotTest;
|
|
|
|
ScopeInit guard;
|
|
|
|
CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", FifoCallback<0>);
|
|
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", FifoCallback<1>);
|
|
CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", FifoCallback<2>);
|
|
CoreTiming::EventType* cb_d = CoreTiming::RegisterEvent("callbackD", FifoCallback<3>);
|
|
CoreTiming::EventType* cb_e = CoreTiming::RegisterEvent("callbackE", FifoCallback<4>);
|
|
|
|
CoreTiming::ScheduleEvent(1000, cb_a, CB_IDS[0]);
|
|
CoreTiming::ScheduleEvent(1000, cb_b, CB_IDS[1]);
|
|
CoreTiming::ScheduleEvent(1000, cb_c, CB_IDS[2]);
|
|
CoreTiming::ScheduleEvent(1000, cb_d, CB_IDS[3]);
|
|
CoreTiming::ScheduleEvent(1000, cb_e, CB_IDS[4]);
|
|
|
|
// Enter slice 0
|
|
CoreTiming::Advance();
|
|
EXPECT_EQ(1000, PowerPC::ppcState.downcount);
|
|
|
|
s_callbacks_ran_flags = 0;
|
|
s_counter = 0;
|
|
s_lateness = 0;
|
|
PowerPC::ppcState.downcount = 0;
|
|
CoreTiming::Advance();
|
|
EXPECT_EQ(MAX_SLICE_LENGTH, PowerPC::ppcState.downcount);
|
|
EXPECT_EQ(0x1FULL, s_callbacks_ran_flags.to_ullong());
|
|
}
|
|
|
|
TEST(CoreTiming, PredictableLateness)
|
|
{
|
|
ScopeInit guard;
|
|
|
|
CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
|
|
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
|
|
|
|
// Enter slice 0
|
|
CoreTiming::Advance();
|
|
|
|
CoreTiming::ScheduleEvent(100, cb_a, CB_IDS[0]);
|
|
CoreTiming::ScheduleEvent(200, cb_b, CB_IDS[1]);
|
|
|
|
AdvanceAndCheck(0, 90, 10, -10); // (100 - 10)
|
|
AdvanceAndCheck(1, MAX_SLICE_LENGTH, 50, -50);
|
|
}
|
|
|
|
namespace ChainSchedulingTest
|
|
{
|
|
static int s_reschedules = 0;
|
|
|
|
static void RescheduleCallback(u64 userdata, s64 lateness)
|
|
{
|
|
--s_reschedules;
|
|
EXPECT_TRUE(s_reschedules >= 0);
|
|
EXPECT_EQ(s_lateness, lateness);
|
|
|
|
if (s_reschedules > 0)
|
|
CoreTiming::ScheduleEvent(1000, reinterpret_cast<CoreTiming::EventType*>(userdata), userdata);
|
|
}
|
|
}
|
|
|
|
TEST(CoreTiming, ChainScheduling)
|
|
{
|
|
using namespace ChainSchedulingTest;
|
|
|
|
ScopeInit guard;
|
|
|
|
CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
|
|
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
|
|
CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", CallbackTemplate<2>);
|
|
CoreTiming::EventType* cb_rs =
|
|
CoreTiming::RegisterEvent("callbackReschedule", RescheduleCallback);
|
|
|
|
// Enter slice 0
|
|
CoreTiming::Advance();
|
|
|
|
CoreTiming::ScheduleEvent(800, cb_a, CB_IDS[0]);
|
|
CoreTiming::ScheduleEvent(1000, cb_b, CB_IDS[1]);
|
|
CoreTiming::ScheduleEvent(2200, cb_c, CB_IDS[2]);
|
|
CoreTiming::ScheduleEvent(1000, cb_rs, reinterpret_cast<u64>(cb_rs));
|
|
EXPECT_EQ(800, PowerPC::ppcState.downcount);
|
|
|
|
s_reschedules = 3;
|
|
AdvanceAndCheck(0, 200); // cb_a
|
|
AdvanceAndCheck(1, 1000); // cb_b, cb_rs
|
|
EXPECT_EQ(2, s_reschedules);
|
|
|
|
PowerPC::ppcState.downcount = 0;
|
|
CoreTiming::Advance(); // cb_rs
|
|
EXPECT_EQ(1, s_reschedules);
|
|
EXPECT_EQ(200, PowerPC::ppcState.downcount);
|
|
|
|
AdvanceAndCheck(2, 800); // cb_c
|
|
|
|
PowerPC::ppcState.downcount = 0;
|
|
CoreTiming::Advance(); // cb_rs
|
|
EXPECT_EQ(0, s_reschedules);
|
|
EXPECT_EQ(MAX_SLICE_LENGTH, PowerPC::ppcState.downcount);
|
|
}
|
|
|
|
namespace ScheduleIntoPastTest
|
|
{
|
|
static CoreTiming::EventType* s_cb_next = nullptr;
|
|
|
|
static void ChainCallback(u64 userdata, s64 lateness)
|
|
{
|
|
EXPECT_EQ(CB_IDS[0] + 1, userdata);
|
|
EXPECT_EQ(0, lateness);
|
|
|
|
CoreTiming::ScheduleEvent(-1000, s_cb_next, userdata - 1);
|
|
}
|
|
}
|
|
|
|
// This can happen when scheduling from outside the CPU Thread.
|
|
// Also, if the callback is very late, it may reschedule itself for the next period which
|
|
// is also in the past.
|
|
TEST(CoreTiming, ScheduleIntoPast)
|
|
{
|
|
using namespace ScheduleIntoPastTest;
|
|
|
|
ScopeInit guard;
|
|
|
|
s_cb_next = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
|
|
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
|
|
CoreTiming::EventType* cb_chain = CoreTiming::RegisterEvent("callbackChain", ChainCallback);
|
|
|
|
// Enter slice 0
|
|
CoreTiming::Advance();
|
|
|
|
CoreTiming::ScheduleEvent(1000, cb_chain, CB_IDS[0] + 1);
|
|
EXPECT_EQ(1000, PowerPC::ppcState.downcount);
|
|
|
|
AdvanceAndCheck(0, MAX_SLICE_LENGTH, 1000); // Run cb_chain into late cb_a
|
|
|
|
// Schedule late from wrong thread
|
|
// The problem with scheduling CPU events from outside the CPU Thread is that g_global_timer
|
|
// is not reliable outside the CPU Thread. It's possible for the other thread to sample the
|
|
// global timer right before the timer is updated by Advance() then submit a new event using
|
|
// the stale value, i.e. effectively half-way through the previous slice.
|
|
// NOTE: We're only testing that the scheduler doesn't break, not whether this makes sense.
|
|
Core::UndeclareAsCPUThread();
|
|
CoreTiming::g.global_timer -= 1000;
|
|
CoreTiming::ScheduleEvent(0, cb_b, CB_IDS[1], CoreTiming::FromThread::NON_CPU);
|
|
CoreTiming::g.global_timer += 1000;
|
|
Core::DeclareAsCPUThread();
|
|
AdvanceAndCheck(1, MAX_SLICE_LENGTH, MAX_SLICE_LENGTH + 1000);
|
|
|
|
// Schedule directly into the past from the CPU.
|
|
// This shouldn't happen in practice, but it's best if we don't mess up the slice length and
|
|
// downcount if we do.
|
|
CoreTiming::ScheduleEvent(-1000, s_cb_next, CB_IDS[0]);
|
|
EXPECT_EQ(0, PowerPC::ppcState.downcount);
|
|
AdvanceAndCheck(0, MAX_SLICE_LENGTH, 1000);
|
|
}
|
|
|
|
TEST(CoreTiming, Overclocking)
|
|
{
|
|
ScopeInit guard;
|
|
|
|
CoreTiming::EventType* cb_a = CoreTiming::RegisterEvent("callbackA", CallbackTemplate<0>);
|
|
CoreTiming::EventType* cb_b = CoreTiming::RegisterEvent("callbackB", CallbackTemplate<1>);
|
|
CoreTiming::EventType* cb_c = CoreTiming::RegisterEvent("callbackC", CallbackTemplate<2>);
|
|
CoreTiming::EventType* cb_d = CoreTiming::RegisterEvent("callbackD", CallbackTemplate<3>);
|
|
CoreTiming::EventType* cb_e = CoreTiming::RegisterEvent("callbackE", CallbackTemplate<4>);
|
|
|
|
// Overclock
|
|
SConfig::GetInstance().m_OCEnable = true;
|
|
SConfig::GetInstance().m_OCFactor = 2.0;
|
|
|
|
// Enter slice 0
|
|
// Updates s_last_OC_factor.
|
|
CoreTiming::Advance();
|
|
|
|
CoreTiming::ScheduleEvent(100, cb_a, CB_IDS[0]);
|
|
CoreTiming::ScheduleEvent(200, cb_b, CB_IDS[1]);
|
|
CoreTiming::ScheduleEvent(400, cb_c, CB_IDS[2]);
|
|
CoreTiming::ScheduleEvent(800, cb_d, CB_IDS[3]);
|
|
CoreTiming::ScheduleEvent(1600, cb_e, CB_IDS[4]);
|
|
EXPECT_EQ(200, PowerPC::ppcState.downcount);
|
|
|
|
AdvanceAndCheck(0, 200); // (200 - 100) * 2
|
|
AdvanceAndCheck(1, 400); // (400 - 200) * 2
|
|
AdvanceAndCheck(2, 800); // (800 - 400) * 2
|
|
AdvanceAndCheck(3, 1600); // (1600 - 800) * 2
|
|
AdvanceAndCheck(4, MAX_SLICE_LENGTH * 2);
|
|
|
|
// Underclock
|
|
SConfig::GetInstance().m_OCFactor = 0.5;
|
|
CoreTiming::Advance();
|
|
|
|
CoreTiming::ScheduleEvent(100, cb_a, CB_IDS[0]);
|
|
CoreTiming::ScheduleEvent(200, cb_b, CB_IDS[1]);
|
|
CoreTiming::ScheduleEvent(400, cb_c, CB_IDS[2]);
|
|
CoreTiming::ScheduleEvent(800, cb_d, CB_IDS[3]);
|
|
CoreTiming::ScheduleEvent(1600, cb_e, CB_IDS[4]);
|
|
EXPECT_EQ(50, PowerPC::ppcState.downcount);
|
|
|
|
AdvanceAndCheck(0, 50); // (200 - 100) / 2
|
|
AdvanceAndCheck(1, 100); // (400 - 200) / 2
|
|
AdvanceAndCheck(2, 200); // (800 - 400) / 2
|
|
AdvanceAndCheck(3, 400); // (1600 - 800) / 2
|
|
AdvanceAndCheck(4, MAX_SLICE_LENGTH / 2);
|
|
|
|
// Try switching the clock mid-emulation
|
|
SConfig::GetInstance().m_OCFactor = 1.0;
|
|
CoreTiming::Advance();
|
|
|
|
CoreTiming::ScheduleEvent(100, cb_a, CB_IDS[0]);
|
|
CoreTiming::ScheduleEvent(200, cb_b, CB_IDS[1]);
|
|
CoreTiming::ScheduleEvent(400, cb_c, CB_IDS[2]);
|
|
CoreTiming::ScheduleEvent(800, cb_d, CB_IDS[3]);
|
|
CoreTiming::ScheduleEvent(1600, cb_e, CB_IDS[4]);
|
|
EXPECT_EQ(100, PowerPC::ppcState.downcount);
|
|
|
|
AdvanceAndCheck(0, 100); // (200 - 100)
|
|
SConfig::GetInstance().m_OCFactor = 2.0;
|
|
AdvanceAndCheck(1, 400); // (400 - 200) * 2
|
|
AdvanceAndCheck(2, 800); // (800 - 400) * 2
|
|
SConfig::GetInstance().m_OCFactor = 0.1f;
|
|
AdvanceAndCheck(3, 80); // (1600 - 800) / 10
|
|
SConfig::GetInstance().m_OCFactor = 1.0;
|
|
AdvanceAndCheck(4, MAX_SLICE_LENGTH);
|
|
}
|