Address CR Comments (#132) + Change Core Migration API

This addresses all CR comments including more codebase-wide changes arising from certain review comments like proper usage of its/it's and consistent contraction of it is into it's. 

An overhaul was made to the presentation and formatting of `KThread.h` and `LoadBalance` works has been superseded by `GetOptimalCoreForThread` which can be used alongside `InsertThread` or `MigrateToCore`. It makes the API far more atomic and neater. This was a major point of contention for the design prior, it's simplified some code and potentially improved performance.
This commit is contained in:
PixelyIon 2021-03-04 19:00:14 +05:30 committed by ◱ Mark
parent 0ea02f2d56
commit fe5061a8e0
46 changed files with 256 additions and 217 deletions

View File

@ -171,7 +171,7 @@
</inspection_tool>
<inspection_tool class="CheckedExceptionClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClangTidy" enabled="true" level="WARNING" enabled_by_default="true">
<option name="clangTidyChecks" value="-*,bugprone-argument-comment,bugprone-assert-side-effect,bugprone-bad-signal-to-kill-thread,bugprone-branch-clone,bugprone-copy-constructor-init,bugprone-dangling-handle,bugprone-dynamic-static-initializers,bugprone-fold-init-type,bugprone-forward-declaration-namespace,bugprone-forwarding-reference-overload,bugprone-inaccurate-erase,bugprone-incorrect-roundings,bugprone-integer-division,bugprone-lambda-function-name,bugprone-macro-parentheses,bugprone-macro-repeated-side-effects,bugprone-misplaced-operator-in-strlen-in-alloc,bugprone-misplaced-pointer-arithmetic-in-alloc,bugprone-misplaced-widening-cast,bugprone-move-forwarding-reference,bugprone-multiple-statement-macro,bugprone-no-escape,bugprone-not-null-terminated-result,bugprone-parent-virtual-call,bugprone-posix-return,bugprone-reserved-identifier,bugprone-sizeof-container,bugprone-sizeof-expression,bugprone-spuriously-wake-up-functions,bugprone-string-constructor,bugprone-string-integer-assignment,bugprone-string-literal-with-embedded-nul,bugprone-suspicious-enum-usage,bugprone-suspicious-include,bugprone-suspicious-memset-usage,bugprone-suspicious-missing-comma,bugprone-suspicious-semicolon,bugprone-suspicious-string-compare,bugprone-swapped-arguments,bugprone-terminating-continue,bugprone-throw-keyword-missing,bugprone-too-small-loop-variable,bugprone-undefined-memory-manipulation,bugprone-undelegated-constructor,bugprone-unhandled-self-assignment,bugprone-unused-raii,bugprone-unused-return-value,bugprone-use-after-move,bugprone-virtual-near-miss,cert-dcl21-cpp,cert-dcl58-cpp,cert-err34-c,cert-err52-cpp,cert-err58-cpp,cert-err60-cpp,cert-flp30-c,cert-msc50-cpp,cert-msc51-cpp,cppcoreguidelines-interfaces-global-init,cppcoreguidelines-narrowing-conversions,cppcoreguidelines-pro-type-member-init,cppcoreguidelines-pro-type-static-cast-downcast,cppcoreguidelines-slicing,google-default-arguments,google-explicit-constructor,google-runtime-operator,hicpp-exception-baseclass,hicpp-multiway-paths-covered,misc-misplaced-const,misc-new-delete-overloads,misc-no-recursion,misc-non-copyable-objects,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,misc-uniqueptr-reset-release,modernize-avoid-bind,modernize-concat-nested-namespaces,modernize-deprecated-headers,modernize-deprecated-ios-base-aliases,modernize-loop-convert,modernize-make-shared,modernize-make-unique,modernize-pass-by-value,modernize-raw-string-literal,modernize-redundant-void-arg,modernize-replace-auto-ptr,modernize-replace-disallow-copy-and-assign-macro,modernize-replace-random-shuffle,modernize-return-braced-init-list,modernize-shrink-to-fit,modernize-unary-static-assert,modernize-use-auto,modernize-use-bool-literals,modernize-use-emplace,modernize-use-equals-default,modernize-use-equals-delete,modernize-use-nodiscard,modernize-use-noexcept,modernize-use-nullptr,modernize-use-override,modernize-use-transparent-functors,modernize-use-uncaught-exceptions,mpi-buffer-deref,mpi-type-mismatch,openmp-use-default-none,performance-faster-string-find,performance-for-range-copy,performance-implicit-conversion-in-loop,performance-inefficient-algorithm,performance-inefficient-string-concatenation,performance-inefficient-vector-operation,performance-move-const-arg,performance-move-constructor-init,performance-no-automatic-move,performance-noexcept-move-constructor,performance-trivially-destructible,performance-type-promotion-in-math-fn,performance-unnecessary-copy-initialization,performance-unnecessary-value-param,portability-simd-intrinsics,readability-avoid-const-params-in-decls,readability-const-return-type,readability-container-size-empty,readability-convert-member-functions-to-static,readability-delete-null-pointer,readability-deleted-default,readability-inconsistent-declaration-parameter-name,readability-make-member-function-const,readability-misleading-indentation,readability-misplaced-array-index,readability-non-const-parameter,readability-redundant-control-flow,readability-redundant-declaration,readability-redundant-function-ptr-dereference,readability-redundant-smartptr-get,readability-redundant-string-cstr,readability-redundant-string-init,readability-simplify-subscript-expr,readability-static-accessed-through-instance,readability-static-definition-in-anonymous-namespace,readability-string-compare,readability-uniqueptr-delete-release,readability-use-anyofallof" />
<option name="clangTidyChecks" value="-*,bugprone-argument-comment,bugprone-assert-side-effect,bugprone-bad-signal-to-kill-thread,bugprone-branch-clone,bugprone-copy-constructor-init,bugprone-dangling-handle,bugprone-dynamic-static-initializers,bugprone-fold-init-type,bugprone-forward-declaration-namespace,bugprone-forwarding-reference-overload,bugprone-inaccurate-erase,bugprone-incorrect-roundings,bugprone-integer-division,bugprone-lambda-function-name,bugprone-macro-parentheses,bugprone-macro-repeated-side-effects,bugprone-misplaced-operator-in-strlen-in-alloc,bugprone-misplaced-pointer-arithmetic-in-alloc,bugprone-misplaced-widening-cast,bugprone-move-forwarding-reference,bugprone-multiple-statement-macro,bugprone-no-escape,bugprone-not-null-terminated-result,bugprone-parent-virtual-call,bugprone-posix-return,bugprone-reserved-identifier,bugprone-sizeof-container,bugprone-sizeof-expression,bugprone-spuriously-wake-up-functions,bugprone-string-constructor,bugprone-string-integer-assignment,bugprone-string-literal-with-embedded-nul,bugprone-suspicious-enum-usage,bugprone-suspicious-include,bugprone-suspicious-memset-usage,bugprone-suspicious-missing-comma,bugprone-suspicious-semicolon,bugprone-suspicious-string-compare,bugprone-swapped-arguments,bugprone-terminating-continue,bugprone-throw-keyword-missing,bugprone-too-small-loop-variable,bugprone-undefined-memory-manipulation,bugprone-undelegated-constructor,bugprone-unhandled-self-assignment,bugprone-unused-raii,bugprone-unused-return-value,bugprone-use-after-move,bugprone-virtual-near-miss,cert-dcl21-cpp,cert-dcl58-cpp,cert-err34-c,cert-err58-cpp,cert-err60-cpp,cert-flp30-c,cert-msc50-cpp,cert-msc51-cpp,cert-str34-c,cppcoreguidelines-interfaces-global-init,cppcoreguidelines-narrowing-conversions,cppcoreguidelines-pro-type-static-cast-downcast,cppcoreguidelines-slicing,google-default-arguments,google-explicit-constructor,google-runtime-operator,hicpp-exception-baseclass,hicpp-multiway-paths-covered,misc-misplaced-const,misc-new-delete-overloads,misc-no-recursion,misc-non-copyable-objects,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,misc-uniqueptr-reset-release,modernize-avoid-bind,modernize-concat-nested-namespaces,modernize-deprecated-ios-base-aliases,modernize-loop-convert,modernize-make-shared,modernize-make-unique,modernize-pass-by-value,modernize-raw-string-literal,modernize-redundant-void-arg,modernize-replace-auto-ptr,modernize-replace-disallow-copy-and-assign-macro,modernize-replace-random-shuffle,modernize-return-braced-init-list,modernize-shrink-to-fit,modernize-unary-static-assert,modernize-use-auto,modernize-use-bool-literals,modernize-use-emplace,modernize-use-equals-default,modernize-use-equals-delete,modernize-use-nodiscard,modernize-use-noexcept,modernize-use-nullptr,modernize-use-override,modernize-use-transparent-functors,modernize-use-uncaught-exceptions,mpi-buffer-deref,mpi-type-mismatch,openmp-use-default-none,performance-faster-string-find,performance-for-range-copy,performance-implicit-conversion-in-loop,performance-inefficient-algorithm,performance-inefficient-string-concatenation,performance-inefficient-vector-operation,performance-move-const-arg,performance-move-constructor-init,performance-no-automatic-move,performance-noexcept-move-constructor,performance-trivially-destructible,performance-type-promotion-in-math-fn,performance-unnecessary-copy-initialization,performance-unnecessary-value-param,portability-simd-intrinsics,readability-avoid-const-params-in-decls,readability-const-return-type,readability-container-size-empty,readability-convert-member-functions-to-static,readability-delete-null-pointer,readability-deleted-default,readability-inconsistent-declaration-parameter-name,readability-make-member-function-const,readability-misleading-indentation,readability-misplaced-array-index,readability-non-const-parameter,readability-redundant-control-flow,readability-redundant-declaration,readability-redundant-function-ptr-dereference,readability-redundant-smartptr-get,readability-redundant-string-cstr,readability-redundant-string-init,readability-simplify-subscript-expr,readability-static-accessed-through-instance,readability-static-definition-in-anonymous-namespace,readability-string-compare,readability-uniqueptr-delete-release,readability-use-anyofallof" />
</inspection_tool>
<inspection_tool class="ClassComplexity" enabled="true" level="WARNING" enabled_by_default="true">
<option name="m_limit" value="80" />

View File

@ -22,7 +22,7 @@ std::weak_ptr<skyline::gpu::GPU> GpuWeak;
std::weak_ptr<skyline::input::Input> InputWeak;
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(JNIEnv *env, jobject instance, jstring romUriJstring, jint romType, jint romFd, jint preferenceFd, jstring appFilesPathJstring) {
skyline::signal::ScopedStackBlocker stackBlocker;
skyline::signal::ScopedStackBlocker stackBlocker; // We do not want anything to unwind past JNI code as there are invalid stack frames which can lead to a segmentation fault
Fps = FrameTime = 0;
pthread_setname_np(pthread_self(), "EmuMain");

View File

@ -41,7 +41,7 @@ namespace skyline {
void Logger::Write(LogLevel level, const std::string& str) {
constexpr std::array<char, 5> levelCharacter{'E', 'W', 'I', 'D', 'V'}; // The LogLevel as written out to a file
constexpr std::array<int, 5> levelAlog{ANDROID_LOG_ERROR, ANDROID_LOG_WARN, ANDROID_LOG_INFO, ANDROID_LOG_DEBUG, ANDROID_LOG_VERBOSE}; // This corresponds to LogLevel and provides it's equivalent for NDK Logging
constexpr std::array<int, 5> levelAlog{ANDROID_LOG_ERROR, ANDROID_LOG_WARN, ANDROID_LOG_INFO, ANDROID_LOG_DEBUG, ANDROID_LOG_VERBOSE}; // This corresponds to LogLevel and provides its equivalent for NDK Logging
if (logTag.empty())
UpdateTag();

View File

@ -20,6 +20,7 @@
#include <string>
#include <sstream>
#include <memory>
#include <compare>
#include <sys/mman.h>
#include <fmt/format.h>
#include <frozen/unordered_map.h>
@ -68,7 +69,7 @@ namespace skyline {
};
/**
* @note Success is 0, 0 - it is the only error that's not specific to a module
* @note Success is 0, it's the only result that's not specific to a module
*/
Result() = default;
@ -299,7 +300,7 @@ namespace skyline {
constexpr span(const std::span<T, Extent> &spn) : std::span<T, Extent>(spn) {}
/**
* @brief We want to support implicitly casting from std::string_view -> span as it is just a specialization of a data view which span is a generic form of, the opposite doesn't hold true as not all data held by a span is string data therefore the conversion isn't implicit there
* @brief We want to support implicitly casting from std::string_view -> span as it's just a specialization of a data view which span is a generic form of, the opposite doesn't hold true as not all data held by a span is string data therefore the conversion isn't implicit there
*/
template<typename Traits>
constexpr span(const std::basic_string_view<T, Traits> &string) : std::span<T, Extent>(const_cast<T *>(string.data()), string.size()) {}

View File

@ -21,6 +21,8 @@ namespace skyline {
PREF_ELEM("log_level", logLevel, static_cast<Logger::LogLevel>(element.text().as_uint(static_cast<unsigned int>(Logger::LogLevel::Info)))),
};
#undef PREF_ELEM
std::bitset<std::tuple_size_v<typeof(preferences)>> preferencesSet{}; // A bitfield to keep track of all the preferences we've set
for (auto element{document.last_child().first_child()}; element; element = element.next_sibling()) {
std::string_view name{element.attribute("name").value()};

View File

@ -10,10 +10,7 @@ namespace skyline::signal {
thread_local std::exception_ptr SignalExceptionPtr;
void ExceptionThrow() {
// We need the compiler to not remove the asm at the end of 'std::rethrow_exception' which is a noreturn function
volatile bool alwaysTrue{true};
if (alwaysTrue)
std::rethrow_exception(SignalExceptionPtr);
std::rethrow_exception(SignalExceptionPtr);
}
std::terminate_handler terminateHandler{};
@ -41,6 +38,7 @@ namespace skyline::signal {
static void *exceptionThrowEnd{};
if (!exceptionThrowEnd) {
// We need to find the function bounds for ExceptionThrow, if we haven't already
u32 *it{reinterpret_cast<u32 *>(&ExceptionThrow) + 1};
while (_Unwind_FindEnclosingFunction(it) == &ExceptionThrow)
it++;
@ -51,11 +49,13 @@ namespace skyline::signal {
bool hasAdvanced{};
while (lookupFrame && lookupFrame->lr) {
if (lookupFrame->lr >= reinterpret_cast<void *>(&ExceptionThrow) && lookupFrame->lr < exceptionThrowEnd) {
// We need to check if the current stack frame is from ExceptionThrow
// As we need to skip past it (2 frames) and be able to recognize when we're in an infinite loop
if (!hasAdvanced) {
frame = SafeFrameRecurse(2, lookupFrame);
hasAdvanced = true;
} else {
terminateHandler(); // We have no handler to consume the exception, it's time to quit
terminateHandler(); // We presumably have no exception handlers left on the stack to consume the exception, it's time to quit
}
}
lookupFrame = lookupFrame->next;

View File

@ -45,9 +45,9 @@ namespace skyline::signal {
class SignalException {
public:
int signal{};
void* pc{};
void *pc{};
void *fault{};
std::vector<void*> frames; //!< A vector of all stack frame entries prior to the signal occuring
std::vector<void *> frames; //!< A vector of all stack frame entries prior to the signal occuring
inline std::string what() const {
if (!fault)

View File

@ -150,7 +150,7 @@ namespace skyline {
public:
std::vector<u8> backing; //!< The object that holds a host copy of the guest texture (Will be replaced with a vk::Image)
std::shared_ptr<GuestTexture> guest; //!< The guest texture from which this was created, it is required for syncing
std::shared_ptr<GuestTexture> guest; //!< The guest texture from which this was created, it's required for syncing
texture::Dimensions dimensions;
texture::Format format;
texture::Swizzle swizzle;

View File

@ -7,7 +7,7 @@
namespace skyline::input {
/**
* @brief A controller equivalent to a physical one connected to the Switch, it's translation into a Player (NpadDevice) is also encapsulated here
* @brief A controller equivalent to a physical one connected to the Switch, its translation into a Player (NpadDevice) is also encapsulated here
*/
struct GuestController {
NpadControllerType type{};
@ -26,7 +26,7 @@ namespace skyline::input {
friend NpadDevice;
/**
* @brief Translates an NPad's ID into it's index in the array
* @brief Translates an NPad's ID into its index in the array
* @param id The ID of the NPad to translate
* @return The corresponding index of the NPad in the array
*/

View File

@ -65,7 +65,7 @@ namespace skyline::input {
};
/**
* @brief A handle to a specific device addressed by it's ID and type
* @brief A handle to a specific device addressed by its ID and type
* @note This is used by both Six-Axis and Vibration
*/
union __attribute__((__packed__)) NpadDeviceHandle {
@ -130,7 +130,7 @@ namespace skyline::input {
NpadControllerState &GetNextEntry(NpadControllerInfo &info);
/**
* @return The NpadControllerInfo for this controller based on it's type
* @return The NpadControllerInfo for this controller based on its type
*/
NpadControllerInfo &GetControllerInfo();

View File

@ -243,7 +243,7 @@ namespace skyline::input {
NpadControllerInfo leftController; //!< The Left Joy-Con controller data (Only in Single Mode, no input rotation based on rotation)
NpadControllerInfo rightController; //!< The Right Joy-Con controller data (Only in Single Mode, no input rotation based on rotation)
NpadControllerInfo palmaController; //!< The Poké Ball Plus controller data
NpadControllerInfo defaultController; //!< The Default controller data (Inputs are rotated based on orientation and SL/SR are mapped to L/R incase it is a single JC)
NpadControllerInfo defaultController; //!< The Default controller data (Inputs are rotated based on orientation and SL/SR are mapped to L/R incase it's a single JC)
NpadSixAxisInfo fullKeySixAxis; //!< The Pro/GC IMU data
NpadSixAxisInfo handheldSixAxis; //!< The Handheld IMU data

View File

@ -8,7 +8,7 @@
namespace skyline::input {
/*
* @brief A description of a point being touched on the screen
* @note All members are jint as it is treated as a jint array in Kotlin
* @note All members are jint as it's treated as a jint array in Kotlin
* @note This structure corresponds to TouchScreenStateData, see that for details
*/
struct TouchScreenPoint {

View File

@ -40,15 +40,15 @@ namespace skyline {
* @url https://switchbrew.org/wiki/IPC_Marshalling#IPC_Command_Structure
*/
struct CommandHeader {
CommandType type : 16;
u8 xNo : 4;
u8 aNo : 4;
u8 bNo : 4;
u8 wNo : 4;
u32 rawSize : 10;
CommandType type : 16;
u8 xNo : 4;
u8 aNo : 4;
u8 bNo : 4;
u8 wNo : 4;
u32 rawSize : 10;
BufferCFlag cFlag : 4;
u32 : 17;
bool handleDesc : 1;
bool handleDesc : 1;
};
static_assert(sizeof(CommandHeader) == 8);
@ -56,7 +56,7 @@ namespace skyline {
* @url https://switchbrew.org/wiki/IPC_Marshalling#Handle_descriptor
*/
struct HandleDescriptor {
bool sendPid : 1;
bool sendPid : 1;
u32 copyCount : 4;
u32 moveCount : 4;
u32 : 23;
@ -263,7 +263,7 @@ namespace skyline {
std::vector<u8> payload; //!< The contents to be pushed to the data payload
public:
Result errorCode{}; //!< The error code to respond with, it is 0 (Success) by default
Result errorCode{}; //!< The error code to respond with, it's 0 (Success) by default
std::vector<KHandle> copyHandles;
std::vector<KHandle> moveHandles;
std::vector<KHandle> domainObjects;

View File

@ -209,7 +209,7 @@ namespace skyline {
};
/**
* @brief MemoryManager keeps track of guest virtual memory and it's related attributes
* @brief MemoryManager keeps track of guest virtual memory and its related attributes
*/
class MemoryManager {
private:
@ -225,7 +225,7 @@ namespace skyline {
memory::Region stack{};
memory::Region tlsIo{}; //!< TLS/IO
std::shared_mutex mutex; //!< Synchronizes any operations done on the VMM, it is locked in shared mode by readers and exclusive mode by writers
std::shared_mutex mutex; //!< Synchronizes any operations done on the VMM, it's locked in shared mode by readers and exclusive mode by writers
MemoryManager(const DeviceState &state);

View File

@ -22,8 +22,7 @@ namespace skyline::kernel {
}
}
Scheduler::CoreContext &Scheduler::LoadBalance(const std::shared_ptr<type::KThread> &thread, bool alwaysInsert) {
std::lock_guard migrationLock(thread->coreMigrationMutex);
Scheduler::CoreContext &Scheduler::GetOptimalCoreForThread(const std::shared_ptr<type::KThread> &thread) {
auto *currentCore{&cores.at(thread->coreId)};
if (!currentCore->queue.empty() && thread->affinityMask.count() != 1) {
@ -36,12 +35,19 @@ namespace skyline::kernel {
u64 timeslice{};
if (!candidateCore.queue.empty()) {
std::unique_lock coreLock(candidateCore.mutex);
std::lock_guard coreLock(candidateCore.mutex);
auto threadIterator{candidateCore.queue.cbegin()};
if (threadIterator != candidateCore.queue.cend()) {
const auto &runningThread{*threadIterator};
timeslice += runningThread->averageTimeslice ? std::min(runningThread->averageTimeslice - (util::GetTimeTicks() - runningThread->timesliceStart), 1UL) : runningThread->timesliceStart ? util::GetTimeTicks() - runningThread->timesliceStart : 1UL;
timeslice += [&]() {
if (runningThread->averageTimeslice)
return std::min(runningThread->averageTimeslice - (util::GetTimeTicks() - runningThread->timesliceStart), 1UL);
else if (runningThread->timesliceStart)
return util::GetTimeTicks() - runningThread->timesliceStart;
else
return 1UL;
}();
while (++threadIterator != candidateCore.queue.cend()) {
const auto &residentThread{*threadIterator};
@ -59,25 +65,36 @@ namespace skyline::kernel {
}
if (optimalCore != currentCore) {
if (!alwaysInsert && thread == state.thread)
RemoveThread();
else if (!alwaysInsert && thread != state.thread)
[[unlikely]]
throw exception("Migrating an external thread (T{}) without 'alwaysInsert' isn't supported", thread->id);
/*
bool isInserted;
if (conditionalInsert) {
auto it{std::find(currentCore->queue.begin(), currentCore->queue.end(), thread)};
isInserted = it != currentCore->queue.end();
if (isInserted && thread == state.thread) {
// If the thread is in it's current core's queue
// We need to remove the thread from the current core's queue
it = currentCore->queue.erase(it);
if (it == currentCore->queue.begin() && it != currentCore->queue.end())
(*it)->scheduleCondition.notify_one();
} else if (isInserted && thread != state.thread) [[unlikely]] {
// It's not very useful to insert an external thread on the core optimal for it
// We leave any sort of load balancing to a thread to do on it's own
// Our systems can support it but it's pointless to do and would waste precious CPU cycles
throw exception("Migrating an external thread (T{}) which is potentially inserted in its resident core's queue isn't supported", thread->id);
}
} else {
isInserted = false;
}
thread->coreId = optimalCore->id;
InsertThread(thread);
*/
state.logger->Debug("Load Balancing T{}: C{} -> C{}", thread->id, currentCore->id, optimalCore->id);
} else {
if (alwaysInsert)
InsertThread(thread);
state.logger->Debug("Load Balancing T{}: C{} (Late)", thread->id, currentCore->id);
}
return *optimalCore;
}
if (alwaysInsert)
InsertThread(thread);
state.logger->Debug("Load Balancing T{}: C{} (Early)", thread->id, currentCore->id);
return *currentCore;
@ -114,31 +131,31 @@ namespace skyline::kernel {
core.queue.push_front(thread);
}
if (thread != state.thread)
thread->wakeCondition.notify_one(); // We only want to trigger the conditional variable if the current thread isn't inserting itself
thread->scheduleCondition.notify_one(); // We only want to trigger the conditional variable if the current thread isn't inserting itself
} else {
core.queue.insert(nextThread, thread);
}
}
void Scheduler::MigrateToIdealCore(const std::shared_ptr<type::KThread> &thread, CoreContext *&core, std::unique_lock<std::mutex> &lock) {
// We need to check if the thread was in it's resident core's queue
void Scheduler::MigrateToCore(const std::shared_ptr<type::KThread> &thread, CoreContext *&currentCore, CoreContext *targetCore, std::unique_lock<std::mutex> &lock) {
// We need to check if the thread was in its resident core's queue
// If it was, we need to remove it from the queue
auto it{std::find(core->queue.begin(), core->queue.end(), thread)};
bool wasInserted{it != core->queue.end()};
auto it{std::find(currentCore->queue.begin(), currentCore->queue.end(), thread)};
bool wasInserted{it != currentCore->queue.end()};
if (wasInserted) {
it = core->queue.erase(it);
if (it == core->queue.begin() && it != core->queue.end())
(*it)->wakeCondition.notify_one();
it = currentCore->queue.erase(it);
if (it == currentCore->queue.begin() && it != currentCore->queue.end())
(*it)->scheduleCondition.notify_one();
}
lock.unlock();
thread->coreId = thread->idealCore;
thread->coreId = targetCore->id;
if (wasInserted)
// We need to add the thread to the ideal core queue, if it was previously it's resident core's queue
// We need to add the thread to the ideal core queue, if it was previously its resident core's queue
InsertThread(thread);
core = &cores.at(thread->coreId);
lock = std::unique_lock(core->mutex);
currentCore = targetCore;
lock = std::unique_lock(targetCore->mutex);
}
void Scheduler::WaitSchedule(bool loadBalance) {
@ -148,30 +165,30 @@ namespace skyline::kernel {
auto wakeFunction{[&]() {
if (!thread->affinityMask.test(thread->coreId)) [[unlikely]] {
MigrateToIdealCore(thread, core, lock);
std::lock_guard migrationLock(thread->coreMigrationMutex);
MigrateToCore(thread, core, &cores.at(thread->idealCore), lock);
}
return !core->queue.empty() && core->queue.front() == thread;
}};
if (loadBalance && thread->affinityMask.count() > 1) {
std::chrono::milliseconds loadBalanceThreshold{PreemptiveTimeslice * 2}; //!< The amount of time that needs to pass unscheduled for a thread to attempt load balancing
while (!thread->wakeCondition.wait_for(lock, loadBalanceThreshold, wakeFunction)) {
lock.unlock();
LoadBalance(state.thread);
if (thread->coreId == core->id) {
lock.lock();
} else {
core = &cores.at(thread->coreId);
lock = std::unique_lock(core->mutex);
}
while (!thread->scheduleCondition.wait_for(lock, loadBalanceThreshold, wakeFunction)) {
lock.unlock(); // We cannot call GetOptimalCoreForThread without relinquishing the core mutex
std::lock_guard migrationLock(thread->coreMigrationMutex);
auto newCore{&GetOptimalCoreForThread(state.thread)};
lock.lock();
if (core != newCore)
MigrateToCore(thread, core, newCore, lock);
loadBalanceThreshold *= 2; // We double the duration required for future load balancing for this invocation to minimize pointless load balancing
}
} else {
thread->wakeCondition.wait(lock, wakeFunction);
thread->scheduleCondition.wait(lock, wakeFunction);
}
if (thread->priority == core->preemptionPriority) {
// If the thread needs to be preempted then arm its preemption timer
struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}};
timer_settime(thread->preemptionTimer, 0, &spec, nullptr);
thread->isPreempted = true;
@ -185,9 +202,10 @@ namespace skyline::kernel {
auto *core{&cores.at(thread->coreId)};
std::unique_lock lock(core->mutex);
if (thread->wakeCondition.wait_for(lock, timeout, [&]() {
if (thread->scheduleCondition.wait_for(lock, timeout, [&]() {
if (!thread->affinityMask.test(thread->coreId)) [[unlikely]] {
MigrateToIdealCore(thread, core, lock);
std::lock_guard migrationLock(thread->coreMigrationMutex);
MigrateToCore(thread, core, &cores.at(thread->idealCore), lock);
}
return !core->queue.empty() && core->queue.front() == thread;
})) {
@ -212,13 +230,13 @@ namespace skyline::kernel {
std::unique_lock lock(core.mutex);
if (core.queue.front() == thread) {
// If this thread is at the front of the thread queue then we need to rotate the thread
// In the case where this thread was forcefully yielded, we don't need to do this as it's done by the thread which yielded us
// Splice the linked element from the beginning of the queue to where it's priority is present
// In the case where this thread was forcefully yielded, we don't need to do this as it's done by the thread which yielded to this thread
// Splice the linked element from the beginning of the queue to where its priority is present
core.queue.splice(std::upper_bound(core.queue.begin(), core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority), core.queue, core.queue.begin());
auto &front{core.queue.front()};
if (front != thread)
front->wakeCondition.notify_one(); // If we aren't at the front of the queue, only then should we wake the thread at the front up
front->scheduleCondition.notify_one(); // If we aren't at the front of the queue, only then should we wake the thread at the front up
} else if (!thread->forceYield) [[unlikely]] {
throw exception("T{} called Rotate while not being in C{}'s queue", thread->id, thread->coreId);
}
@ -250,7 +268,7 @@ namespace skyline::kernel {
thread->averageTimeslice = (thread->averageTimeslice / 4) + (3 * (util::GetTimeTicks() - thread->timesliceStart / 4));
if (it != core.queue.end())
(*it)->wakeCondition.notify_one(); // We need to wake the thread at the front of the queue, if we were at the front previously
(*it)->scheduleCondition.notify_one(); // We need to wake the thread at the front of the queue, if we were at the front previously
}
}
}
@ -274,7 +292,7 @@ namespace skyline::kernel {
// If the thread isn't in the queue then the new priority will be handled automatically on insertion
return;
if (currentIt == core->queue.begin()) {
// Alternatively, if it's currently running then we'd just want to cause it to yield if it's priority is lower than the the thread behind it
// Alternatively, if it's currently running then we'd just want to cause it to yield if its priority is lower than the the thread behind it
auto nextIt{std::next(currentIt)};
if (nextIt != core->queue.end() && (*nextIt)->priority < thread->priority) {
if (!thread->pendingYield) {
@ -282,7 +300,7 @@ namespace skyline::kernel {
thread->pendingYield = true;
}
} else if (!thread->isPreempted && thread->priority == core->preemptionPriority) {
// If the thread needs to be preempted due to the new priority then arm it's preemption timer
// If the thread needs to be preempted due to the new priority then arm its preemption timer
struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}};
timer_settime(thread->preemptionTimer, 0, &spec, nullptr);
thread->isPreempted = true;
@ -318,11 +336,11 @@ namespace skyline::kernel {
void Scheduler::UpdateCore(const std::shared_ptr<type::KThread> &thread) {
auto *core{&cores.at(thread->coreId)};
std::unique_lock coreLock(core->mutex);
std::lock_guard coreLock(core->mutex);
if (core->queue.front() == thread)
thread->SendSignal(YieldSignal);
else
thread->wakeCondition.notify_one();
thread->scheduleCondition.notify_one();
}
void Scheduler::ParkThread() {
@ -339,7 +357,7 @@ namespace skyline::kernel {
if (thread->coreId == constant::ParkedCoreId) {
std::unique_lock lock(parkedMutex);
parkedQueue.insert(std::upper_bound(parkedQueue.begin(), parkedQueue.end(), thread->priority.load(), type::KThread::IsHigherPriority), thread);
thread->wakeCondition.wait(lock, [&]() { return parkedQueue.front() == thread && thread->coreId != constant::ParkedCoreId; });
thread->scheduleCondition.wait(lock, [&]() { return parkedQueue.front() == thread && thread->coreId != constant::ParkedCoreId; });
}
InsertThread(thread);
@ -355,12 +373,12 @@ namespace skyline::kernel {
nextThread = nextThread->priority == thread->priority ? nextThread : nullptr; // If the next thread doesn't have the same priority then it won't be scheduled next
auto parkedThread{parkedQueue.front()};
// We need to be conservative about waking up a parked thread, it should only be done if it's priority is higher than the current thread
// Alternatively, it should be done if it's priority is equivalent to the current thread's priority but the next thread had been scheduled prior or if there is no next thread (Current thread would be rescheduled)
// We need to be conservative about waking up a parked thread, it should only be done if its priority is higher than the current thread
// Alternatively, it should be done if its priority is equivalent to the current thread's priority but the next thread had been scheduled prior or if there is no next thread (Current thread would be rescheduled)
if (parkedThread->priority < thread->priority || (parkedThread->priority == thread->priority && (!nextThread || parkedThread->timesliceStart < nextThread->timesliceStart))) {
parkedThread->coreId = thread->coreId;
parkedLock.unlock();
parkedThread->wakeCondition.notify_one();
parkedThread->scheduleCondition.notify_one();
}
}
}

View File

@ -35,7 +35,7 @@ namespace skyline {
}
};
/*
/**
* @brief The Scheduler is responsible for determining which threads should run on which virtual cores and when they should be scheduled
* @note We tend to stray a lot from HOS in our scheduler design as we've designed it around our 1 host thread per guest thread which leads to scheduling from the perspective of threads while the HOS scheduler deals with scheduling from the perspective of cores, not doing this would lead to missing out on key optimizations and serialization of scheduling
*/
@ -58,10 +58,11 @@ namespace skyline {
std::list<std::shared_ptr<type::KThread>> parkedQueue; //!< A queue of threads which are parked and waiting on core migration
/**
* @brief Migrate a thread from it's resident core to it's ideal core
* @note This is used to handle non-cooperative core affinity mask changes where the resident core is not in it's new affinity mask
* @brief Migrate a thread from its resident core to its ideal core
* @note 'KThread::coreMigrationMutex' **must** be locked by the calling thread prior to calling this
* @note This is used to handle non-cooperative core affinity mask changes where the resident core is not in its new affinity mask
*/
void MigrateToIdealCore(const std::shared_ptr<type::KThread>& thread, CoreContext*& core, std::unique_lock<std::mutex>& lock);
void MigrateToCore(const std::shared_ptr<type::KThread> &thread, CoreContext *&currentCore, CoreContext* targetCore, std::unique_lock<std::mutex> &lock);
public:
static constexpr std::chrono::milliseconds PreemptiveTimeslice{10}; //!< The duration of time a preemptive thread can run before yielding
@ -76,79 +77,78 @@ namespace skyline {
static void SignalHandler(int signal, siginfo *info, ucontext *ctx, void **tls);
/**
* @brief Checks all cores and migrates the specified thread to the core where the calling thread should be scheduled the earliest
* @param alwaysInsert If to insert the thread even if it hasn't migrated cores, this is used during thread creation
* @return A reference to the CoreContext of the core which the calling thread is running on after load balancing
* @note This inserts the thread into the migrated process's queue after load balancing, there is no need to call it redundantly
* @note alwaysInsert makes the assumption that the thread isn't inserted in any core's queue currently
* @brief Checks all cores and determines the core where the supplied thread should be scheduled the earliest
* @note 'KThread::coreMigrationMutex' **must** be locked by the calling thread prior to calling this
* @note No core mutexes should be held by the calling thread, that will cause a recursive lock and lead to a deadlock
* @return A reference to the CoreContext of the optimal core
*/
CoreContext& LoadBalance(const std::shared_ptr<type::KThread> &thread, bool alwaysInsert = false);
CoreContext &GetOptimalCoreForThread(const std::shared_ptr<type::KThread> &thread);
/**
* @brief Inserts the specified thread into the scheduler queue at the appropriate location based on it's priority
* @brief Inserts the specified thread into the scheduler queue at the appropriate location based on its priority
*/
void InsertThread(const std::shared_ptr<type::KThread>& thread);
void InsertThread(const std::shared_ptr<type::KThread> &thread);
/**
* @brief Wait for the current thread to be scheduled on it's resident core
* @brief Wait for the calling thread to be scheduled on its resident core
* @param loadBalance If the thread is appropriate for load balancing then if to load balance it occassionally or not
* @note There is an assumption of the thread being on it's resident core queue, if it's not this'll never return
* @note There is an assumption of the thread being on its resident core queue, if it's not this'll never return
*/
void WaitSchedule(bool loadBalance = true);
/**
* @brief Wait for the current thread to be scheduled on it's resident core or for the timeout to expire
* @brief Wait for the calling thread to be scheduled on its resident core or for the timeout to expire
* @return If the thread has been scheduled (true) or if the timer expired before it could be (false)
* @note This will never load balance as it uses the timeout itself as a result this shouldn't be used as a replacement for regular waits
*/
bool TimedWaitSchedule(std::chrono::nanoseconds timeout);
/**
* @brief Rotates the calling thread's resident core queue, if it is at the front of it
* @brief Rotates the calling thread's resident core queue, if it's at the front of it
* @param cooperative If this was triggered by a cooperative yield as opposed to a preemptive one
*/
void Rotate(bool cooperative = true);
/**
* @brief Removes the calling thread from it's resident core queue
* @brief Removes the calling thread from its resident core queue
*/
void RemoveThread();
/**
* @brief Updates the placement of the supplied thread in it's resident core's queue according to it's new priority
* @brief Updates the placement of the supplied thread in its resident core's queue according to its new priority
*/
void UpdatePriority(const std::shared_ptr<type::KThread>& thread);
void UpdatePriority(const std::shared_ptr<type::KThread> &thread);
/**
* @brief Updates the core that the supplied thread is resident to according to it's new affinity mask and ideal core
* @brief Updates the core that the supplied thread is resident to according to its new affinity mask and ideal core
* @note This supports changing the core of a thread which is currently running
*/
void UpdateCore(const std::shared_ptr<type::KThread>& thread);
void UpdateCore(const std::shared_ptr<type::KThread> &thread);
/**
* @brief Parks the calling thread after removing it from it's resident core's queue and inserts it on the core it's been awoken on
* @brief Parks the calling thread after removing it from its resident core's queue and inserts it on the core it's been awoken on
* @note This will not handle waiting for the thread to be scheduled, this should be followed with a call to WaitSchedule/TimedWaitSchedule
*/
void ParkThread();
/**
* @brief Wakes a single parked thread which may be appropriate for running next on this core
* @note We will only wake a thread if it is determined to be a better pick than the thread which would be run on this core next
* @note We will only wake a thread if it's determined to be a better pick than the thread which would be run on this core next
*/
void WakeParkedThread();
};
/**
* @brief A lock which removes the calling thread from it's resident core's scheduler queue and adds it back when being destroyed
* @note It also blocks till the thread has been rescheduled in it's destructor, this behavior might not be preferable in some cases
* @note This is not an analogue to KScopedSchedulerLock on HOS, it is for handling thread state changes which we handle with Scheduler::YieldPending
* @brief A lock which removes the calling thread from its resident core's scheduler queue and adds it back when being destroyed
* @note It also blocks till the thread has been rescheduled in its destructor, this behavior might not be preferable in some cases
* @note This is not an analogue to KScopedSchedulerLock on HOS, it's for handling thread state changes which we handle with Scheduler::YieldPending
*/
struct SchedulerScopedLock {
private:
const DeviceState& state;
const DeviceState &state;
public:
inline SchedulerScopedLock(const DeviceState& state) : state(state) {
inline SchedulerScopedLock(const DeviceState &state) : state(state) {
state.scheduler->RemoveThread();
}

View File

@ -352,7 +352,7 @@ namespace skyline::kernel::svc {
thread->basePriority = priority;
u8 newPriority{};
do {
// Try to CAS the priority of the thread with it's new base priority
// Try to CAS the priority of the thread with its new base priority
// If the new priority is equivalent to the current priority then we don't need to CAS
newPriority = thread->priority.load();
newPriority = std::min(newPriority, priority);
@ -396,7 +396,7 @@ namespace skyline::kernel::svc {
} else if (idealCore == IdealCoreNoUpdate) {
idealCore = thread->idealCore;
} else if (idealCore == IdealCoreDontCare) {
idealCore = __builtin_ctzll(affinityMask.to_ullong()); // The first enabled core in the affinity mask
idealCore = std::countr_zero(affinityMask.to_ullong()); // The first enabled core in the affinity mask
}
auto processMask{state.process->npdm.threadInfo.coreMask};
@ -1151,6 +1151,6 @@ namespace skyline::kernel::svc {
else if (result == result::InvalidState)
state.logger->Debug("svcSignalToAddress: The value at 0x{:X} did not satisfy the mutation condition", address);
state.ctx->gpr.w0 = Result{};
state.ctx->gpr.w0 = result;
}
}

View File

@ -97,7 +97,7 @@ namespace skyline::kernel::svc {
void GetCurrentProcessorNumber(const DeviceState &state);
/**
* @brief Clears a KEvent of it's signal
* @brief Resets a KEvent to its unsignalled state
* @url https://switchbrew.org/wiki/SVC#ClearEvent
*/
void ClearEvent(const DeviceState &state);

View File

@ -100,7 +100,7 @@ namespace skyline::kernel::type {
return std::nullopt;
}
constexpr u32 HandleWaitersBit{0b01000000000000000000000000000000}; //!< A bit which denotes if a mutex psuedo-handle has waiters or not
constexpr u32 HandleWaitersBit{1UL << 30}; //!< A bit which denotes if a mutex psuedo-handle has waiters or not
Result KProcess::MutexLock(u32 *mutex, KHandle ownerHandle, KHandle tag) {
std::shared_ptr<KThread> owner;
@ -126,7 +126,7 @@ namespace skyline::kernel::type {
isHighestPriority = waiters.insert(std::upper_bound(waiters.begin(), waiters.end(), state.thread->priority.load(), KThread::IsHigherPriority), state.thread) == waiters.begin();
state.scheduler->RemoveThread();
std::atomic_store(&state.thread->waitThread, owner);
state.thread->waitThread = owner;
state.thread->waitKey = mutex;
state.thread->waitTag = tag;
}
@ -143,15 +143,10 @@ namespace skyline::kernel::type {
} while (ownerPriority != priority && owner->priority.compare_exchange_strong(ownerPriority, priority));
if (ownerPriority != priority) {
auto waitThread{std::atomic_load(&owner->waitThread)};
while (waitThread) {
std::shared_ptr<KThread> waitThread;
{
std::lock_guard lock(waitThread->waiterMutex);
auto currentWaitThread{std::atomic_load(&owner->waitThread)};
if (waitThread != currentWaitThread) {
waitThread = currentWaitThread;
continue;
}
waitThread = owner->waitThread;
// We need to update the location of the owner thread in the waiter queue of the thread it's waiting on
auto &waiters{waitThread->waiters};
@ -181,7 +176,7 @@ namespace skyline::kernel::type {
if (nextOwnerIt != waiters.end()) {
auto nextOwner{*nextOwnerIt};
std::lock_guard nextLock(nextOwner->waiterMutex);
std::atomic_store(&nextOwner->waitThread, std::shared_ptr<KThread>{nullptr});
nextOwner->waitThread = std::shared_ptr<KThread>{nullptr};
nextOwner->waitKey = nullptr;
// Move all threads waiting on this key to the next owner's waiter list
@ -190,7 +185,7 @@ namespace skyline::kernel::type {
auto thread{*it};
if (thread->waitKey == mutex) {
nextOwner->waiters.splice(std::upper_bound(nextOwner->waiters.begin(), nextOwner->waiters.end(), (*it)->priority.load(), KThread::IsHigherPriority), waiters, it);
std::atomic_store(&thread->waitThread, nextOwner);
thread->waitThread = nextOwner;
if (!nextWaiter)
nextWaiter = thread;
}
@ -216,7 +211,7 @@ namespace skyline::kernel::type {
}
if (nextWaiter) {
// If there is a waiter on the new owner then try to inherit it's priority
// If there is a waiter on the new owner then try to inherit its priority
u8 priority, ownerPriority;
do {
ownerPriority = nextOwner->priority.load();
@ -279,8 +274,8 @@ namespace skyline::kernel::type {
auto queue{syncWaiters.equal_range(key)};
auto it{queue.first};
for (i32 waiterCount{amount}; it != queue.second && (amount <= 0 || waiterCount); it = syncWaiters.erase(it), waiterCount--)
state.scheduler->InsertThread(it->second);
for (i32 waiterCount{amount}; it != queue.second && (amount <= 0 || waiterCount); it = syncWaiters.erase(it), waiterCount--)
state.scheduler->InsertThread(it->second);
if (it == queue.second)
__atomic_store_n(key, false, __ATOMIC_SEQ_CST); // We need to update the boolean flag denoting that there are no more threads waiting on this conditional variable
@ -299,14 +294,15 @@ namespace skyline::kernel::type {
}
if (timeout > 0 && !state.scheduler->TimedWaitSchedule(std::chrono::nanoseconds(timeout))) {
std::unique_lock lock(syncWaiterMutex);
auto queue{syncWaiters.equal_range(address)};
auto iterator{std::find(queue.first, queue.second, SyncWaiters::value_type{address, state.thread})};
if (iterator != queue.second)
if (syncWaiters.erase(iterator) == queue.second)
__atomic_store_n(address, false, __ATOMIC_SEQ_CST);
{
std::lock_guard lock(syncWaiterMutex);
auto queue{syncWaiters.equal_range(address)};
auto iterator{std::find(queue.first, queue.second, SyncWaiters::value_type{address, state.thread})};
if (iterator != queue.second)
if (syncWaiters.erase(iterator) == queue.second)
__atomic_store_n(address, false, __ATOMIC_SEQ_CST);
}
lock.unlock();
state.scheduler->InsertThread(state.thread);
state.scheduler->WaitSchedule();

View File

@ -26,7 +26,7 @@ namespace skyline {
private:
std::mutex threadMutex; //!< Synchronizes thread creation to prevent a race between thread creation and thread killing
bool disableThreadCreation{}; //!< If to disable thread creation, we use this to prevent thread creation after all threads have been killed
bool disableThreadCreation{}; //!< Whether to disable thread creation, we use this to prevent thread creation after all threads have been killed
std::vector<std::shared_ptr<KThread>> threads;
using SyncWaiters = std::multimap<void *, std::shared_ptr<KThread>>;
@ -47,13 +47,13 @@ namespace skyline {
/**
* @return A non-null pointer to a TLS page slot on success, a nullptr will be returned if this page is full
* @note This function is not thread-safe and should be called by exclusively one thread at a time
* @note This function is not thread-safe and should be called exclusively by one thread at a time
*/
u8 *ReserveSlot();
};
public:
u8* tlsExceptionContext{}; //!< A pointer to the TLS Exception Handling Context slot
u8 *tlsExceptionContext{}; //!< A pointer to the TLS exception handling context slot
std::mutex tlsMutex; //!< A mutex to synchronize allocation of TLS pages to prevent extra pages from being created
std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< All TLS pages allocated by this process
std::shared_ptr<KPrivateMemory> mainThreadStack; //!< The stack memory of the main thread stack is owned by the KProcess itself
@ -72,8 +72,8 @@ namespace skyline {
/**
* @brief Kill the main thread/all running threads in the process in a graceful manner
* @param join Return after the main thread has joined rather than instantly
* @param all If to kill all running threads or just the main thread
* @param disableCreation If to disable further thread creation
* @param all Whether to kill all running threads or just the main thread
* @param disableCreation Whether to disable further thread creation
* @note If there are no threads then this will silently return
* @note The main thread should eventually kill the rest of the threads itself
*/
@ -226,12 +226,12 @@ namespace skyline {
/**
* @brief Waits on the supplied address with the specified arbitration function
*/
Result WaitForAddress(u32 *address, u32 value, i64 timeout, bool(*arbitrationFunction)(u32* address, u32 value));
Result WaitForAddress(u32 *address, u32 value, i64 timeout, bool(*arbitrationFunction)(u32 *address, u32 value));
/**
* @brief Signals a variable amount of waiters at the supplied address
*/
Result SignalToAddress(u32 *address, u32 value, i32 amount, bool(*mutateFunction)(u32* address, u32 value, u32 waiterCount) = nullptr);
Result SignalToAddress(u32 *address, u32 value, i32 amount, bool(*mutateFunction)(u32 *address, u32 value, u32 waiterCount) = nullptr);
};
}
}

View File

@ -8,7 +8,7 @@ namespace skyline::kernel::type {
void KSyncObject::Signal() {
std::lock_guard lock(syncObjectMutex);
signalled = true;
for (auto& waiter : syncObjectWaiters) {
for (auto &waiter : syncObjectWaiters) {
if (waiter->isCancellable) {
waiter->isCancellable = false;
waiter->wakeObject = this;
@ -16,4 +16,13 @@ namespace skyline::kernel::type {
}
}
}
bool KSyncObject::ResetSignal() {
std::lock_guard lock(syncObjectMutex);
if (signalled) [[likely]] {
signalled = false;
return true;
}
return false;
}
}

View File

@ -30,14 +30,7 @@ namespace skyline::kernel::type {
* @brief Resets the object to an unsignalled state
* @return If the signal was reset or not
*/
inline bool ResetSignal() {
std::lock_guard lock(syncObjectMutex);
if (signalled) {
signalled = false;
return true;
}
return false;
}
bool ResetSignal();
virtual ~KSyncObject() = default;
};

View File

@ -39,7 +39,7 @@ namespace skyline::kernel::type {
state.scheduler->RemoveThread();
{
std::unique_lock lock(statusMutex);
std::lock_guard lock(statusMutex);
running = false;
ready = false;
statusCondition.notify_all();
@ -89,7 +89,7 @@ namespace skyline::kernel::type {
"MOV X0, SP\n\t"
"STR X0, [%x0, #0x2A8]\n\t" // Write ThreadContext::hostSp
"MOV SP, %x1\n\t" // Replace SP with guest stack
"MOV LR, %x2\n\t" // Store entry in Link Register so it is jumped to on return
"MOV LR, %x2\n\t" // Store entry in Link Register so it's jumped to on return
"MOV X0, %x3\n\t" // Store the argument in X0
"MOV X1, %x4\n\t" // Store the thread handle in X1, NCA applications require this
"MOV X2, XZR\n\t" // Zero out other GP and SIMD registers, not doing this will break applications
@ -185,7 +185,12 @@ namespace skyline::kernel::type {
void KThread::Start(bool self) {
std::unique_lock lock(statusMutex);
if (!running) {
state.scheduler->LoadBalance(shared_from_this(), true); // This will automatically insert the thread into the core queue after load balancing
{
std::lock_guard migrationLock(coreMigrationMutex);
auto thisShared{shared_from_this()};
coreId = state.scheduler->GetOptimalCoreForThread(thisShared).id;
state.scheduler->InsertThread(thisShared);
}
running = true;
killed = false;

View File

@ -22,14 +22,18 @@ namespace skyline {
std::thread thread; //!< If this KThread is backed by a host thread then this'll hold it
pthread_t pthread{}; //!< The pthread_t for the host thread running this guest thread
/**
* @brief Entry function any guest threads, sets up necessary context and jumps into guest code from the calling thread
* @note This function also serves as the entry point for host threads created in StartThread
*/
void StartThread();
public:
std::mutex statusMutex; //!< Synchronizes all thread state changes, running/ready
std::condition_variable statusCondition; //!< A conditional variable signalled on the status of the thread changing
std::mutex statusMutex; //!< Synchronizes all thread state changes (running/ready/killed)
std::condition_variable statusCondition; //!< Signalled on the status of the thread changing
bool running{false}; //!< If the host thread that corresponds to this thread is running, this doesn't reflect guest scheduling changes
bool killed{false}; //!< If this thread was previously running and has been killed
bool ready{false}; //!< If this thread is ready to recieve signals or not
bool killed{false}; //!< If this thread was previously running and has been killed
KHandle handle;
size_t id; //!< Index of thread in parent process's KThread vector
@ -37,31 +41,36 @@ namespace skyline {
nce::ThreadContext ctx{}; //!< The context of the guest thread during the last SVC
jmp_buf originalCtx; //!< The context of the host thread prior to jumping into guest code
void *entry;
u64 entryArgument;
void *stackTop;
void *entry; //!< A function pointer to the thread's entry
u64 entryArgument; //!< An argument to provide with to the thread entry function
void *stackTop; //!< The top of the guest's stack, this is set to the initial guest stack pointer
std::condition_variable wakeCondition; //!< A conditional variable which is signalled to wake the current thread while it's sleeping
std::condition_variable scheduleCondition; //!< Signalled to wake the thread when it's scheduled or its resident core changes
std::atomic<u8> basePriority; //!< The priority of the thread for the scheduler without any priority-inheritance
std::atomic<u8> priority; //!< The priority of the thread for the scheduler
std::atomic<u8> priority; //!< The priority of the thread for the scheduler including priority-inheritance
std::mutex coreMigrationMutex; //!< Synchronizes operations which depend on which core the thread is running on
i8 idealCore; //!< The ideal CPU core for this thread to run on
i8 coreId; //!< The CPU core on which this thread is running
CoreMask affinityMask{}; //!< A mask of CPU cores this thread is allowed to run on
std::mutex coreMigrationMutex; //!< Synchronizes operations which depend on which core the thread is running on
u64 timesliceStart{}; //!< Start of the scheduler timeslice
u64 timesliceStart{}; //!< A timestamp in host CNTVCT ticks of when the thread's current timeslice started
u64 averageTimeslice{}; //!< A weighted average of the timeslice duration for this thread
timer_t preemptionTimer{}; //!< A kernel timer used for preemption interrupts
bool isPreempted{}; //!< If the preemption timer has been armed and will fire
bool pendingYield{}; //!< If the current thread has been yielded and hasn't been acted upon it yet
bool pendingYield{}; //!< If the thread has been yielded and hasn't been acted upon it yet
bool forceYield{}; //!< If the thread has been forcefully yielded by another thread
std::mutex waiterMutex; //!< Synchronizes operations on mutation of the waiter members
u32* waitKey; //!< The key of the mutex which this thread is waiting on
u32 *waitKey; //!< The key of the mutex which this thread is waiting on
KHandle waitTag; //!< The handle of the thread which requested the mutex lock
std::shared_ptr<KThread> waitThread; //!< The thread which this thread is waiting on
std::list<std::shared_ptr<type::KThread>> waiters; //!< A queue of threads waiting on this thread sorted by priority
bool isCancellable{false}; //!< If the thread is currently in a position where it is cancellable
bool cancelSync{false}; //!< If to cancel a SvcWaitSynchronization call this thread currently is in/the next one it joins
type::KSyncObject* wakeObject{}; //!< A pointer to the synchronization object responsible for waking this thread up
bool isCancellable{false}; //!< If the thread is currently in a position where it's cancellable
bool cancelSync{false}; //!< Whether to cancel the SvcWaitSynchronization call this thread currently is in/the next one it joins
type::KSyncObject *wakeObject{}; //!< A pointer to the synchronization object responsible for waking this thread up
KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, u8 priority, i8 idealCore);
@ -85,7 +94,7 @@ namespace skyline {
void SendSignal(int signal);
/**
* @return If the supplied priority value is higher than the current thread
* @return If the supplied priority value is higher than the supplied thread's priority value
*/
static constexpr bool IsHigherPriority(const i8 priority, const std::shared_ptr<type::KThread> &it) {
return priority < it->priority;

View File

@ -50,16 +50,16 @@ namespace skyline::loader {
class Loader {
private:
/**
* @brief All data used to determine the corresponding symbol for an address
* @brief All data used to determine the corresponding symbol for an address from an executable
*/
struct ExecutableSymbolicInfo {
void* patchStart; //!< A pointer to the start of this executable's patch section
void* programStart; //!< A pointer to the start of this executable
void* programEnd; //!< A pointer to the end of this executable
std::string name; //!< The name of the executable this belongs to
std::string patchName; //!< The name of the executable this belongs to's patch section
span<Elf64_Sym> symbols; //!< A span over the .dynsym section of this executable
span<char> symbolStrings; //!< A span over the .dynstr section of this executable
void *patchStart; //!< A pointer to the start of this executable's patch section
void *programStart; //!< A pointer to the start of the executable
void *programEnd; //!< A pointer to the end of the executable
std::string name; //!< The name of the executable
std::string patchName; //!< The name of the patch section
span<Elf64_Sym> symbols; //!< A span over the .dynsym section
span<char> symbolStrings; //!< A span over the .dynstr section
};
std::vector<ExecutableSymbolicInfo> executables;
@ -80,7 +80,7 @@ namespace skyline::loader {
* @param name An optional name for the executable, used for symbol resolution
* @return An ExecutableLoadInfo struct containing the load base and size
*/
ExecutableLoadInfo LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset = 0, const std::string& name = {});
ExecutableLoadInfo LoadExecutable(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, Executable &executable, size_t offset = 0, const std::string &name = {});
std::optional<vfs::NACP> nacp;
std::shared_ptr<vfs::Backing> romFs;
@ -96,8 +96,11 @@ namespace skyline::loader {
*/
virtual void *LoadProcessData(const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state) = 0;
/**
* @note The lifetime of the data contained within is tied to the lifetime of the Loader class it was obtained from (as this points to symbols from the executables loaded into memory directly)
*/
struct SymbolInfo {
char* name; //!< The name of the symbol that was found
char *name; //!< The name of the symbol that was found
std::string_view executableName; //!< The executable that contained the symbol
};
@ -111,11 +114,11 @@ namespace skyline::loader {
* @param frame The initial stack frame or the calling function's stack frame by default
* @return A string with the stack trace based on the supplied context
*/
std::string GetStackTrace(signal::StackFrame* frame = nullptr);
std::string GetStackTrace(signal::StackFrame *frame = nullptr);
/**
* @return A string with the stack trace based on the stack frames in the supplied vector
*/
std::string GetStackTrace(const std::vector<void*> frames);
std::string GetStackTrace(const std::vector<void *> frames);
};
}

View File

@ -48,7 +48,7 @@ namespace skyline::loader {
/**
* @brief The asset section was created by homebrew developers to store additional data for their applications to use
* @note This would actually be retrieved by NRO homebrew by reading the NRO file itself (reading it's own binary) but libnx homebrew wrongly detects the images to be running in NSO mode where the RomFS is handled by HOS, this allows us to just provide the parsed data from the asset section to it directly
* @note This would actually be retrieved by NRO homebrew by reading the NRO file itself (reading its own binary) but libnx homebrew wrongly detects the images to be running in NSO mode where the RomFS is handled by HOS, this allows us to just provide the parsed data from the asset section to it directly
*/
struct NroAssetHeader {
u32 magic; //!< "ASET"

View File

@ -29,7 +29,7 @@ namespace skyline::loader {
return outputBuffer;
}
Loader::ExecutableLoadInfo NsoLoader::LoadNso(Loader *loader, const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, size_t offset, const std::string& name) {
Loader::ExecutableLoadInfo NsoLoader::LoadNso(Loader *loader, const std::shared_ptr<vfs::Backing> &backing, const std::shared_ptr<kernel::type::KProcess> process, const DeviceState &state, size_t offset, const std::string &name) {
auto header{backing->Read<NsoHeader>()};
if (header.magic != util::MakeMagic<u32>("NSO0"))

View File

@ -35,7 +35,7 @@ namespace skyline::loader {
static_assert(sizeof(NsoSegmentHeader) == 0xC);
struct NsoRelativeSegmentHeader {
u32 offset; //!< The offset of the segment into it's parent segment
u32 offset; //!< The offset of the segment into its parent segment
u32 size; //!< Size of the segment
};
static_assert(sizeof(NsoRelativeSegmentHeader) == 0x8);

View File

@ -35,7 +35,7 @@ namespace skyline {
/**
* @brief Writes a vector of 128-bit user IDs to an output buffer
*/
Result WriteUserList(span <u8> buffer, std::vector <UserId> userIds);
Result WriteUserList(span <u8> buffer, std::vector<UserId> userIds);
public:
IAccountServiceForApplication(const DeviceState &state, ServiceManager &manager);
@ -76,16 +76,14 @@ namespace skyline {
*/
Result GetBaasAccountManagerForApplication(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
SERVICE_DECL (
SFUNC(
0x1, IAccountServiceForApplication, GetUserExistence),
SFUNC(0x2, IAccountServiceForApplication, ListAllUsers),
SFUNC(0x3, IAccountServiceForApplication, ListOpenUsers),
SFUNC(0x4, IAccountServiceForApplication, GetLastOpenedUser),
SFUNC(0x5, IAccountServiceForApplication, GetProfile),
SFUNC(0x64, IAccountServiceForApplication, InitializeApplicationInfoV0),
SFUNC(0x65, IAccountServiceForApplication, GetBaasAccountManagerForApplication)
SERVICE_DECL(
SFUNC(0x1, IAccountServiceForApplication, GetUserExistence),
SFUNC(0x2, IAccountServiceForApplication, ListAllUsers),
SFUNC(0x3, IAccountServiceForApplication, ListOpenUsers),
SFUNC(0x4, IAccountServiceForApplication, GetLastOpenedUser),
SFUNC(0x5, IAccountServiceForApplication, GetProfile),
SFUNC(0x64, IAccountServiceForApplication, InitializeApplicationInfoV0),
SFUNC(0x65, IAccountServiceForApplication, GetBaasAccountManagerForApplication)
)
};
}

View File

@ -8,7 +8,7 @@
namespace skyline::service::am {
/**
* @brief This is used to notify an application about it's own state
* @brief This is used to notify an application about its own state
* @url https://switchbrew.org/wiki/Applet_Manager_services#IApplicationFunctions
*/
class IApplicationFunctions : public BaseService {

View File

@ -31,7 +31,7 @@ namespace skyline::service {
*/
class BaseService {
private:
std::string name; //!< The name of the service, it is only assigned after GetName is called and shouldn't be used directly
std::string name; //!< The name of the service, it's only assigned after GetName is called and shouldn't be used directly
protected:
const DeviceState &state;

View File

@ -30,7 +30,7 @@ namespace skyline::service {
/**
* @brief This constructor fills in the Parcel object with data from a IPC buffer
* @param buffer The buffer that contains the parcel
* @param hasToken If the parcel starts with a token, it is skipped if this flag is true
* @param hasToken If the parcel starts with a token, it's skipped if this flag is true
*/
Parcel(span<u8> buffer, const DeviceState &state, bool hasToken = false);

View File

@ -27,7 +27,7 @@ namespace skyline::service::hid {
Result ActivateDebugPad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Activates the touch screen (if it's disabled, it is enabled by default)
* @brief Activates the touch screen (if it's disabled, it's enabled by default)
*/
Result ActivateTouchScreen(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);

View File

@ -20,7 +20,7 @@ namespace skyline::service::hosbinder {
Parcel out(state);
// We opted for just supporting a single layer and display as it's what basically all games use and wasting cycles on it is pointless
// If this was not done then we would need to maintain an array of GraphicBufferProducer objects for each layer and send the request it specifically
// If this was not done then we would need to maintain an array of GraphicBufferProducer objects for each layer and send the request for it specifically
// There would also need to be an external compositor which composites all the graphics buffers submitted to every GraphicBufferProducer
state.logger->Debug("TransactParcel: Layer ID: {}, Code: {}", layerId, code);

View File

@ -25,13 +25,13 @@ namespace skyline::service::hosbinder {
Result TransactParcel(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Adjusts the reference counts to the underlying binder, it is stubbed as we aren't using the real symbols
* @brief Adjusts the reference counts to the underlying binder, it's stubbed as we aren't using the real symbols
* @url https://switchbrew.org/wiki/Nvnflinger_services#AdjustRefcount
*/
Result AdjustRefcount(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Adjusts the reference counts to the underlying binder, it is stubbed as we aren't using the real symbols
* @brief Adjusts the reference counts to the underlying binder, it's stubbed as we aren't using the real symbols
* @url https://switchbrew.org/wiki/Nvnflinger_services#GetNativeHandle
*/
Result GetNativeHandle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);

View File

@ -55,7 +55,7 @@ namespace skyline {
class NvHostCtrl : public NvDevice {
private:
/**
* @brief Metadata about an event, it is used by QueryEvent and EventWait
* @brief Metadata about an event, it's used by QueryEvent and EventWait
*/
union EventValue {
u32 val;

View File

@ -56,7 +56,7 @@ namespace skyline::service::nvdrv::device {
NvStatus Create(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Returns the handle of an NvMapObject from it's ID
* @brief Returns the handle of an NvMapObject from its ID
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FROM_ID
*/
NvStatus FromId(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
@ -80,7 +80,7 @@ namespace skyline::service::nvdrv::device {
NvStatus Param(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Returns the ID of an NvMapObject from it's handle
* @brief Returns the ID of an NvMapObject from its handle
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_GET_ID
*/
NvStatus GetId(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);

View File

@ -67,7 +67,7 @@ namespace skyline::service::nvdrv {
}
/**
* @brief Closes the specified device with it's file descriptor
* @brief Closes the specified device with its file descriptor
*/
void CloseDevice(u32 fd);
};

View File

@ -29,7 +29,7 @@ namespace skyline::service {
ServiceManager(const DeviceState &state);
/**
* @brief Creates a new service using it's type enum and writes it's handle or virtual handle (If it's a domain request) to IpcResponse
* @brief Creates a new service using its type enum and writes its handle or virtual handle (If it's a domain request) to IpcResponse
* @param name The service's name
* @param session The session object of the command
* @param response The response object to write the handle or virtual handle to
@ -37,7 +37,7 @@ namespace skyline::service {
std::shared_ptr<BaseService> NewService(ServiceName name, type::KSession &session, ipc::IpcResponse &response);
/**
* @brief Registers a service object in the manager and writes it's handle or virtual handle (If it's a domain request) to IpcResponse
* @brief Registers a service object in the manager and writes its handle or virtual handle (If it's a domain request) to IpcResponse
* @param serviceObject An instance of the service
* @param session The session object of the command
* @param response The response object to write the handle or virtual handle to

View File

@ -26,7 +26,7 @@ namespace skyline::service {
Result GetAvailableLanguageCodes(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Converts a language code list index to it's corresponding language code
* @brief Converts a language code list index to its corresponding language code
*/
Result MakeLanguageCode(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);

View File

@ -33,7 +33,7 @@ namespace skyline::service::sm {
Result Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Returns a handle to a service with it's name passed in as an argument
* @brief Returns a handle to a service with its name passed in as an argument
* @url https://switchbrew.org/wiki/Services_API#GetService
*/
Result GetService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);

View File

@ -39,13 +39,13 @@ namespace skyline::service::visrv {
Result GetManagerDisplayService(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Opens up a display using it's name as the input
* @brief Opens up a display using its name as the input
* @url https://switchbrew.org/wiki/Display_services#OpenDisplay
*/
Result OpenDisplay(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Closes an open display using it's ID
* @brief Closes an open display using its ID
* @url https://switchbrew.org/wiki/Display_services#CloseDisplay
*/
Result CloseDisplay(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);

View File

@ -33,7 +33,7 @@ namespace skyline::service::visrv {
Result CreateStrayLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Destroys a stray layer by it's ID
* @brief Destroys a stray layer by its ID
*/
Result DestroyStrayLayer(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
};

View File

@ -51,7 +51,7 @@ namespace skyline::vfs {
std::vector<NpdmKernelCapability> capabilities(aci0.kernelCapability.size / sizeof(NpdmKernelCapability));
backing->Read(span(capabilities), meta.aci0.offset + aci0.kernelCapability.offset);
for (auto capability : capabilities) {
auto trailingOnes{__builtin_ctz(~capability.raw)};
auto trailingOnes{std::countr_zero(~capability.raw)};
switch (trailingOnes) {
case 3:
threadInfo.priority = kernel::Priority{capability.threadInfo.highestPriority, capability.threadInfo.lowestPriority};
@ -65,6 +65,9 @@ namespace skyline::vfs {
kernelVersion.minorVersion = capability.kernelVersion.minorVersion;
kernelVersion.majorVersion = capability.kernelVersion.majorVersion;
break;
default:
break;
}
}

View File

@ -7,7 +7,7 @@
namespace skyline {
namespace constant {
constexpr u32 RomFsEmptyEntry{0xFFFFFFFF}; //!< The value a RomFS entry has it's offset set to if it is empty
constexpr u32 RomFsEmptyEntry{0xFFFFFFFF}; //!< The value a RomFS entry has its offset set to, if it's empty
}
namespace vfs {

View File

@ -241,6 +241,8 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
if (stopEmulation())
emulationThread.join()
vibrators.forEach { (_, vibrator) -> vibrator.cancel() }
shouldFinish = true
executeApplication(intent?.data!!)
@ -410,7 +412,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
ButtonId.RightStick -> StickId.Right
else -> error("Invalid button id")
else -> error("Invalid button ID")
}
setAxisValue(0, stickId.xAxis.ordinal, (position.x * Short.MAX_VALUE).toInt())
setAxisValue(0, stickId.yAxis.ordinal, (-position.y * Short.MAX_VALUE).toInt()) // Y is inverted, since drawing starts from top left