diff --git a/CMakeLists.txt b/CMakeLists.txt index 1745edbf3..84facd82b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1215,6 +1215,7 @@ add_library(${CoreLibName} ${CoreLinkType} Core/HLE/HLETables.cpp Core/HLE/HLETables.h Core/HLE/KernelWaitHelpers.h + Core/HLE/ThreadQueueList.h Core/HLE/__sceAudio.cpp Core/HLE/__sceAudio.h Core/HLE/sceAdler.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index a7c8394b8..8573613f0 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -605,6 +605,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 88d02d163..f62a0aaa9 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1164,6 +1164,9 @@ FileLoaders + + HLE + diff --git a/Core/HLE/ThreadQueueList.h b/Core/HLE/ThreadQueueList.h new file mode 100644 index 000000000..9c1b1e5da --- /dev/null +++ b/Core/HLE/ThreadQueueList.h @@ -0,0 +1,278 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include "Core/HLE/sceKernel.h" +#include "Common/ChunkFile.h" + +struct ThreadQueueList { + // Number of queues (number of priority levels starting at 0.) + static const int NUM_QUEUES = 128; + // Initial number of threads a single queue can handle. + static const int INITIAL_CAPACITY = 32; + + struct Queue { + // Next ever-been-used queue (worse priority.) + Queue *next; + // First valid item in data. + int first; + // One after last valid item in data. + int end; + // A too-large array with room on the front and end. + SceUID *data; + // Size of data array. + int capacity; + + inline int size() const { + return end - first; + } + inline bool empty() const { + return first == end; + } + inline int full() const { + return end == capacity; + } + }; + + ThreadQueueList() { + memset(queues, 0, sizeof(queues)); + first = invalid(); + } + + ~ThreadQueueList() { + clear(); + } + + // Only for debugging, returns priority level. + int contains(const SceUID uid) { + for (int i = 0; i < NUM_QUEUES; ++i) { + if (queues[i].data == nullptr) + continue; + + Queue *cur = &queues[i]; + for (int j = cur->first; j < cur->end; ++j) { + if (cur->data[j] == uid) + return i; + } + } + + return -1; + } + + inline SceUID pop_first() { + Queue *cur = first; + while (cur != invalid()) { + if (cur->size() > 0) + return cur->data[cur->first++]; + cur = cur->next; + } + + _dbg_assert_msg_(SCEKERNEL, false, "ThreadQueueList should not be empty."); + return 0; + } + + inline SceUID pop_first_better(u32 priority) { + Queue *cur = first; + // Don't bother looking past (worse than) this priority. + Queue *stop = &queues[priority]; + while (cur < stop) { + if (cur->size() > 0) + return cur->data[cur->first++]; + cur = cur->next; + } + + return 0; + } + + inline void push_front(u32 priority, const SceUID threadID) { + Queue *cur = &queues[priority]; + cur->data[--cur->first] = threadID; + // If we ran out of room toward the front, add more room for next time. + if (cur->first == 0) + rebalance(priority); + } + + inline void push_back(u32 priority, const SceUID threadID) { + Queue *cur = &queues[priority]; + cur->data[cur->end++] = threadID; + if (cur->full()) + rebalance(priority); + } + + inline void remove(u32 priority, const SceUID threadID) { + Queue *cur = &queues[priority]; + _dbg_assert_msg_(SCEKERNEL, cur->next != nullptr, "ThreadQueueList::Queue should already be linked up."); + + for (int i = cur->first; i < cur->end; ++i) { + if (cur->data[i] == threadID) { + // How many more after this one? + int remaining = cur->end - i; + // If there are more, move them into place. + if (remaining > 0) + memmove(&cur->data[i], &cur->data[i + 1], remaining * sizeof(SceUID)); + + // Now we're one shorter. + --cur->end; + return; + } + } + + // Wasn't there. + } + + inline void rotate(u32 priority) { + Queue *cur = &queues[priority]; + _dbg_assert_msg_(SCEKERNEL, cur->next != nullptr, "ThreadQueueList::Queue should already be linked up."); + + if (cur->size() > 1) { + // Grab the front and push it on the end. + cur->data[cur->end++] = cur->data[cur->first++]; + if (cur->full()) + rebalance(priority); + } + } + + inline void clear() { + for (int i = 0; i < NUM_QUEUES; ++i) { + if (queues[i].data != nullptr) + free(queues[i].data); + } + memset(queues, 0, sizeof(queues)); + first = invalid(); + } + + inline bool empty(u32 priority) const { + const Queue *cur = &queues[priority]; + return cur->empty(); + } + + inline void prepare(u32 priority) { + Queue *cur = &queues[priority]; + if (cur->next == nullptr) + link(priority, INITIAL_CAPACITY); + } + + void DoState(PointerWrap &p) { + auto s = p.Section("ThreadQueueList", 1); + if (!s) + return; + + int numQueues = NUM_QUEUES; + p.Do(numQueues); + if (numQueues != NUM_QUEUES) { + p.SetError(p.ERROR_FAILURE); + ERROR_LOG(SCEKERNEL, "Savestate loading error: invalid data"); + return; + } + + if (p.mode == p.MODE_READ) + clear(); + + for (int i = 0; i < NUM_QUEUES; ++i) { + Queue *cur = &queues[i]; + int size = cur->size(); + p.Do(size); + int capacity = cur->capacity; + p.Do(capacity); + + if (capacity == 0) + continue; + + if (p.mode == p.MODE_READ) { + link(i, capacity); + cur->first = (cur->capacity - size) / 2; + cur->end = cur->first + size; + } + + if (size != 0) + p.DoArray(&cur->data[cur->first], size); + } + } + +private: + Queue *invalid() const { + return (Queue *)-1; + } + + // Initialize a priority level and link to other queues. + void link(u32 priority, int size) { + _dbg_assert_msg_(SCEKERNEL, queues[priority].data == nullptr, "ThreadQueueList::Queue should only be initialized once."); + + // Make sure we stay a multiple of INITIAL_CAPACITY. + if (size <= INITIAL_CAPACITY) + size = INITIAL_CAPACITY; + else { + int goal = size; + size = INITIAL_CAPACITY; + while (size < goal) + size *= 2; + } + + // Allocate the queue. + Queue *cur = &queues[priority]; + cur->data = (SceUID *)malloc(sizeof(SceUID) * size); + cur->capacity = size; + // Start smack in the middle so it can move both directions. + cur->first = size / 2; + cur->end = size / 2; + + for (int i = (int)priority - 1; i >= 0; --i) { + // This queue is before ours, and points past us. + // We'll have it point to our new queue, inserting into the chain. + if (queues[i].next != nullptr) { + cur->next = queues[i].next; + queues[i].next = cur; + return; + } + } + + // Never found above - that means there's no better queue yet. + // The new one is now first, and whoever was first is after it. + cur->next = first; + first = cur; + } + + // Move or allocate as necessary to maintain free space on both sides. + void rebalance(u32 priority) { + Queue *cur = &queues[priority]; + int size = cur->size(); + // Basically full. Time for a larger queue? + if (size >= cur->capacity - 2) { + int new_capacity = cur->capacity * 2; + SceUID *new_data = (SceUID *)realloc(cur->data, new_capacity * sizeof(SceUID)); + if (new_data != nullptr) { + // Success, it's bigger now. + cur->capacity = new_capacity; + cur->data = new_data; + } + } + + // If we center all the items, it should start here. + int newFirst = (cur->capacity - size) / 2; + if (newFirst != cur->first) { + memmove(&cur->data[newFirst], &cur->data[cur->first], size * sizeof(SceUID)); + cur->first = newFirst; + cur->end = newFirst + size; + } + } + + // The first queue that's ever been used. + Queue *first; + // The priority level queues of thread ids. + Queue queues[NUM_QUEUES]; +}; diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 098bb729c..4044cb898 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -41,6 +41,7 @@ #include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceKernelInterrupt.h" #include "Core/HLE/KernelWaitHelpers.h" +#include "Core/HLE/ThreadQueueList.h" typedef struct { @@ -600,298 +601,6 @@ public: std::map pausedWaits; }; -struct ThreadQueueList -{ - // Number of queues (number of priority levels starting at 0.) - static const int NUM_QUEUES = 128; - // Initial number of threads a single queue can handle. - static const int INITIAL_CAPACITY = 32; - - struct Queue - { - // Next ever-been-used queue (worse priority.) - Queue *next; - // First valid item in data. - int first; - // One after last valid item in data. - int end; - // A too-large array with room on the front and end. - SceUID *data; - // Size of data array. - int capacity; - - inline int size() const { - return end - first; - } - inline bool empty() const { - return first == end; - } - inline int full() const { - return end == capacity; - } - }; - - ThreadQueueList() - { - memset(queues, 0, sizeof(queues)); - first = invalid(); - } - - ~ThreadQueueList() - { - clear(); - } - - // Only for debugging, returns priority level. - int contains(const SceUID uid) - { - for (int i = 0; i < NUM_QUEUES; ++i) - { - if (queues[i].data == nullptr) - continue; - - Queue *cur = &queues[i]; - for (int j = cur->first; j < cur->end; ++j) - { - if (cur->data[j] == uid) - return i; - } - } - - return -1; - } - - inline SceUID pop_first() - { - Queue *cur = first; - while (cur != invalid()) - { - if (cur->size() > 0) - return cur->data[cur->first++]; - cur = cur->next; - } - - _dbg_assert_msg_(SCEKERNEL, false, "ThreadQueueList should not be empty."); - return 0; - } - - inline SceUID pop_first_better(u32 priority) - { - Queue *cur = first; - // Don't bother looking past (worse than) this priority. - Queue *stop = &queues[priority]; - while (cur < stop) - { - if (cur->size() > 0) - return cur->data[cur->first++]; - cur = cur->next; - } - - return 0; - } - - inline void push_front(u32 priority, const SceUID threadID) - { - Queue *cur = &queues[priority]; - cur->data[--cur->first] = threadID; - // If we ran out of room toward the front, add more room for next time. - if (cur->first == 0) - rebalance(priority); - } - - inline void push_back(u32 priority, const SceUID threadID) - { - Queue *cur = &queues[priority]; - cur->data[cur->end++] = threadID; - if (cur->full()) - rebalance(priority); - } - - inline void remove(u32 priority, const SceUID threadID) - { - Queue *cur = &queues[priority]; - _dbg_assert_msg_(SCEKERNEL, cur->next != nullptr, "ThreadQueueList::Queue should already be linked up."); - - for (int i = cur->first; i < cur->end; ++i) - { - if (cur->data[i] == threadID) - { - // How many more after this one? - int remaining = cur->end - i; - // If there are more, move them into place. - if (remaining > 0) - memmove(&cur->data[i], &cur->data[i + 1], remaining * sizeof(SceUID)); - - // Now we're one shorter. - --cur->end; - return; - } - } - - // Wasn't there. - } - - inline void rotate(u32 priority) - { - Queue *cur = &queues[priority]; - _dbg_assert_msg_(SCEKERNEL, cur->next != nullptr, "ThreadQueueList::Queue should already be linked up."); - - if (cur->size() > 1) - { - // Grab the front and push it on the end. - cur->data[cur->end++] = cur->data[cur->first++]; - if (cur->full()) - rebalance(priority); - } - } - - inline void clear() - { - for (int i = 0; i < NUM_QUEUES; ++i) - { - if (queues[i].data != nullptr) - free(queues[i].data); - } - memset(queues, 0, sizeof(queues)); - first = invalid(); - } - - inline bool empty(u32 priority) const - { - const Queue *cur = &queues[priority]; - return cur->empty(); - } - - inline void prepare(u32 priority) - { - Queue *cur = &queues[priority]; - if (cur->next == nullptr) - link(priority, INITIAL_CAPACITY); - } - - void DoState(PointerWrap &p) - { - auto s = p.Section("ThreadQueueList", 1); - if (!s) - return; - - int numQueues = NUM_QUEUES; - p.Do(numQueues); - if (numQueues != NUM_QUEUES) - { - p.SetError(p.ERROR_FAILURE); - ERROR_LOG(SCEKERNEL, "Savestate loading error: invalid data"); - return; - } - - if (p.mode == p.MODE_READ) - clear(); - - for (int i = 0; i < NUM_QUEUES; ++i) - { - Queue *cur = &queues[i]; - int size = cur->size(); - p.Do(size); - int capacity = cur->capacity; - p.Do(capacity); - - if (capacity == 0) - continue; - - if (p.mode == p.MODE_READ) - { - link(i, capacity); - cur->first = (cur->capacity - size) / 2; - cur->end = cur->first + size; - } - - if (size != 0) - p.DoArray(&cur->data[cur->first], size); - } - } - -private: - Queue *invalid() const - { - return (Queue *) -1; - } - - // Initialize a priority level and link to other queues. - void link(u32 priority, int size) - { - _dbg_assert_msg_(SCEKERNEL, queues[priority].data == nullptr, "ThreadQueueList::Queue should only be initialized once."); - - // Make sure we stay a multiple of INITIAL_CAPACITY. - if (size <= INITIAL_CAPACITY) - size = INITIAL_CAPACITY; - else - { - int goal = size; - size = INITIAL_CAPACITY; - while (size < goal) - size *= 2; - } - - // Allocate the queue. - Queue *cur = &queues[priority]; - cur->data = (SceUID *) malloc(sizeof(SceUID) * size); - cur->capacity = size; - // Start smack in the middle so it can move both directions. - cur->first = size / 2; - cur->end = size / 2; - - for (int i = (int) priority - 1; i >= 0; --i) - { - // This queue is before ours, and points past us. - // We'll have it point to our new queue, inserting into the chain. - if (queues[i].next != nullptr) - { - cur->next = queues[i].next; - queues[i].next = cur; - return; - } - } - - // Never found above - that means there's no better queue yet. - // The new one is now first, and whoever was first is after it. - cur->next = first; - first = cur; - } - - // Move or allocate as necessary to maintain free space on both sides. - void rebalance(u32 priority) - { - Queue *cur = &queues[priority]; - int size = cur->size(); - // Basically full. Time for a larger queue? - if (size >= cur->capacity - 2) - { - int new_capacity = cur->capacity * 2; - SceUID *new_data = (SceUID *)realloc(cur->data, new_capacity * sizeof(SceUID)); - if (new_data != nullptr) - { - // Success, it's bigger now. - cur->capacity = new_capacity; - cur->data = new_data; - } - } - - // If we center all the items, it should start here. - int newFirst = (cur->capacity - size) / 2; - if (newFirst != cur->first) - { - memmove(&cur->data[newFirst], &cur->data[cur->first], size * sizeof(SceUID)); - cur->first = newFirst; - cur->end = newFirst + size; - } - } - - // The first queue that's ever been used. - Queue *first; - // The priority level queues of thread ids. - Queue queues[NUM_QUEUES]; -}; - struct WaitTypeFuncs { WaitBeginCallbackFunc beginFunc;