From 853676d54c36dec7e8b1dae2f113c23815fafa89 Mon Sep 17 00:00:00 2001 From: GPUCode Date: Mon, 4 Dec 2023 23:18:52 +0200 Subject: [PATCH] kernel: Add KAutoObject and KSlabHeap --- src/common/common_funcs.h | 8 + src/core/CMakeLists.txt | 4 + src/core/hle/kernel/k_auto_object.cpp | 23 ++ src/core/hle/kernel/k_auto_object.h | 288 ++++++++++++++++++++++++++ src/core/hle/kernel/k_slab_heap.h | 191 +++++++++++++++++ src/core/hle/kernel/kernel.cpp | 18 +- src/core/hle/kernel/kernel.h | 23 ++ src/core/hle/kernel/slab_helpers.h | 130 ++++++++++++ 8 files changed, 683 insertions(+), 2 deletions(-) create mode 100644 src/core/hle/kernel/k_auto_object.cpp create mode 100644 src/core/hle/kernel/k_auto_object.h create mode 100644 src/core/hle/kernel/k_slab_heap.h create mode 100644 src/core/hle/kernel/slab_helpers.h diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index ff7452f6c..158227420 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -110,6 +110,14 @@ __declspec(dllimport) void __stdcall DebugBreak(void); return static_cast(key) == 0; \ } +#define CITRA_NON_COPYABLE(cls) \ +cls(const cls&) = delete; \ + cls& operator=(const cls&) = delete + +#define CITRA_NON_MOVEABLE(cls) \ + cls(cls&&) = delete; \ + cls& operator=(cls&&) = delete + // Generic function to get last error message. // Call directly after the command or use the error num. // This function might change the error code. diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0a4570bcc..d228c222a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -147,6 +147,9 @@ add_library(citra_core STATIC hle/kernel/ipc.h hle/kernel/ipc_debugger/recorder.cpp hle/kernel/ipc_debugger/recorder.h + hle/kernel/k_auto_object.cpp + hle/kernel/k_auto_object.h + hle/kernel/k_slab_heap.h hle/kernel/kernel.cpp hle/kernel/kernel.h hle/kernel/memory.cpp @@ -171,6 +174,7 @@ add_library(citra_core STATIC hle/kernel/shared_memory.h hle/kernel/shared_page.cpp hle/kernel/shared_page.h + hle/kernel/slab_helpers.h hle/kernel/svc.cpp hle/kernel/svc.h hle/kernel/svc_wrapper.h diff --git a/src/core/hle/kernel/k_auto_object.cpp b/src/core/hle/kernel/k_auto_object.cpp new file mode 100644 index 000000000..101d34a46 --- /dev/null +++ b/src/core/hle/kernel/k_auto_object.cpp @@ -0,0 +1,23 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/kernel/k_auto_object.h" +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +KAutoObject* KAutoObject::Create(KAutoObject* obj) { + obj->m_ref_count = 1; + return obj; +} + +void KAutoObject::RegisterWithKernel() { + m_kernel.RegisterKernelObject(this); +} + +void KAutoObject::UnregisterWithKernel(KernelSystem& kernel, KAutoObject* self) { + kernel.UnregisterKernelObject(self); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h new file mode 100644 index 000000000..040a081de --- /dev/null +++ b/src/core/hle/kernel/k_auto_object.h @@ -0,0 +1,288 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Kernel { + +class KernelSystem; +class Process; + +enum class ClassTokenType : u32 { + KAutoObject = 0, + WaitObject = 1, + KSemaphore = 27, + KEvent = 31, + KTimer = 53, + KMutex = 57, + KDebug = 77, + KServerPort = 85, + KDmaObject = 89, + KClientPort = 101, + KCodeSet = 104, + KSession = 112, + KThread = 141, + KServerSession = 149, + KAddressArbiter = 152, + KClientSession = 165, + KPort = 168, + KSharedMemory = 176, + KProcess = 197, + KResourceLimit = 200, +}; +DECLARE_ENUM_FLAG_OPERATORS(ClassTokenType) + +#define KERNEL_AUTOOBJECT_TRAITS_IMPL(CLASS, BASE_CLASS, ATTRIBUTE) \ +private: \ + static constexpr inline const char* const TypeName = #CLASS; \ + static constexpr inline auto ClassToken = ClassTokenType::CLASS; \ + \ +public: \ + CITRA_NON_COPYABLE(CLASS); \ + CITRA_NON_MOVEABLE(CLASS); \ + \ + using BaseClass = BASE_CLASS; \ + static constexpr TypeObj GetStaticTypeObj() { \ + return TypeObj(TypeName, ClassToken); \ + } \ + static constexpr const char* GetStaticTypeName() { return TypeName; } \ + virtual TypeObj GetTypeObj() ATTRIBUTE { return GetStaticTypeObj(); } \ + virtual const char* GetTypeName() ATTRIBUTE { return GetStaticTypeName(); } \ + \ +private: \ + constexpr bool operator!=(const TypeObj& rhs) + +#define KERNEL_AUTOOBJECT_TRAITS(CLASS, BASE_CLASS) \ + KERNEL_AUTOOBJECT_TRAITS_IMPL(CLASS, BASE_CLASS, const override) + +class KAutoObject { +protected: + class TypeObj { + public: + constexpr explicit TypeObj(const char* n, ClassTokenType tok) + : m_name(n), m_class_token(tok) {} + + constexpr const char* GetName() const { + return m_name; + } + constexpr ClassTokenType GetClassToken() const { + return m_class_token; + } + + constexpr bool operator==(const TypeObj& rhs) const { + return this->GetClassToken() == rhs.GetClassToken(); + } + + constexpr bool operator!=(const TypeObj& rhs) const { + return this->GetClassToken() != rhs.GetClassToken(); + } + + constexpr bool IsDerivedFrom(const TypeObj& rhs) const { + return (this->GetClassToken() | rhs.GetClassToken()) == this->GetClassToken(); + } + + private: + const char* m_name; + ClassTokenType m_class_token; + }; + +private: + KERNEL_AUTOOBJECT_TRAITS_IMPL(KAutoObject, KAutoObject, const); + +public: + explicit KAutoObject(KernelSystem& kernel) : m_kernel(kernel) { + RegisterWithKernel(); + } + virtual ~KAutoObject() = default; + + static KAutoObject* Create(KAutoObject* ptr); + + // Destroy is responsible for destroying the auto object's resources when ref_count hits zero. + virtual void Destroy() { + UNIMPLEMENTED(); + } + + // Finalize is responsible for cleaning up resource, but does not destroy the object. + virtual void Finalize() {} + + virtual Process* GetOwner() const { + return nullptr; + } + + u32 GetReferenceCount() const { + return m_ref_count.load(); + } + + bool IsDerivedFrom(const TypeObj& rhs) const { + return this->GetTypeObj().IsDerivedFrom(rhs); + } + + bool IsDerivedFrom(const KAutoObject& rhs) const { + return this->IsDerivedFrom(rhs.GetTypeObj()); + } + + template + Derived DynamicCast() { + static_assert(std::is_pointer_v); + using DerivedType = std::remove_pointer_t; + + if (this->IsDerivedFrom(DerivedType::GetStaticTypeObj())) { + return static_cast(this); + } else { + return nullptr; + } + } + + template + const Derived DynamicCast() const { + static_assert(std::is_pointer_v); + using DerivedType = std::remove_pointer_t; + + if (this->IsDerivedFrom(DerivedType::GetStaticTypeObj())) { + return static_cast(this); + } else { + return nullptr; + } + } + + bool Open() { + // Atomically increment the reference count, only if it's positive. + u32 cur_ref_count = m_ref_count.load(std::memory_order_acquire); + do { + if (cur_ref_count == 0) { + return false; + } + ASSERT(cur_ref_count < cur_ref_count + 1); + } while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count + 1, + std::memory_order_relaxed)); + + return true; + } + + void Close() { + // Atomically decrement the reference count, not allowing it to become negative. + u32 cur_ref_count = m_ref_count.load(std::memory_order_acquire); + do { + ASSERT(cur_ref_count > 0); + } while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count - 1, + std::memory_order_acq_rel)); + + // If ref count hits zero, destroy the object. + if (cur_ref_count - 1 == 0) { + KernelSystem& kernel = m_kernel; + this->Destroy(); + KAutoObject::UnregisterWithKernel(kernel, this); + } + } + +private: + void RegisterWithKernel(); + static void UnregisterWithKernel(KernelSystem& kernel, KAutoObject* self); + +protected: + KernelSystem& m_kernel; + +private: + std::atomic m_ref_count{}; +}; + +template +class KScopedAutoObject { +public: + CITRA_NON_COPYABLE(KScopedAutoObject); + + constexpr KScopedAutoObject() = default; + + constexpr KScopedAutoObject(T* o) : m_obj(o) { + if (m_obj != nullptr) { + m_obj->Open(); + } + } + + ~KScopedAutoObject() { + if (m_obj != nullptr) { + m_obj->Close(); + } + m_obj = nullptr; + } + + template + requires(std::derived_from || std::derived_from) + constexpr KScopedAutoObject(KScopedAutoObject&& rhs) { + if constexpr (std::derived_from) { + // Upcast. + m_obj = rhs.m_obj; + rhs.m_obj = nullptr; + } else { + // Downcast. + T* derived = nullptr; + if (rhs.m_obj != nullptr) { + derived = rhs.m_obj->template DynamicCast(); + if (derived == nullptr) { + rhs.m_obj->Close(); + } + } + + m_obj = derived; + rhs.m_obj = nullptr; + } + } + + constexpr KScopedAutoObject& operator=(KScopedAutoObject&& rhs) { + rhs.Swap(*this); + return *this; + } + + constexpr T* operator->() { + return m_obj; + } + constexpr T& operator*() { + return *m_obj; + } + + constexpr void Reset(T* o) { + KScopedAutoObject(o).Swap(*this); + } + + constexpr T* GetPointerUnsafe() { + return m_obj; + } + + constexpr T* GetPointerUnsafe() const { + return m_obj; + } + + constexpr T* ReleasePointerUnsafe() { + T* ret = m_obj; + m_obj = nullptr; + return ret; + } + + constexpr bool IsNull() const { + return m_obj == nullptr; + } + constexpr bool IsNotNull() const { + return m_obj != nullptr; + } + +private: + template + friend class KScopedAutoObject; + +private: + T* m_obj{}; + +private: + constexpr void Swap(KScopedAutoObject& rhs) noexcept { + std::swap(m_obj, rhs.m_obj); + } +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/k_slab_heap.h b/src/core/hle/kernel/k_slab_heap.h new file mode 100644 index 000000000..15e4a2d88 --- /dev/null +++ b/src/core/hle/kernel/k_slab_heap.h @@ -0,0 +1,191 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/assert.h" +#include "common/atomic_ops.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Kernel { + +class KernelSystem; + +namespace impl { + +class KSlabHeapImpl { + CITRA_NON_COPYABLE(KSlabHeapImpl); + CITRA_NON_MOVEABLE(KSlabHeapImpl); + +public: + struct Node { + Node* next{}; + }; + +public: + constexpr KSlabHeapImpl() = default; + + void Initialize() { + ASSERT(m_head == nullptr); + } + + Node* GetHead() const { + return m_head; + } + + void* Allocate() { + Node* ret = m_head; + if (ret != nullptr) [[likely]] { + m_head = ret->next; + } + return ret; + } + + void Free(void* obj) { + Node* node = static_cast(obj); + node->next = m_head; + m_head = node; + } + +private: + std::atomic m_head{}; +}; + +} // namespace impl + +class KSlabHeapBase : protected impl::KSlabHeapImpl { + CITRA_NON_COPYABLE(KSlabHeapBase); + CITRA_NON_MOVEABLE(KSlabHeapBase); + +private: + size_t m_obj_size{}; + uintptr_t m_peak{}; + uintptr_t m_start{}; + uintptr_t m_end{}; + +private: + void UpdatePeakImpl(uintptr_t obj) { + const uintptr_t alloc_peak = obj + this->GetObjectSize(); + uintptr_t cur_peak = m_peak; + do { + if (alloc_peak <= cur_peak) { + break; + } + } while ( + !Common::AtomicCompareAndSwap(std::addressof(m_peak), alloc_peak, cur_peak, cur_peak)); + } + +public: + constexpr KSlabHeapBase() = default; + + bool Contains(uintptr_t address) const { + return m_start <= address && address < m_end; + } + + void Initialize(size_t obj_size, void* memory, size_t memory_size) { + // Ensure we don't initialize a slab using null memory. + ASSERT(memory != nullptr); + + // Set our object size. + m_obj_size = obj_size; + + // Initialize the base allocator. + KSlabHeapImpl::Initialize(); + + // Set our tracking variables. + const size_t num_obj = (memory_size / obj_size); + m_start = reinterpret_cast(memory); + m_end = m_start + num_obj * obj_size; + m_peak = m_start; + + // Free the objects. + u8* cur = reinterpret_cast(m_end); + + for (size_t i = 0; i < num_obj; i++) { + cur -= obj_size; + KSlabHeapImpl::Free(cur); + } + } + + size_t GetSlabHeapSize() const { + return (m_end - m_start) / this->GetObjectSize(); + } + + size_t GetObjectSize() const { + return m_obj_size; + } + + void* Allocate() { + void* obj = KSlabHeapImpl::Allocate(); + return obj; + } + + void Free(void* obj) { + // Don't allow freeing an object that wasn't allocated from this heap. + const bool contained = this->Contains(reinterpret_cast(obj)); + ASSERT(contained); + KSlabHeapImpl::Free(obj); + } + + size_t GetObjectIndex(const void* obj) const { + return (reinterpret_cast(obj) - m_start) / this->GetObjectSize(); + } + + size_t GetPeakIndex() const { + return this->GetObjectIndex(reinterpret_cast(m_peak)); + } + + uintptr_t GetSlabHeapAddress() const { + return m_start; + } + + size_t GetNumRemaining() const { + // Only calculate the number of remaining objects under debug configuration. + return 0; + } +}; + +template +class KSlabHeap final : public KSlabHeapBase { +private: + using BaseHeap = KSlabHeapBase; + +public: + constexpr KSlabHeap() = default; + + void Initialize(void* memory, size_t memory_size) { + BaseHeap::Initialize(sizeof(T), memory, memory_size); + } + + T* Allocate() { + T* obj = static_cast(BaseHeap::Allocate()); + + if (obj != nullptr) [[likely]] { + std::construct_at(obj); + } + return obj; + } + + T* Allocate(KernelSystem& kernel) { + T* obj = static_cast(BaseHeap::Allocate()); + + if (obj != nullptr) [[likely]] { + std::construct_at(obj, kernel); + } + return obj; + } + + void Free(T* obj) { + BaseHeap::Free(obj); + } + + size_t GetObjectIndex(const T* obj) const { + return BaseHeap::GetObjectIndex(obj); + } +}; + +} // namespace Kernel diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index db0caf59c..5276841b7 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -6,10 +6,8 @@ #include #include #include "common/archives.h" -#include "common/serialization/atomic.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/config_mem.h" -#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/ipc_debugger/recorder.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/memory.h" @@ -29,6 +27,7 @@ KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing, : memory(memory), timing(timing), prepare_reschedule_callback(std::move(prepare_reschedule_callback)), memory_mode(memory_mode), n3ds_hw_caps(n3ds_hw_caps) { + slab_heap_container = std::make_unique(); std::generate(memory_regions.begin(), memory_regions.end(), [] { return std::make_shared(); }); MemoryInit(memory_mode, n3ds_hw_caps.memory_mode, override_init_time); @@ -195,6 +194,21 @@ void KernelSystem::serialize(Archive& ar, const unsigned int file_version) { } } +void KernelSystem::RegisterKernelObject(KAutoObject* object) { + registered_objects.insert(object); +} + +void KernelSystem::UnregisterKernelObject(KAutoObject* object) { + registered_objects.erase(object); +} + +struct KernelSystem::SlabHeapContainer { +}; + +template +KSlabHeap& KernelSystem::SlabHeap() { +} + SERIALIZE_IMPL(KernelSystem) } // namespace Kernel diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index e9f77be6c..b26379692 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,7 @@ class SharedMemory; class ThreadManager; class TimerManager; class VMManager; +class KAutoObject; struct AddressMapping; enum class ResetType { @@ -132,6 +134,9 @@ private: friend class boost::serialization::access; }; +template +class KSlabHeap; + class KernelSystem { public: explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing, @@ -264,6 +269,18 @@ public: u32 GenerateObjectID(); + /// Gets the slab heap for the specified kernel object type. + template + KSlabHeap& SlabHeap(); + + /// Registers all kernel objects with the global emulation state, this is purely for tracking + /// leaks after emulation has been shutdown. + void RegisterKernelObject(KAutoObject* object); + + /// Unregisters a kernel object previously registered with RegisterKernelObject when it was + /// destroyed during the current emulation session. + void UnregisterKernelObject(KAutoObject* object); + /// Retrieves a process from the current list of processes. std::shared_ptr GetProcessById(u32 process_id) const; @@ -377,6 +394,12 @@ private: MemoryMode memory_mode; New3dsHwCapabilities n3ds_hw_caps; + /// Helper to encapsulate all slab heaps in a single heap allocated container + struct SlabHeapContainer; + std::unique_ptr slab_heap_container; + + std::unordered_set registered_objects; + /* * Synchronizes access to the internal HLE kernel structures, it is acquired when a guest * application thread performs a syscall. It should be acquired by any host threads that read or diff --git a/src/core/hle/kernel/slab_helpers.h b/src/core/hle/kernel/slab_helpers.h new file mode 100644 index 000000000..3821d8126 --- /dev/null +++ b/src/core/hle/kernel/slab_helpers.h @@ -0,0 +1,130 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/k_auto_object.h" +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +template +class KSlabAllocated { +public: + constexpr KSlabAllocated() = default; + + size_t GetSlabIndex(KernelSystem& kernel) const { + return kernel.SlabHeap().GetIndex(static_cast(this)); + } + +public: + static void InitializeSlabHeap(KernelSystem& kernel, void* memory, size_t memory_size) { + kernel.SlabHeap().Initialize(memory, memory_size); + } + + static Derived* Allocate(KernelSystem& kernel) { + return kernel.SlabHeap().Allocate(kernel); + } + + static void Free(KernelSystem& kernel, Derived* obj) { + kernel.SlabHeap().Free(obj); + } + + static size_t GetObjectSize(KernelSystem& kernel) { + return kernel.SlabHeap().GetObjectSize(); + } + + static size_t GetSlabHeapSize(KernelSystem& kernel) { + return kernel.SlabHeap().GetSlabHeapSize(); + } + + static size_t GetPeakIndex(KernelSystem& kernel) { + return kernel.SlabHeap().GetPeakIndex(); + } + + static uintptr_t GetSlabHeapAddress(KernelSystem& kernel) { + return kernel.SlabHeap().GetSlabHeapAddress(); + } + + static size_t GetNumRemaining(KernelSystem& kernel) { + return kernel.SlabHeap().GetNumRemaining(); + } +}; + +template +class KAutoObjectWithSlabHeap : public Base { + static_assert(std::is_base_of::value); + +private: + static Derived* Allocate(KernelSystem& kernel) { + return kernel.SlabHeap().Allocate(kernel); + } + + static void Free(KernelSystem& kernel, Derived* obj) { + kernel.SlabHeap().Free(obj); + } + +public: + explicit KAutoObjectWithSlabHeap(KernelSystem& kernel) : Base(kernel) {} + virtual ~KAutoObjectWithSlabHeap() = default; + + virtual void Destroy() override { + const bool is_initialized = this->IsInitialized(); + uintptr_t arg = 0; + if (is_initialized) { + arg = this->GetPostDestroyArgument(); + this->Finalize(); + } + Free(Base::m_kernel, static_cast(this)); + if (is_initialized) { + Derived::PostDestroy(arg); + } + } + + virtual bool IsInitialized() const { + return true; + } + virtual uintptr_t GetPostDestroyArgument() const { + return 0; + } + + size_t GetSlabIndex() const { + return SlabHeap(Base::m_kernel).GetObjectIndex(static_cast(this)); + } + +public: + static void InitializeSlabHeap(KernelSystem& kernel, void* memory, size_t memory_size) { + kernel.SlabHeap().Initialize(memory, memory_size); + } + + static Derived* Create(KernelSystem& kernel) { + Derived* obj = Allocate(kernel); + if (obj != nullptr) { + KAutoObject::Create(obj); + } + return obj; + } + + static size_t GetObjectSize(KernelSystem& kernel) { + return kernel.SlabHeap().GetObjectSize(); + } + + static size_t GetSlabHeapSize(KernelSystem& kernel) { + return kernel.SlabHeap().GetSlabHeapSize(); + } + + static size_t GetPeakIndex(KernelSystem& kernel) { + return kernel.SlabHeap().GetPeakIndex(); + } + + static uintptr_t GetSlabHeapAddress(KernelSystem& kernel) { + return kernel.SlabHeap().GetSlabHeapAddress(); + } + + static size_t GetNumRemaining(KernelSystem& kernel) { + return kernel.SlabHeap().GetNumRemaining(); + } +}; + +} // namespace Kernel