2 changed files with 444 additions and 0 deletions
@ -0,0 +1,443 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
// This file references various implementation details from Atmosphere, an open-source firmware for |
|||
// the Nintendo Switch. Copyright 2018-2020 Atmosphere-NX. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/assert.h" |
|||
#include "common/bit_set.h" |
|||
#include "common/bit_util.h" |
|||
#include "common/common_types.h" |
|||
|
|||
namespace Kernel { |
|||
|
|||
class Thread; |
|||
|
|||
template <typename T> |
|||
concept KPriorityQueueAffinityMask = !std::is_reference<T>::value && requires(T & t) { |
|||
{ t.GetAffinityMask() } |
|||
->std::convertible_to<u64>; |
|||
{t.SetAffinityMask(std::declval<u64>())}; |
|||
|
|||
{ t.GetAffinity(std::declval<int32_t>()) } |
|||
->std::same_as<bool>; |
|||
{t.SetAffinity(std::declval<int32_t>(), std::declval<bool>())}; |
|||
{t.SetAll()}; |
|||
}; |
|||
|
|||
template <typename T> |
|||
concept KPriorityQueueMember = !std::is_reference<T>::value && requires(T & t) { |
|||
{typename T::QueueEntry()}; |
|||
{(typename T::QueueEntry()).Initialize()}; |
|||
{(typename T::QueueEntry()).SetPrev(std::addressof(t))}; |
|||
{(typename T::QueueEntry()).SetNext(std::addressof(t))}; |
|||
{ (typename T::QueueEntry()).GetNext() } |
|||
->std::same_as<T*>; |
|||
{ (typename T::QueueEntry()).GetPrev() } |
|||
->std::same_as<T*>; |
|||
{ t.GetPriorityQueueEntry(std::declval<s32>()) } |
|||
->std::same_as<typename T::QueueEntry&>; |
|||
|
|||
{t.GetAffinityMask()}; |
|||
{ typename std::remove_cvref<decltype(t.GetAffinityMask())>::type() } |
|||
->KPriorityQueueAffinityMask; |
|||
|
|||
{ t.GetActiveCore() } |
|||
->std::convertible_to<s32>; |
|||
{ t.GetPriority() } |
|||
->std::convertible_to<s32>; |
|||
}; |
|||
|
|||
template <typename Member, size_t _NumCores, int LowestPriority, int HighestPriority> |
|||
requires KPriorityQueueMember<Member> class KPriorityQueue { |
|||
public: |
|||
using AffinityMaskType = typename std::remove_cv<typename std::remove_reference<decltype( |
|||
std::declval<Member>().GetAffinityMask())>::type>::type; |
|||
|
|||
static_assert(LowestPriority >= 0); |
|||
static_assert(HighestPriority >= 0); |
|||
static_assert(LowestPriority >= HighestPriority); |
|||
static constexpr size_t NumPriority = LowestPriority - HighestPriority + 1; |
|||
static constexpr size_t NumCores = _NumCores; |
|||
|
|||
static constexpr bool IsValidCore(s32 core) { |
|||
return 0 <= core && core < static_cast<s32>(NumCores); |
|||
} |
|||
|
|||
static constexpr bool IsValidPriority(s32 priority) { |
|||
return HighestPriority <= priority && priority <= LowestPriority + 1; |
|||
} |
|||
|
|||
private: |
|||
using Entry = typename Member::QueueEntry; |
|||
|
|||
public: |
|||
class KPerCoreQueue { |
|||
private: |
|||
Entry root[NumCores]; |
|||
|
|||
public: |
|||
constexpr KPerCoreQueue() : root() { |
|||
for (size_t i = 0; i < NumCores; i++) { |
|||
this->root[i].Initialize(); |
|||
} |
|||
} |
|||
|
|||
constexpr bool PushBack(s32 core, Member* member) { |
|||
/* Get the entry associated with the member. */ |
|||
Entry& member_entry = member->GetPriorityQueueEntry(core); |
|||
|
|||
/* Get the entry associated with the end of the queue. */ |
|||
Member* tail = this->root[core].GetPrev(); |
|||
Entry& tail_entry = |
|||
(tail != nullptr) ? tail->GetPriorityQueueEntry(core) : this->root[core]; |
|||
|
|||
/* Link the entries. */ |
|||
member_entry.SetPrev(tail); |
|||
member_entry.SetNext(nullptr); |
|||
tail_entry.SetNext(member); |
|||
this->root[core].SetPrev(member); |
|||
|
|||
return (tail == nullptr); |
|||
} |
|||
|
|||
constexpr bool PushFront(s32 core, Member* member) { |
|||
/* Get the entry associated with the member. */ |
|||
Entry& member_entry = member->GetPriorityQueueEntry(core); |
|||
|
|||
/* Get the entry associated with the front of the queue. */ |
|||
Member* head = this->root[core].GetNext(); |
|||
Entry& head_entry = |
|||
(head != nullptr) ? head->GetPriorityQueueEntry(core) : this->root[core]; |
|||
|
|||
/* Link the entries. */ |
|||
member_entry.SetPrev(nullptr); |
|||
member_entry.SetNext(head); |
|||
head_entry.SetPrev(member); |
|||
this->root[core].SetNext(member); |
|||
|
|||
return (head == nullptr); |
|||
} |
|||
|
|||
constexpr bool Remove(s32 core, Member* member) { |
|||
/* Get the entry associated with the member. */ |
|||
Entry& member_entry = member->GetPriorityQueueEntry(core); |
|||
|
|||
/* Get the entries associated with next and prev. */ |
|||
Member* prev = member_entry.GetPrev(); |
|||
Member* next = member_entry.GetNext(); |
|||
Entry& prev_entry = |
|||
(prev != nullptr) ? prev->GetPriorityQueueEntry(core) : this->root[core]; |
|||
Entry& next_entry = |
|||
(next != nullptr) ? next->GetPriorityQueueEntry(core) : this->root[core]; |
|||
|
|||
/* Unlink. */ |
|||
prev_entry.SetNext(next); |
|||
next_entry.SetPrev(prev); |
|||
|
|||
return (this->GetFront(core) == nullptr); |
|||
} |
|||
|
|||
constexpr Member* GetFront(s32 core) const { |
|||
return this->root[core].GetNext(); |
|||
} |
|||
}; |
|||
|
|||
class KPriorityQueueImpl { |
|||
private: |
|||
KPerCoreQueue queues[NumPriority]; |
|||
Common::BitSet64<NumPriority> available_priorities[NumCores]; |
|||
|
|||
public: |
|||
constexpr KPriorityQueueImpl() : queues(), available_priorities() { /* ... */ |
|||
} |
|||
|
|||
constexpr void PushBack(s32 priority, s32 core, Member* member) { |
|||
ASSERT(IsValidCore(core)); |
|||
ASSERT(IsValidPriority(priority)); |
|||
|
|||
if (priority <= LowestPriority) { |
|||
if (this->queues[priority].PushBack(core, member)) { |
|||
this->available_priorities[core].SetBit(priority); |
|||
} |
|||
} |
|||
} |
|||
|
|||
constexpr void PushFront(s32 priority, s32 core, Member* member) { |
|||
ASSERT(IsValidCore(core)); |
|||
ASSERT(IsValidPriority(priority)); |
|||
|
|||
if (priority <= LowestPriority) { |
|||
if (this->queues[priority].PushFront(core, member)) { |
|||
this->available_priorities[core].SetBit(priority); |
|||
} |
|||
} |
|||
} |
|||
|
|||
constexpr void Remove(s32 priority, s32 core, Member* member) { |
|||
ASSERT(IsValidCore(core)); |
|||
ASSERT(IsValidPriority(priority)); |
|||
|
|||
if (priority <= LowestPriority) { |
|||
if (this->queues[priority].Remove(core, member)) { |
|||
this->available_priorities[core].ClearBit(priority); |
|||
} |
|||
} |
|||
} |
|||
|
|||
constexpr Member* GetFront(s32 core) const { |
|||
ASSERT(IsValidCore(core)); |
|||
|
|||
const s32 priority = |
|||
static_cast<s32>(this->available_priorities[core].CountLeadingZero()); |
|||
if (priority <= LowestPriority) { |
|||
return this->queues[priority].GetFront(core); |
|||
} else { |
|||
return nullptr; |
|||
} |
|||
} |
|||
|
|||
constexpr Member* GetFront(s32 priority, s32 core) const { |
|||
ASSERT(IsValidCore(core)); |
|||
ASSERT(IsValidPriority(priority)); |
|||
|
|||
if (priority <= LowestPriority) { |
|||
return this->queues[priority].GetFront(core); |
|||
} else { |
|||
return nullptr; |
|||
} |
|||
} |
|||
|
|||
constexpr Member* GetNext(s32 core, const Member* member) const { |
|||
ASSERT(IsValidCore(core)); |
|||
|
|||
Member* next = member->GetPriorityQueueEntry(core).GetNext(); |
|||
if (next == nullptr) { |
|||
const s32 priority = static_cast<s32>( |
|||
this->available_priorities[core].GetNextSet(member->GetPriority())); |
|||
if (priority <= LowestPriority) { |
|||
next = this->queues[priority].GetFront(core); |
|||
} |
|||
} |
|||
return next; |
|||
} |
|||
|
|||
constexpr void MoveToFront(s32 priority, s32 core, Member* member) { |
|||
ASSERT(IsValidCore(core)); |
|||
ASSERT(IsValidPriority(priority)); |
|||
|
|||
if (priority <= LowestPriority) { |
|||
this->queues[priority].Remove(core, member); |
|||
this->queues[priority].PushFront(core, member); |
|||
} |
|||
} |
|||
|
|||
constexpr Member* MoveToBack(s32 priority, s32 core, Member* member) { |
|||
ASSERT(IsValidCore(core)); |
|||
ASSERT(IsValidPriority(priority)); |
|||
|
|||
if (priority <= LowestPriority) { |
|||
this->queues[priority].Remove(core, member); |
|||
this->queues[priority].PushBack(core, member); |
|||
return this->queues[priority].GetFront(core); |
|||
} else { |
|||
return nullptr; |
|||
} |
|||
} |
|||
}; |
|||
|
|||
private: |
|||
KPriorityQueueImpl scheduled_queue; |
|||
KPriorityQueueImpl suggested_queue; |
|||
|
|||
private: |
|||
constexpr void ClearAffinityBit(u64& affinity, s32 core) { |
|||
affinity &= ~(u64(1ul) << core); |
|||
} |
|||
|
|||
constexpr s32 GetNextCore(u64& affinity) { |
|||
const s32 core = Common::CountTrailingZeroes64(affinity); |
|||
ClearAffinityBit(affinity, core); |
|||
return core; |
|||
} |
|||
|
|||
constexpr void PushBack(s32 priority, Member* member) { |
|||
ASSERT(IsValidPriority(priority)); |
|||
|
|||
/* Push onto the scheduled queue for its core, if we can. */ |
|||
u64 affinity = member->GetAffinityMask().GetAffinityMask(); |
|||
if (const s32 core = member->GetActiveCore(); core >= 0) { |
|||
this->scheduled_queue.PushBack(priority, core, member); |
|||
ClearAffinityBit(affinity, core); |
|||
} |
|||
|
|||
/* And suggest the thread for all other cores. */ |
|||
while (affinity) { |
|||
this->suggested_queue.PushBack(priority, GetNextCore(affinity), member); |
|||
} |
|||
} |
|||
|
|||
constexpr void PushFront(s32 priority, Member* member) { |
|||
ASSERT(IsValidPriority(priority)); |
|||
|
|||
/* Push onto the scheduled queue for its core, if we can. */ |
|||
u64 affinity = member->GetAffinityMask().GetAffinityMask(); |
|||
if (const s32 core = member->GetActiveCore(); core >= 0) { |
|||
this->scheduled_queue.PushFront(priority, core, member); |
|||
ClearAffinityBit(affinity, core); |
|||
} |
|||
|
|||
/* And suggest the thread for all other cores. */ |
|||
/* Note: Nintendo pushes onto the back of the suggested queue, not the front. */ |
|||
while (affinity) { |
|||
this->suggested_queue.PushBack(priority, GetNextCore(affinity), member); |
|||
} |
|||
} |
|||
|
|||
constexpr void Remove(s32 priority, Member* member) { |
|||
ASSERT(IsValidPriority(priority)); |
|||
|
|||
/* Remove from the scheduled queue for its core. */ |
|||
u64 affinity = member->GetAffinityMask().GetAffinityMask(); |
|||
if (const s32 core = member->GetActiveCore(); core >= 0) { |
|||
this->scheduled_queue.Remove(priority, core, member); |
|||
ClearAffinityBit(affinity, core); |
|||
} |
|||
|
|||
/* Remove from the suggested queue for all other cores. */ |
|||
while (affinity) { |
|||
this->suggested_queue.Remove(priority, GetNextCore(affinity), member); |
|||
} |
|||
} |
|||
|
|||
public: |
|||
constexpr KPriorityQueue() : scheduled_queue(), suggested_queue() { /* ... */ |
|||
} |
|||
|
|||
/* Getters. */ |
|||
constexpr Member* GetScheduledFront(s32 core) const { |
|||
return this->scheduled_queue.GetFront(core); |
|||
} |
|||
|
|||
constexpr Member* GetScheduledFront(s32 core, s32 priority) const { |
|||
return this->scheduled_queue.GetFront(priority, core); |
|||
} |
|||
|
|||
constexpr Member* GetSuggestedFront(s32 core) const { |
|||
return this->suggested_queue.GetFront(core); |
|||
} |
|||
|
|||
constexpr Member* GetSuggestedFront(s32 core, s32 priority) const { |
|||
return this->suggested_queue.GetFront(priority, core); |
|||
} |
|||
|
|||
constexpr Member* GetScheduledNext(s32 core, const Member* member) const { |
|||
return this->scheduled_queue.GetNext(core, member); |
|||
} |
|||
|
|||
constexpr Member* GetSuggestedNext(s32 core, const Member* member) const { |
|||
return this->suggested_queue.GetNext(core, member); |
|||
} |
|||
|
|||
constexpr Member* GetSamePriorityNext(s32 core, const Member* member) const { |
|||
return member->GetPriorityQueueEntry(core).GetNext(); |
|||
} |
|||
|
|||
/* Mutators. */ |
|||
constexpr void PushBack(Member* member) { |
|||
this->PushBack(member->GetPriority(), member); |
|||
} |
|||
|
|||
constexpr void Remove(Member* member) { |
|||
this->Remove(member->GetPriority(), member); |
|||
} |
|||
|
|||
constexpr void MoveToScheduledFront(Member* member) { |
|||
this->scheduled_queue.MoveToFront(member->GetPriority(), member->GetActiveCore(), member); |
|||
} |
|||
|
|||
constexpr Thread* MoveToScheduledBack(Member* member) { |
|||
return this->scheduled_queue.MoveToBack(member->GetPriority(), member->GetActiveCore(), |
|||
member); |
|||
} |
|||
|
|||
/* First class fancy operations. */ |
|||
constexpr void ChangePriority(s32 prev_priority, bool is_running, Member* member) { |
|||
ASSERT(IsValidPriority(prev_priority)); |
|||
|
|||
/* Remove the member from the queues. */ |
|||
const s32 new_priority = member->GetPriority(); |
|||
this->Remove(prev_priority, member); |
|||
|
|||
/* And enqueue. If the member is running, we want to keep it running. */ |
|||
if (is_running) { |
|||
this->PushFront(new_priority, member); |
|||
} else { |
|||
this->PushBack(new_priority, member); |
|||
} |
|||
} |
|||
|
|||
constexpr void ChangeAffinityMask(s32 prev_core, const AffinityMaskType& prev_affinity, |
|||
Member* member) { |
|||
/* Get the new information. */ |
|||
const s32 priority = member->GetPriority(); |
|||
const AffinityMaskType& new_affinity = member->GetAffinityMask(); |
|||
const s32 new_core = member->GetActiveCore(); |
|||
|
|||
/* Remove the member from all queues it was in before. */ |
|||
for (s32 core = 0; core < static_cast<s32>(NumCores); core++) { |
|||
if (prev_affinity.GetAffinity(core)) { |
|||
if (core == prev_core) { |
|||
this->scheduled_queue.Remove(priority, core, member); |
|||
} else { |
|||
this->suggested_queue.Remove(priority, core, member); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* And add the member to all queues it should be in now. */ |
|||
for (s32 core = 0; core < static_cast<s32>(NumCores); core++) { |
|||
if (new_affinity.GetAffinity(core)) { |
|||
if (core == new_core) { |
|||
this->scheduled_queue.PushBack(priority, core, member); |
|||
} else { |
|||
this->suggested_queue.PushBack(priority, core, member); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
constexpr void ChangeCore(s32 prev_core, Member* member, bool to_front = false) { |
|||
/* Get the new information. */ |
|||
const s32 new_core = member->GetActiveCore(); |
|||
const s32 priority = member->GetPriority(); |
|||
|
|||
/* We don't need to do anything if the core is the same. */ |
|||
if (prev_core != new_core) { |
|||
/* Remove from the scheduled queue for the previous core. */ |
|||
if (prev_core >= 0) { |
|||
this->scheduled_queue.Remove(priority, prev_core, member); |
|||
} |
|||
|
|||
/* Remove from the suggested queue and add to the scheduled queue for the new core. */ |
|||
if (new_core >= 0) { |
|||
this->suggested_queue.Remove(priority, new_core, member); |
|||
if (to_front) { |
|||
this->scheduled_queue.PushFront(priority, new_core, member); |
|||
} else { |
|||
this->scheduled_queue.PushBack(priority, new_core, member); |
|||
} |
|||
} |
|||
|
|||
/* Add to the suggested queue for the previous core. */ |
|||
if (prev_core >= 0) { |
|||
this->suggested_queue.PushBack(priority, prev_core, member); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
} // namespace Kernel |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue