diff options
Diffstat (limited to '')
-rw-r--r-- | src/common/CMakeLists.txt | 20 | ||||
-rw-r--r-- | src/common/fs/file.cpp | 10 | ||||
-rw-r--r-- | src/common/fs/file.h | 3 | ||||
-rw-r--r-- | src/common/fs/fs.cpp | 18 | ||||
-rw-r--r-- | src/common/fs/path_util.h | 2 | ||||
-rw-r--r-- | src/common/host_memory.cpp | 538 | ||||
-rw-r--r-- | src/common/host_memory.h | 70 | ||||
-rw-r--r-- | src/common/logging/backend.cpp | 147 | ||||
-rw-r--r-- | src/common/logging/backend.h | 43 | ||||
-rw-r--r-- | src/common/logging/filter.cpp | 132 | ||||
-rw-r--r-- | src/common/logging/filter.h | 12 | ||||
-rw-r--r-- | src/common/logging/log.h | 120 | ||||
-rw-r--r-- | src/common/logging/text_formatter.cpp | 2 | ||||
-rw-r--r-- | src/common/logging/types.h | 142 | ||||
-rw-r--r-- | src/common/page_table.h | 2 | ||||
-rw-r--r-- | src/common/settings.cpp | 10 | ||||
-rw-r--r-- | src/common/settings.h | 6 |
17 files changed, 975 insertions, 302 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 7a4d9e354..7534eb8f1 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -21,14 +21,14 @@ find_package(Git QUIET) add_custom_command(OUTPUT scm_rev.cpp COMMAND ${CMAKE_COMMAND} - -DSRC_DIR="${CMAKE_SOURCE_DIR}" - -DBUILD_REPOSITORY="${BUILD_REPOSITORY}" - -DTITLE_BAR_FORMAT_IDLE="${TITLE_BAR_FORMAT_IDLE}" - -DTITLE_BAR_FORMAT_RUNNING="${TITLE_BAR_FORMAT_RUNNING}" - -DBUILD_TAG="${BUILD_TAG}" - -DBUILD_ID="${DISPLAY_VERSION}" - -DGIT_EXECUTABLE="${GIT_EXECUTABLE}" - -P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" + -DSRC_DIR=${CMAKE_SOURCE_DIR} + -DBUILD_REPOSITORY=${BUILD_REPOSITORY} + -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE} + -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING} + -DBUILD_TAG=${BUILD_TAG} + -DBUILD_ID=${DISPLAY_VERSION} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake DEPENDS # WARNING! It was too much work to try and make a common location for this list, # so if you need to change it, please update CMakeModules/GenerateSCMRev.cmake as well @@ -92,6 +92,7 @@ add_custom_command(OUTPUT scm_rev.cpp "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h" # technically we should regenerate if the git version changed, but its not worth the effort imo "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" + VERBATIM ) add_library(common STATIC @@ -130,6 +131,8 @@ add_library(common STATIC hash.h hex_util.cpp hex_util.h + host_memory.cpp + host_memory.h intrusive_red_black_tree.h logging/backend.cpp logging/backend.h @@ -138,6 +141,7 @@ add_library(common STATIC logging/log.h logging/text_formatter.cpp logging/text_formatter.h + logging/types.h lz4_compression.cpp lz4_compression.h math_util.h diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index 9f3de1cb0..710e88b39 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp @@ -183,10 +183,6 @@ size_t WriteStringToFile(const std::filesystem::path& path, FileType type, size_t AppendStringToFile(const std::filesystem::path& path, FileType type, std::string_view string) { - if (!Exists(path)) { - return WriteStringToFile(path, type, string); - } - if (!IsFile(path)) { return 0; } @@ -309,7 +305,11 @@ bool IOFile::Flush() const { errno = 0; - const auto flush_result = std::fflush(file) == 0; +#ifdef _WIN32 + const auto flush_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0; +#else + const auto flush_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0; +#endif if (!flush_result) { const auto ec = std::error_code{errno, std::generic_category()}; diff --git a/src/common/fs/file.h b/src/common/fs/file.h index 50e270c5b..0f10b6003 100644 --- a/src/common/fs/file.h +++ b/src/common/fs/file.h @@ -71,7 +71,7 @@ template <typename Path> /** * Writes a string to a file at path and returns the number of characters successfully written. - * If an file already exists at path, its contents will be erased. + * If a file already exists at path, its contents will be erased. * If the filesystem object at path is not a file, this function returns 0. * * @param path Filesystem path @@ -95,7 +95,6 @@ template <typename Path> /** * Appends a string to a file at path and returns the number of characters successfully written. - * If a file does not exist at path, WriteStringToFile is called instead. * If the filesystem object at path is not a file, this function returns 0. * * @param path Filesystem path diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index d492480d9..d3159e908 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp @@ -321,7 +321,8 @@ bool RemoveDirContentsRecursively(const fs::path& path) { std::error_code ec; - for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { + // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC. + for (const auto& entry : fs::directory_iterator(path, ec)) { if (ec) { LOG_ERROR(Common_Filesystem, "Failed to completely enumerate the directory at path={}, ec_message={}", @@ -337,6 +338,12 @@ bool RemoveDirContentsRecursively(const fs::path& path) { PathToUTF8String(entry.path()), ec.message()); break; } + + // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator. + // recursive_directory_iterator throws an exception despite passing in a std::error_code. + if (entry.status().type() == fs::file_type::directory) { + return RemoveDirContentsRecursively(entry.path()); + } } if (ec) { @@ -475,7 +482,8 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, std::error_code ec; - for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { + // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC. + for (const auto& entry : fs::directory_iterator(path, ec)) { if (ec) { break; } @@ -495,6 +503,12 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, break; } } + + // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator. + // recursive_directory_iterator throws an exception despite passing in a std::error_code. + if (entry.status().type() == fs::file_type::directory) { + IterateDirEntriesRecursively(entry.path(), callback, filter); + } } if (callback_error || ec) { diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 14e8c35d7..f956ac9a2 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -209,7 +209,7 @@ void SetYuzuPath(YuzuPath yuzu_path, const std::filesystem::path& new_path); #ifdef _WIN32 template <typename Path> -[[nodiscard]] void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { +void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { if constexpr (IsChar<typename Path::value_type>) { SetYuzuPath(yuzu_path, ToU8String(new_path)); } else { diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp new file mode 100644 index 000000000..8bd70abc7 --- /dev/null +++ b/src/common/host_memory.cpp @@ -0,0 +1,538 @@ +#ifdef _WIN32 + +#include <iterator> +#include <unordered_map> +#include <boost/icl/separate_interval_set.hpp> +#include <windows.h> +#include "common/dynamic_library.h" + +#elif defined(__linux__) // ^^^ Windows ^^^ vvv Linux vvv + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include <fcntl.h> +#include <sys/mman.h> +#include <unistd.h> + +#endif // ^^^ Linux ^^^ + +#include <mutex> + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/host_memory.h" +#include "common/logging/log.h" +#include "common/scope_exit.h" + +namespace Common { + +constexpr size_t PageAlignment = 0x1000; +constexpr size_t HugePageSize = 0x200000; + +#ifdef _WIN32 + +// Manually imported for MinGW compatibility +#ifndef MEM_RESERVE_PLACEHOLDER +#define MEM_RESERVE_PLACEHOLDER 0x0004000 +#endif +#ifndef MEM_REPLACE_PLACEHOLDER +#define MEM_REPLACE_PLACEHOLDER 0x00004000 +#endif +#ifndef MEM_COALESCE_PLACEHOLDERS +#define MEM_COALESCE_PLACEHOLDERS 0x00000001 +#endif +#ifndef MEM_PRESERVE_PLACEHOLDER +#define MEM_PRESERVE_PLACEHOLDER 0x00000002 +#endif + +using PFN_CreateFileMapping2 = _Ret_maybenull_ HANDLE(WINAPI*)( + _In_ HANDLE File, _In_opt_ SECURITY_ATTRIBUTES* SecurityAttributes, _In_ ULONG DesiredAccess, + _In_ ULONG PageProtection, _In_ ULONG AllocationAttributes, _In_ ULONG64 MaximumSize, + _In_opt_ PCWSTR Name, + _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters, + _In_ ULONG ParameterCount); + +using PFN_VirtualAlloc2 = _Ret_maybenull_ PVOID(WINAPI*)( + _In_opt_ HANDLE Process, _In_opt_ PVOID BaseAddress, _In_ SIZE_T Size, + _In_ ULONG AllocationType, _In_ ULONG PageProtection, + _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters, + _In_ ULONG ParameterCount); + +using PFN_MapViewOfFile3 = _Ret_maybenull_ PVOID(WINAPI*)( + _In_ HANDLE FileMapping, _In_opt_ HANDLE Process, _In_opt_ PVOID BaseAddress, + _In_ ULONG64 Offset, _In_ SIZE_T ViewSize, _In_ ULONG AllocationType, _In_ ULONG PageProtection, + _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters, + _In_ ULONG ParameterCount); + +using PFN_UnmapViewOfFile2 = BOOL(WINAPI*)(_In_ HANDLE Process, _In_ PVOID BaseAddress, + _In_ ULONG UnmapFlags); + +template <typename T> +static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn) { + if (!dll.GetSymbol(name, &pfn)) { + LOG_CRITICAL(HW_Memory, "Failed to load {}", name); + throw std::bad_alloc{}; + } +} + +class HostMemory::Impl { +public: + explicit Impl(size_t backing_size_, size_t virtual_size_) + : backing_size{backing_size_}, virtual_size{virtual_size_}, process{GetCurrentProcess()}, + kernelbase_dll("Kernelbase") { + if (!kernelbase_dll.IsOpen()) { + LOG_CRITICAL(HW_Memory, "Failed to load Kernelbase.dll"); + throw std::bad_alloc{}; + } + GetFuncAddress(kernelbase_dll, "CreateFileMapping2", pfn_CreateFileMapping2); + GetFuncAddress(kernelbase_dll, "VirtualAlloc2", pfn_VirtualAlloc2); + GetFuncAddress(kernelbase_dll, "MapViewOfFile3", pfn_MapViewOfFile3); + GetFuncAddress(kernelbase_dll, "UnmapViewOfFile2", pfn_UnmapViewOfFile2); + + // Allocate backing file map + backing_handle = + pfn_CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_WRITE | FILE_MAP_READ, + PAGE_READWRITE, SEC_COMMIT, backing_size, nullptr, nullptr, 0); + if (!backing_handle) { + LOG_CRITICAL(HW_Memory, "Failed to allocate {} MiB of backing memory", + backing_size >> 20); + throw std::bad_alloc{}; + } + // Allocate a virtual memory for the backing file map as placeholder + backing_base = static_cast<u8*>(pfn_VirtualAlloc2(process, nullptr, backing_size, + MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, + PAGE_NOACCESS, nullptr, 0)); + if (!backing_base) { + Release(); + LOG_CRITICAL(HW_Memory, "Failed to reserve {} MiB of virtual memory", + backing_size >> 20); + throw std::bad_alloc{}; + } + // Map backing placeholder + void* const ret = pfn_MapViewOfFile3(backing_handle, process, backing_base, 0, backing_size, + MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0); + if (ret != backing_base) { + Release(); + LOG_CRITICAL(HW_Memory, "Failed to map {} MiB of virtual memory", backing_size >> 20); + throw std::bad_alloc{}; + } + // Allocate virtual address placeholder + virtual_base = static_cast<u8*>(pfn_VirtualAlloc2(process, nullptr, virtual_size, + MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, + PAGE_NOACCESS, nullptr, 0)); + if (!virtual_base) { + Release(); + LOG_CRITICAL(HW_Memory, "Failed to reserve {} GiB of virtual memory", + virtual_size >> 30); + throw std::bad_alloc{}; + } + } + + ~Impl() { + Release(); + } + + void Map(size_t virtual_offset, size_t host_offset, size_t length) { + std::unique_lock lock{placeholder_mutex}; + if (!IsNiechePlaceholder(virtual_offset, length)) { + Split(virtual_offset, length); + } + ASSERT(placeholders.find({virtual_offset, virtual_offset + length}) == placeholders.end()); + TrackPlaceholder(virtual_offset, host_offset, length); + + MapView(virtual_offset, host_offset, length); + } + + void Unmap(size_t virtual_offset, size_t length) { + std::lock_guard lock{placeholder_mutex}; + + // Unmap until there are no more placeholders + while (UnmapOnePlaceholder(virtual_offset, length)) { + } + } + + void Protect(size_t virtual_offset, size_t length, bool read, bool write) { + DWORD new_flags{}; + if (read && write) { + new_flags = PAGE_READWRITE; + } else if (read && !write) { + new_flags = PAGE_READONLY; + } else if (!read && !write) { + new_flags = PAGE_NOACCESS; + } else { + UNIMPLEMENTED_MSG("Protection flag combination read={} write={}", read, write); + } + const size_t virtual_end = virtual_offset + length; + + std::lock_guard lock{placeholder_mutex}; + auto [it, end] = placeholders.equal_range({virtual_offset, virtual_end}); + while (it != end) { + const size_t offset = std::max(it->lower(), virtual_offset); + const size_t protect_length = std::min(it->upper(), virtual_end) - offset; + DWORD old_flags{}; + if (!VirtualProtect(virtual_base + offset, protect_length, new_flags, &old_flags)) { + LOG_CRITICAL(HW_Memory, "Failed to change virtual memory protect rules"); + } + ++it; + } + } + + const size_t backing_size; ///< Size of the backing memory in bytes + const size_t virtual_size; ///< Size of the virtual address placeholder in bytes + + u8* backing_base{}; + u8* virtual_base{}; + +private: + /// Release all resources in the object + void Release() { + if (!placeholders.empty()) { + for (const auto& placeholder : placeholders) { + if (!pfn_UnmapViewOfFile2(process, virtual_base + placeholder.lower(), + MEM_PRESERVE_PLACEHOLDER)) { + LOG_CRITICAL(HW_Memory, "Failed to unmap virtual memory placeholder"); + } + } + Coalesce(0, virtual_size); + } + if (virtual_base) { + if (!VirtualFree(virtual_base, 0, MEM_RELEASE)) { + LOG_CRITICAL(HW_Memory, "Failed to free virtual memory"); + } + } + if (backing_base) { + if (!pfn_UnmapViewOfFile2(process, backing_base, MEM_PRESERVE_PLACEHOLDER)) { + LOG_CRITICAL(HW_Memory, "Failed to unmap backing memory placeholder"); + } + if (!VirtualFreeEx(process, backing_base, 0, MEM_RELEASE)) { + LOG_CRITICAL(HW_Memory, "Failed to free backing memory"); + } + } + if (!CloseHandle(backing_handle)) { + LOG_CRITICAL(HW_Memory, "Failed to free backing memory file handle"); + } + } + + /// Unmap one placeholder in the given range (partial unmaps are supported) + /// Return true when there are no more placeholders to unmap + bool UnmapOnePlaceholder(size_t virtual_offset, size_t length) { + const auto it = placeholders.find({virtual_offset, virtual_offset + length}); + const auto begin = placeholders.begin(); + const auto end = placeholders.end(); + if (it == end) { + return false; + } + const size_t placeholder_begin = it->lower(); + const size_t placeholder_end = it->upper(); + const size_t unmap_begin = std::max(virtual_offset, placeholder_begin); + const size_t unmap_end = std::min(virtual_offset + length, placeholder_end); + ASSERT(unmap_begin >= placeholder_begin && unmap_begin < placeholder_end); + ASSERT(unmap_end <= placeholder_end && unmap_end > placeholder_begin); + + const auto host_pointer_it = placeholder_host_pointers.find(placeholder_begin); + ASSERT(host_pointer_it != placeholder_host_pointers.end()); + const size_t host_offset = host_pointer_it->second; + + const bool split_left = unmap_begin > placeholder_begin; + const bool split_right = unmap_end < placeholder_end; + + if (!pfn_UnmapViewOfFile2(process, virtual_base + placeholder_begin, + MEM_PRESERVE_PLACEHOLDER)) { + LOG_CRITICAL(HW_Memory, "Failed to unmap placeholder"); + } + // If we have to remap memory regions due to partial unmaps, we are in a data race as + // Windows doesn't support remapping memory without unmapping first. Avoid adding any extra + // logic within the panic region described below. + + // Panic region, we are in a data race right now + if (split_left || split_right) { + Split(unmap_begin, unmap_end - unmap_begin); + } + if (split_left) { + MapView(placeholder_begin, host_offset, unmap_begin - placeholder_begin); + } + if (split_right) { + MapView(unmap_end, host_offset + unmap_end - placeholder_begin, + placeholder_end - unmap_end); + } + // End panic region + + size_t coalesce_begin = unmap_begin; + if (!split_left) { + // Try to coalesce pages to the left + coalesce_begin = it == begin ? 0 : std::prev(it)->upper(); + if (coalesce_begin != placeholder_begin) { + Coalesce(coalesce_begin, unmap_end - coalesce_begin); + } + } + if (!split_right) { + // Try to coalesce pages to the right + const auto next = std::next(it); + const size_t next_begin = next == end ? virtual_size : next->lower(); + if (placeholder_end != next_begin) { + // We can coalesce to the right + Coalesce(coalesce_begin, next_begin - coalesce_begin); + } + } + // Remove and reinsert placeholder trackers + UntrackPlaceholder(it); + if (split_left) { + TrackPlaceholder(placeholder_begin, host_offset, unmap_begin - placeholder_begin); + } + if (split_right) { + TrackPlaceholder(unmap_end, host_offset + unmap_end - placeholder_begin, + placeholder_end - unmap_end); + } + return true; + } + + void MapView(size_t virtual_offset, size_t host_offset, size_t length) { + if (!pfn_MapViewOfFile3(backing_handle, process, virtual_base + virtual_offset, host_offset, + length, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0)) { + LOG_CRITICAL(HW_Memory, "Failed to map placeholder"); + } + } + + void Split(size_t virtual_offset, size_t length) { + if (!VirtualFreeEx(process, reinterpret_cast<LPVOID>(virtual_base + virtual_offset), length, + MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) { + LOG_CRITICAL(HW_Memory, "Failed to split placeholder"); + } + } + + void Coalesce(size_t virtual_offset, size_t length) { + if (!VirtualFreeEx(process, reinterpret_cast<LPVOID>(virtual_base + virtual_offset), length, + MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) { + LOG_CRITICAL(HW_Memory, "Failed to coalesce placeholders"); + } + } + + void TrackPlaceholder(size_t virtual_offset, size_t host_offset, size_t length) { + placeholders.insert({virtual_offset, virtual_offset + length}); + placeholder_host_pointers.emplace(virtual_offset, host_offset); + } + + void UntrackPlaceholder(boost::icl::separate_interval_set<size_t>::iterator it) { + placeholders.erase(it); + placeholder_host_pointers.erase(it->lower()); + } + + /// Return true when a given memory region is a "nieche" and the placeholders don't have to be + /// splitted. + bool IsNiechePlaceholder(size_t virtual_offset, size_t length) const { + const auto it = placeholders.upper_bound({virtual_offset, virtual_offset + length}); + if (it != placeholders.end() && it->lower() == virtual_offset + length) { + const bool is_root = it == placeholders.begin() && virtual_offset == 0; + return is_root || std::prev(it)->upper() == virtual_offset; + } + return false; + } + + HANDLE process{}; ///< Current process handle + HANDLE backing_handle{}; ///< File based backing memory + + DynamicLibrary kernelbase_dll; + PFN_CreateFileMapping2 pfn_CreateFileMapping2{}; + PFN_VirtualAlloc2 pfn_VirtualAlloc2{}; + PFN_MapViewOfFile3 pfn_MapViewOfFile3{}; + PFN_UnmapViewOfFile2 pfn_UnmapViewOfFile2{}; + + std::mutex placeholder_mutex; ///< Mutex for placeholders + boost::icl::separate_interval_set<size_t> placeholders; ///< Mapped placeholders + std::unordered_map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset +}; + +#elif defined(__linux__) // ^^^ Windows ^^^ vvv Linux vvv + +class HostMemory::Impl { +public: + explicit Impl(size_t backing_size_, size_t virtual_size_) + : backing_size{backing_size_}, virtual_size{virtual_size_} { + bool good = false; + SCOPE_EXIT({ + if (!good) { + Release(); + } + }); + + // Backing memory initialization + fd = memfd_create("HostMemory", 0); + if (fd == -1) { + LOG_CRITICAL(HW_Memory, "memfd_create failed: {}", strerror(errno)); + throw std::bad_alloc{}; + } + + // Defined to extend the file with zeros + int ret = ftruncate(fd, backing_size); + if (ret != 0) { + LOG_CRITICAL(HW_Memory, "ftruncate failed with {}, are you out-of-memory?", + strerror(errno)); + throw std::bad_alloc{}; + } + + backing_base = static_cast<u8*>( + mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); + if (backing_base == MAP_FAILED) { + LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); + throw std::bad_alloc{}; + } + + // Virtual memory initialization + virtual_base = static_cast<u8*>( + mmap(nullptr, virtual_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + if (virtual_base == MAP_FAILED) { + LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); + throw std::bad_alloc{}; + } + + good = true; + } + + ~Impl() { + Release(); + } + + void Map(size_t virtual_offset, size_t host_offset, size_t length) { + + void* ret = mmap(virtual_base + virtual_offset, length, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED, fd, host_offset); + ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); + } + + void Unmap(size_t virtual_offset, size_t length) { + // The method name is wrong. We're still talking about the virtual range. + // We don't want to unmap, we want to reserve this memory. + + void* ret = mmap(virtual_base + virtual_offset, length, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); + } + + void Protect(size_t virtual_offset, size_t length, bool read, bool write) { + int flags = 0; + if (read) { + flags |= PROT_READ; + } + if (write) { + flags |= PROT_WRITE; + } + int ret = mprotect(virtual_base + virtual_offset, length, flags); + ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno)); + } + + const size_t backing_size; ///< Size of the backing memory in bytes + const size_t virtual_size; ///< Size of the virtual address placeholder in bytes + + u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)}; + u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)}; + +private: + /// Release all resources in the object + void Release() { + if (virtual_base != MAP_FAILED) { + int ret = munmap(virtual_base, virtual_size); + ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno)); + } + + if (backing_base != MAP_FAILED) { + int ret = munmap(backing_base, backing_size); + ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno)); + } + + if (fd != -1) { + int ret = close(fd); + ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno)); + } + } + + int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create +}; + +#else // ^^^ Linux ^^^ vvv Generic vvv + +class HostMemory::Impl { +public: + explicit Impl(size_t /*backing_size */, size_t /* virtual_size */) { + // This is just a place holder. + // Please implement fastmem in a propper way on your platform. + throw std::bad_alloc{}; + } + + void Map(size_t virtual_offset, size_t host_offset, size_t length) {} + + void Unmap(size_t virtual_offset, size_t length) {} + + void Protect(size_t virtual_offset, size_t length, bool read, bool write) {} + + u8* backing_base{nullptr}; + u8* virtual_base{nullptr}; +}; + +#endif // ^^^ Generic ^^^ + +HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) + : backing_size(backing_size_), virtual_size(virtual_size_) { + try { + // Try to allocate a fastmem arena. + // The implementation will fail with std::bad_alloc on errors. + impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), + AlignUp(virtual_size, PageAlignment) + + 3 * HugePageSize); + backing_base = impl->backing_base; + virtual_base = impl->virtual_base; + + if (virtual_base) { + virtual_base += 2 * HugePageSize - 1; + virtual_base -= reinterpret_cast<size_t>(virtual_base) & (HugePageSize - 1); + virtual_base_offset = virtual_base - impl->virtual_base; + } + + } catch (const std::bad_alloc&) { + LOG_CRITICAL(HW_Memory, + "Fastmem unavailable, falling back to VirtualBuffer for memory allocation"); + fallback_buffer = std::make_unique<Common::VirtualBuffer<u8>>(backing_size); + backing_base = fallback_buffer->data(); + virtual_base = nullptr; + } +} + +HostMemory::~HostMemory() = default; + +HostMemory::HostMemory(HostMemory&&) noexcept = default; + +HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default; + +void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) { + ASSERT(virtual_offset % PageAlignment == 0); + ASSERT(host_offset % PageAlignment == 0); + ASSERT(length % PageAlignment == 0); + ASSERT(virtual_offset + length <= virtual_size); + ASSERT(host_offset + length <= backing_size); + if (length == 0 || !virtual_base || !impl) { + return; + } + impl->Map(virtual_offset + virtual_base_offset, host_offset, length); +} + +void HostMemory::Unmap(size_t virtual_offset, size_t length) { + ASSERT(virtual_offset % PageAlignment == 0); + ASSERT(length % PageAlignment == 0); + ASSERT(virtual_offset + length <= virtual_size); + if (length == 0 || !virtual_base || !impl) { + return; + } + impl->Unmap(virtual_offset + virtual_base_offset, length); +} + +void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write) { + ASSERT(virtual_offset % PageAlignment == 0); + ASSERT(length % PageAlignment == 0); + ASSERT(virtual_offset + length <= virtual_size); + if (length == 0 || !virtual_base || !impl) { + return; + } + impl->Protect(virtual_offset + virtual_base_offset, length, read, write); +} + +} // namespace Common diff --git a/src/common/host_memory.h b/src/common/host_memory.h new file mode 100644 index 000000000..9b8326d0f --- /dev/null +++ b/src/common/host_memory.h @@ -0,0 +1,70 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "common/common_types.h" +#include "common/virtual_buffer.h" + +namespace Common { + +/** + * A low level linear memory buffer, which supports multiple mappings + * Its purpose is to rebuild a given sparse memory layout, including mirrors. + */ +class HostMemory { +public: + explicit HostMemory(size_t backing_size_, size_t virtual_size_); + ~HostMemory(); + + /** + * Copy constructors. They shall return a copy of the buffer without the mappings. + * TODO: Implement them with COW if needed. + */ + HostMemory(const HostMemory& other) = delete; + HostMemory& operator=(const HostMemory& other) = delete; + + /** + * Move constructors. They will move the buffer and the mappings to the new object. + */ + HostMemory(HostMemory&& other) noexcept; + HostMemory& operator=(HostMemory&& other) noexcept; + + void Map(size_t virtual_offset, size_t host_offset, size_t length); + + void Unmap(size_t virtual_offset, size_t length); + + void Protect(size_t virtual_offset, size_t length, bool read, bool write); + + [[nodiscard]] u8* BackingBasePointer() noexcept { + return backing_base; + } + [[nodiscard]] const u8* BackingBasePointer() const noexcept { + return backing_base; + } + + [[nodiscard]] u8* VirtualBasePointer() noexcept { + return virtual_base; + } + [[nodiscard]] const u8* VirtualBasePointer() const noexcept { + return virtual_base; + } + +private: + size_t backing_size{}; + size_t virtual_size{}; + + // Low level handler for the platform dependent memory routines + class Impl; + std::unique_ptr<Impl> impl; + u8* backing_base{}; + u8* virtual_base{}; + size_t virtual_base_offset{}; + + // Fallback if fastmem is not supported on this platform + std::unique_ptr<Common::VirtualBuffer<u8>> fallback_buffer; +}; + +} // namespace Common diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 6aa8ac960..d5cff400f 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -17,6 +17,7 @@ #endif #include "common/assert.h" +#include "common/fs/file.h" #include "common/fs/fs.h" #include "common/logging/backend.h" #include "common/logging/log.h" @@ -140,10 +141,14 @@ private: std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; }; +ConsoleBackend::~ConsoleBackend() = default; + void ConsoleBackend::Write(const Entry& entry) { PrintMessage(entry); } +ColorConsoleBackend::~ColorConsoleBackend() = default; + void ColorConsoleBackend::Write(const Entry& entry) { PrintColoredMessage(entry); } @@ -157,16 +162,19 @@ FileBackend::FileBackend(const std::filesystem::path& filename) { void(FS::RemoveFile(old_filename)); void(FS::RenameFile(filename, old_filename)); - file = FS::IOFile(filename, FS::FileAccessMode::Write, FS::FileType::TextFile); + file = + std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile); } +FileBackend::~FileBackend() = default; + void FileBackend::Write(const Entry& entry) { // prevent logs from going over the maximum size (in case its spamming and the user doesn't // know) constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024; constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024; - if (!file.IsOpen()) { + if (!file->IsOpen()) { return; } @@ -176,147 +184,20 @@ void FileBackend::Write(const Entry& entry) { return; } - bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); + bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n')); if (entry.log_level >= Level::Error) { - void(file.Flush()); + void(file->Flush()); } } +DebuggerBackend::~DebuggerBackend() = default; + void DebuggerBackend::Write(const Entry& entry) { #ifdef _WIN32 ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); #endif } -/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. -#define ALL_LOG_CLASSES() \ - CLS(Log) \ - CLS(Common) \ - SUB(Common, Filesystem) \ - SUB(Common, Memory) \ - CLS(Core) \ - SUB(Core, ARM) \ - SUB(Core, Timing) \ - CLS(Config) \ - CLS(Debug) \ - SUB(Debug, Emulated) \ - SUB(Debug, GPU) \ - SUB(Debug, Breakpoint) \ - SUB(Debug, GDBStub) \ - CLS(Kernel) \ - SUB(Kernel, SVC) \ - CLS(Service) \ - SUB(Service, ACC) \ - SUB(Service, Audio) \ - SUB(Service, AM) \ - SUB(Service, AOC) \ - SUB(Service, APM) \ - SUB(Service, ARP) \ - SUB(Service, BCAT) \ - SUB(Service, BPC) \ - SUB(Service, BGTC) \ - SUB(Service, BTDRV) \ - SUB(Service, BTM) \ - SUB(Service, Capture) \ - SUB(Service, ERPT) \ - SUB(Service, ETicket) \ - SUB(Service, EUPLD) \ - SUB(Service, Fatal) \ - SUB(Service, FGM) \ - SUB(Service, Friend) \ - SUB(Service, FS) \ - SUB(Service, GRC) \ - SUB(Service, HID) \ - SUB(Service, IRS) \ - SUB(Service, LBL) \ - SUB(Service, LDN) \ - SUB(Service, LDR) \ - SUB(Service, LM) \ - SUB(Service, Migration) \ - SUB(Service, Mii) \ - SUB(Service, MM) \ - SUB(Service, NCM) \ - SUB(Service, NFC) \ - SUB(Service, NFP) \ - SUB(Service, NIFM) \ - SUB(Service, NIM) \ - SUB(Service, NPNS) \ - SUB(Service, NS) \ - SUB(Service, NVDRV) \ - SUB(Service, OLSC) \ - SUB(Service, PCIE) \ - SUB(Service, PCTL) \ - SUB(Service, PCV) \ - SUB(Service, PM) \ - SUB(Service, PREPO) \ - SUB(Service, PSC) \ - SUB(Service, PSM) \ - SUB(Service, SET) \ - SUB(Service, SM) \ - SUB(Service, SPL) \ - SUB(Service, SSL) \ - SUB(Service, TCAP) \ - SUB(Service, Time) \ - SUB(Service, USB) \ - SUB(Service, VI) \ - SUB(Service, WLAN) \ - CLS(HW) \ - SUB(HW, Memory) \ - SUB(HW, LCD) \ - SUB(HW, GPU) \ - SUB(HW, AES) \ - CLS(IPC) \ - CLS(Frontend) \ - CLS(Render) \ - SUB(Render, Software) \ - SUB(Render, OpenGL) \ - SUB(Render, Vulkan) \ - CLS(Audio) \ - SUB(Audio, DSP) \ - SUB(Audio, Sink) \ - CLS(Input) \ - CLS(Network) \ - CLS(Loader) \ - CLS(CheatEngine) \ - CLS(Crypto) \ - CLS(WebService) - -// GetClassName is a macro defined by Windows.h, grrr... -const char* GetLogClassName(Class log_class) { - switch (log_class) { -#define CLS(x) \ - case Class::x: \ - return #x; -#define SUB(x, y) \ - case Class::x##_##y: \ - return #x "." #y; - ALL_LOG_CLASSES() -#undef CLS -#undef SUB - case Class::Count: - break; - } - return "Invalid"; -} - -const char* GetLevelName(Level log_level) { -#define LVL(x) \ - case Level::x: \ - return #x - switch (log_level) { - LVL(Trace); - LVL(Debug); - LVL(Info); - LVL(Warning); - LVL(Error); - LVL(Critical); - case Level::Count: - break; - } -#undef LVL - return "Invalid"; -} - void SetGlobalFilter(const Filter& filter) { Impl::Instance().SetGlobalFilter(filter); } diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index eb629a33f..4b9a910c1 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -1,43 +1,32 @@ // Copyright 2014 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. + #pragma once -#include <chrono> #include <filesystem> #include <memory> #include <string> #include <string_view> -#include "common/fs/file.h" #include "common/logging/filter.h" #include "common/logging/log.h" +namespace Common::FS { +class IOFile; +} + namespace Common::Log { class Filter; /** - * A log entry. Log entries are store in a structured format to permit more varied output - * formatting on different frontends, as well as facilitating filtering and aggregation. - */ -struct Entry { - std::chrono::microseconds timestamp; - Class log_class{}; - Level log_level{}; - const char* filename = nullptr; - unsigned int line_num = 0; - std::string function; - std::string message; - bool final_entry = false; -}; - -/** * Interface for logging backends. As loggers can be created and removed at runtime, this can be * used by a frontend for adding a custom logging backend as needed */ class Backend { public: virtual ~Backend() = default; + virtual void SetFilter(const Filter& new_filter) { filter = new_filter; } @@ -53,6 +42,8 @@ private: */ class ConsoleBackend : public Backend { public: + ~ConsoleBackend() override; + static const char* Name() { return "console"; } @@ -67,6 +58,8 @@ public: */ class ColorConsoleBackend : public Backend { public: + ~ColorConsoleBackend() override; + static const char* Name() { return "color_console"; } @@ -83,6 +76,7 @@ public: class FileBackend : public Backend { public: explicit FileBackend(const std::filesystem::path& filename); + ~FileBackend() override; static const char* Name() { return "file"; @@ -95,7 +89,7 @@ public: void Write(const Entry& entry) override; private: - FS::IOFile file; + std::unique_ptr<FS::IOFile> file; std::size_t bytes_written = 0; }; @@ -104,6 +98,8 @@ private: */ class DebuggerBackend : public Backend { public: + ~DebuggerBackend() override; + static const char* Name() { return "debugger"; } @@ -120,17 +116,6 @@ void RemoveBackend(std::string_view backend_name); Backend* GetBackend(std::string_view backend_name); /** - * Returns the name of the passed log class as a C-string. Subclasses are separated by periods - * instead of underscores as in the enumeration. - */ -const char* GetLogClassName(Class log_class); - -/** - * Returns the name of the passed log level as a C-string. - */ -const char* GetLevelName(Level log_level); - -/** * The global filter will prevent any messages from even being processed if they are filtered. Each * backend can have a filter, but if the level is lower than the global filter, the backend will * never get the message diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 20a2dd106..4f2cc29e1 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include <algorithm> -#include "common/logging/backend.h" #include "common/logging/filter.h" #include "common/string_util.h" @@ -22,7 +21,7 @@ Level GetLevelByName(const It begin, const It end) { template <typename It> Class GetClassByName(const It begin, const It end) { - for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) { + for (u8 i = 0; i < static_cast<u8>(Class::Count); ++i) { const char* level_name = GetLogClassName(static_cast<Class>(i)); if (Common::ComparePartialString(begin, end, level_name)) { return static_cast<Class>(i); @@ -62,6 +61,135 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { } } // Anonymous namespace +/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. +#define ALL_LOG_CLASSES() \ + CLS(Log) \ + CLS(Common) \ + SUB(Common, Filesystem) \ + SUB(Common, Memory) \ + CLS(Core) \ + SUB(Core, ARM) \ + SUB(Core, Timing) \ + CLS(Config) \ + CLS(Debug) \ + SUB(Debug, Emulated) \ + SUB(Debug, GPU) \ + SUB(Debug, Breakpoint) \ + SUB(Debug, GDBStub) \ + CLS(Kernel) \ + SUB(Kernel, SVC) \ + CLS(Service) \ + SUB(Service, ACC) \ + SUB(Service, Audio) \ + SUB(Service, AM) \ + SUB(Service, AOC) \ + SUB(Service, APM) \ + SUB(Service, ARP) \ + SUB(Service, BCAT) \ + SUB(Service, BPC) \ + SUB(Service, BGTC) \ + SUB(Service, BTDRV) \ + SUB(Service, BTM) \ + SUB(Service, Capture) \ + SUB(Service, ERPT) \ + SUB(Service, ETicket) \ + SUB(Service, EUPLD) \ + SUB(Service, Fatal) \ + SUB(Service, FGM) \ + SUB(Service, Friend) \ + SUB(Service, FS) \ + SUB(Service, GRC) \ + SUB(Service, HID) \ + SUB(Service, IRS) \ + SUB(Service, LBL) \ + SUB(Service, LDN) \ + SUB(Service, LDR) \ + SUB(Service, LM) \ + SUB(Service, Migration) \ + SUB(Service, Mii) \ + SUB(Service, MM) \ + SUB(Service, NCM) \ + SUB(Service, NFC) \ + SUB(Service, NFP) \ + SUB(Service, NIFM) \ + SUB(Service, NIM) \ + SUB(Service, NPNS) \ + SUB(Service, NS) \ + SUB(Service, NVDRV) \ + SUB(Service, OLSC) \ + SUB(Service, PCIE) \ + SUB(Service, PCTL) \ + SUB(Service, PCV) \ + SUB(Service, PM) \ + SUB(Service, PREPO) \ + SUB(Service, PSC) \ + SUB(Service, PSM) \ + SUB(Service, SET) \ + SUB(Service, SM) \ + SUB(Service, SPL) \ + SUB(Service, SSL) \ + SUB(Service, TCAP) \ + SUB(Service, Time) \ + SUB(Service, USB) \ + SUB(Service, VI) \ + SUB(Service, WLAN) \ + CLS(HW) \ + SUB(HW, Memory) \ + SUB(HW, LCD) \ + SUB(HW, GPU) \ + SUB(HW, AES) \ + CLS(IPC) \ + CLS(Frontend) \ + CLS(Render) \ + SUB(Render, Software) \ + SUB(Render, OpenGL) \ + SUB(Render, Vulkan) \ + CLS(Audio) \ + SUB(Audio, DSP) \ + SUB(Audio, Sink) \ + CLS(Input) \ + CLS(Network) \ + CLS(Loader) \ + CLS(CheatEngine) \ + CLS(Crypto) \ + CLS(WebService) + +// GetClassName is a macro defined by Windows.h, grrr... +const char* GetLogClassName(Class log_class) { + switch (log_class) { +#define CLS(x) \ + case Class::x: \ + return #x; +#define SUB(x, y) \ + case Class::x##_##y: \ + return #x "." #y; + ALL_LOG_CLASSES() +#undef CLS +#undef SUB + case Class::Count: + break; + } + return "Invalid"; +} + +const char* GetLevelName(Level log_level) { +#define LVL(x) \ + case Level::x: \ + return #x + switch (log_level) { + LVL(Trace); + LVL(Debug); + LVL(Info); + LVL(Warning); + LVL(Error); + LVL(Critical); + case Level::Count: + break; + } +#undef LVL + return "Invalid"; +} + Filter::Filter(Level default_level) { ResetAll(default_level); } diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h index f5673a9f6..1a3074e04 100644 --- a/src/common/logging/filter.h +++ b/src/common/logging/filter.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include <chrono> #include <cstddef> #include <string_view> #include "common/logging/log.h" @@ -12,6 +13,17 @@ namespace Common::Log { /** + * Returns the name of the passed log class as a C-string. Subclasses are separated by periods + * instead of underscores as in the enumeration. + */ +const char* GetLogClassName(Class log_class); + +/** + * Returns the name of the passed log level as a C-string. + */ +const char* GetLevelName(Level log_level); + +/** * Implements a log message filter which allows different log classes to have different minimum * severity levels. The filter can be changed at runtime and can be parsed from a string to allow * editing via the interface or loading from a configuration file. diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 1f0f8db52..8d43eddc7 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -5,7 +5,7 @@ #pragma once #include <fmt/format.h> -#include "common/common_types.h" +#include "common/logging/types.h" namespace Common::Log { @@ -18,124 +18,6 @@ constexpr const char* TrimSourcePath(std::string_view source) { return source.data() + idx; } -/// Specifies the severity or level of detail of the log message. -enum class Level : u8 { - Trace, ///< Extremely detailed and repetitive debugging information that is likely to - ///< pollute logs. - Debug, ///< Less detailed debugging information. - Info, ///< Status information from important points during execution. - Warning, ///< Minor or potential problems found during execution of a task. - Error, ///< Major problems found during execution of a task that prevent it from being - ///< completed. - Critical, ///< Major problems during execution that threaten the stability of the entire - ///< application. - - Count ///< Total number of logging levels -}; - -typedef u8 ClassType; - -/** - * Specifies the sub-system that generated the log message. - * - * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in - * backend.cpp. - */ -enum class Class : ClassType { - Log, ///< Messages about the log system itself - Common, ///< Library routines - Common_Filesystem, ///< Filesystem interface library - Common_Memory, ///< Memory mapping and management functions - Core, ///< LLE emulation core - Core_ARM, ///< ARM CPU core - Core_Timing, ///< CoreTiming functions - Config, ///< Emulator configuration (including commandline) - Debug, ///< Debugging tools - Debug_Emulated, ///< Debug messages from the emulated programs - Debug_GPU, ///< GPU debugging tools - Debug_Breakpoint, ///< Logging breakpoints and watchpoints - Debug_GDBStub, ///< GDB Stub - Kernel, ///< The HLE implementation of the CTR kernel - Kernel_SVC, ///< Kernel system calls - Service, ///< HLE implementation of system services. Each major service - ///< should have its own subclass. - Service_ACC, ///< The ACC (Accounts) service - Service_AM, ///< The AM (Applet manager) service - Service_AOC, ///< The AOC (AddOn Content) service - Service_APM, ///< The APM (Performance) service - Service_ARP, ///< The ARP service - Service_Audio, ///< The Audio (Audio control) service - Service_BCAT, ///< The BCAT service - Service_BGTC, ///< The BGTC (Background Task Controller) service - Service_BPC, ///< The BPC service - Service_BTDRV, ///< The Bluetooth driver service - Service_BTM, ///< The BTM service - Service_Capture, ///< The capture service - Service_ERPT, ///< The error reporting service - Service_ETicket, ///< The ETicket service - Service_EUPLD, ///< The error upload service - Service_Fatal, ///< The Fatal service - Service_FGM, ///< The FGM service - Service_Friend, ///< The friend service - Service_FS, ///< The FS (Filesystem) service - Service_GRC, ///< The game recording service - Service_HID, ///< The HID (Human interface device) service - Service_IRS, ///< The IRS service - Service_LBL, ///< The LBL (LCD backlight) service - Service_LDN, ///< The LDN (Local domain network) service - Service_LDR, ///< The loader service - Service_LM, ///< The LM (Logger) service - Service_Migration, ///< The migration service - Service_Mii, ///< The Mii service - Service_MM, ///< The MM (Multimedia) service - Service_NCM, ///< The NCM service - Service_NFC, ///< The NFC (Near-field communication) service - Service_NFP, ///< The NFP service - Service_NIFM, ///< The NIFM (Network interface) service - Service_NIM, ///< The NIM service - Service_NPNS, ///< The NPNS service - Service_NS, ///< The NS services - Service_NVDRV, ///< The NVDRV (Nvidia driver) service - Service_OLSC, ///< The OLSC service - Service_PCIE, ///< The PCIe service - Service_PCTL, ///< The PCTL (Parental control) service - Service_PCV, ///< The PCV service - Service_PM, ///< The PM service - Service_PREPO, ///< The PREPO (Play report) service - Service_PSC, ///< The PSC service - Service_PSM, ///< The PSM service - Service_SET, ///< The SET (Settings) service - Service_SM, ///< The SM (Service manager) service - Service_SPL, ///< The SPL service - Service_SSL, ///< The SSL service - Service_TCAP, ///< The TCAP service. - Service_Time, ///< The time service - Service_USB, ///< The USB (Universal Serial Bus) service - Service_VI, ///< The VI (Video interface) service - Service_WLAN, ///< The WLAN (Wireless local area network) service - HW, ///< Low-level hardware emulation - HW_Memory, ///< Memory-map and address translation - HW_LCD, ///< LCD register emulation - HW_GPU, ///< GPU control emulation - HW_AES, ///< AES engine emulation - IPC, ///< IPC interface - Frontend, ///< Emulator UI - Render, ///< Emulator video output and hardware acceleration - Render_Software, ///< Software renderer backend - Render_OpenGL, ///< OpenGL backend - Render_Vulkan, ///< Vulkan backend - Audio, ///< Audio emulation - Audio_DSP, ///< The HLE implementation of the DSP - Audio_Sink, ///< Emulator audio output backend - Loader, ///< ROM loader - CheatEngine, ///< Memory manipulation and engine VM functions - Crypto, ///< Cryptographic engine/functions - Input, ///< Input emulation - Network, ///< Network emulation - WebService, ///< Interface to yuzu Web Services - Count ///< Total number of logging classes -}; - /// Logs a message to the global logger, using fmt void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, unsigned int line_num, const char* function, const char* format, diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 80ee2cca1..cfc0d5846 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp @@ -11,7 +11,7 @@ #include "common/assert.h" #include "common/common_funcs.h" -#include "common/logging/backend.h" +#include "common/logging/filter.h" #include "common/logging/log.h" #include "common/logging/text_formatter.h" #include "common/string_util.h" diff --git a/src/common/logging/types.h b/src/common/logging/types.h new file mode 100644 index 000000000..ee9a1ed84 --- /dev/null +++ b/src/common/logging/types.h @@ -0,0 +1,142 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> + +#include "common/common_types.h" + +namespace Common::Log { + +/// Specifies the severity or level of detail of the log message. +enum class Level : u8 { + Trace, ///< Extremely detailed and repetitive debugging information that is likely to + ///< pollute logs. + Debug, ///< Less detailed debugging information. + Info, ///< Status information from important points during execution. + Warning, ///< Minor or potential problems found during execution of a task. + Error, ///< Major problems found during execution of a task that prevent it from being + ///< completed. + Critical, ///< Major problems during execution that threaten the stability of the entire + ///< application. + + Count ///< Total number of logging levels +}; + +/** + * Specifies the sub-system that generated the log message. + * + * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in + * filter.cpp. + */ +enum class Class : u8 { + Log, ///< Messages about the log system itself + Common, ///< Library routines + Common_Filesystem, ///< Filesystem interface library + Common_Memory, ///< Memory mapping and management functions + Core, ///< LLE emulation core + Core_ARM, ///< ARM CPU core + Core_Timing, ///< CoreTiming functions + Config, ///< Emulator configuration (including commandline) + Debug, ///< Debugging tools + Debug_Emulated, ///< Debug messages from the emulated programs + Debug_GPU, ///< GPU debugging tools + Debug_Breakpoint, ///< Logging breakpoints and watchpoints + Debug_GDBStub, ///< GDB Stub + Kernel, ///< The HLE implementation of the CTR kernel + Kernel_SVC, ///< Kernel system calls + Service, ///< HLE implementation of system services. Each major service + ///< should have its own subclass. + Service_ACC, ///< The ACC (Accounts) service + Service_AM, ///< The AM (Applet manager) service + Service_AOC, ///< The AOC (AddOn Content) service + Service_APM, ///< The APM (Performance) service + Service_ARP, ///< The ARP service + Service_Audio, ///< The Audio (Audio control) service + Service_BCAT, ///< The BCAT service + Service_BGTC, ///< The BGTC (Background Task Controller) service + Service_BPC, ///< The BPC service + Service_BTDRV, ///< The Bluetooth driver service + Service_BTM, ///< The BTM service + Service_Capture, ///< The capture service + Service_ERPT, ///< The error reporting service + Service_ETicket, ///< The ETicket service + Service_EUPLD, ///< The error upload service + Service_Fatal, ///< The Fatal service + Service_FGM, ///< The FGM service + Service_Friend, ///< The friend service + Service_FS, ///< The FS (Filesystem) service + Service_GRC, ///< The game recording service + Service_HID, ///< The HID (Human interface device) service + Service_IRS, ///< The IRS service + Service_LBL, ///< The LBL (LCD backlight) service + Service_LDN, ///< The LDN (Local domain network) service + Service_LDR, ///< The loader service + Service_LM, ///< The LM (Logger) service + Service_Migration, ///< The migration service + Service_Mii, ///< The Mii service + Service_MM, ///< The MM (Multimedia) service + Service_NCM, ///< The NCM service + Service_NFC, ///< The NFC (Near-field communication) service + Service_NFP, ///< The NFP service + Service_NIFM, ///< The NIFM (Network interface) service + Service_NIM, ///< The NIM service + Service_NPNS, ///< The NPNS service + Service_NS, ///< The NS services + Service_NVDRV, ///< The NVDRV (Nvidia driver) service + Service_OLSC, ///< The OLSC service + Service_PCIE, ///< The PCIe service + Service_PCTL, ///< The PCTL (Parental control) service + Service_PCV, ///< The PCV service + Service_PM, ///< The PM service + Service_PREPO, ///< The PREPO (Play report) service + Service_PSC, ///< The PSC service + Service_PSM, ///< The PSM service + Service_SET, ///< The SET (Settings) service + Service_SM, ///< The SM (Service manager) service + Service_SPL, ///< The SPL service + Service_SSL, ///< The SSL service + Service_TCAP, ///< The TCAP service. + Service_Time, ///< The time service + Service_USB, ///< The USB (Universal Serial Bus) service + Service_VI, ///< The VI (Video interface) service + Service_WLAN, ///< The WLAN (Wireless local area network) service + HW, ///< Low-level hardware emulation + HW_Memory, ///< Memory-map and address translation + HW_LCD, ///< LCD register emulation + HW_GPU, ///< GPU control emulation + HW_AES, ///< AES engine emulation + IPC, ///< IPC interface + Frontend, ///< Emulator UI + Render, ///< Emulator video output and hardware acceleration + Render_Software, ///< Software renderer backend + Render_OpenGL, ///< OpenGL backend + Render_Vulkan, ///< Vulkan backend + Audio, ///< Audio emulation + Audio_DSP, ///< The HLE implementation of the DSP + Audio_Sink, ///< Emulator audio output backend + Loader, ///< ROM loader + CheatEngine, ///< Memory manipulation and engine VM functions + Crypto, ///< Cryptographic engine/functions + Input, ///< Input emulation + Network, ///< Network emulation + WebService, ///< Interface to yuzu Web Services + Count ///< Total number of logging classes +}; + +/** + * A log entry. Log entries are store in a structured format to permit more varied output + * formatting on different frontends, as well as facilitating filtering and aggregation. + */ +struct Entry { + std::chrono::microseconds timestamp; + Class log_class{}; + Level log_level{}; + const char* filename = nullptr; + unsigned int line_num = 0; + std::string function; + std::string message; + bool final_entry = false; +}; + +} // namespace Common::Log diff --git a/src/common/page_table.h b/src/common/page_table.h index e92b66b2b..8267e8b4d 100644 --- a/src/common/page_table.h +++ b/src/common/page_table.h @@ -111,6 +111,8 @@ struct PageTable { VirtualBuffer<u64> backing_addr; size_t current_address_space_width_in_bits; + + u8* fastmem_arena; }; } // namespace Common diff --git a/src/common/settings.cpp b/src/common/settings.cpp index bcb4e4be1..9ec71eced 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -55,6 +55,7 @@ void LogSettings() { log_setting("Renderer_UseAsynchronousGpuEmulation", values.use_asynchronous_gpu_emulation.GetValue()); log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue()); + log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue()); log_setting("Renderer_UseVsync", values.use_vsync.GetValue()); log_setting("Renderer_UseAssemblyShaders", values.use_assembly_shaders.GetValue()); log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); @@ -90,6 +91,13 @@ bool IsGPULevelHigh() { values.gpu_accuracy.GetValue() == GPUAccuracy::High; } +bool IsFastmemEnabled() { + if (values.cpu_accuracy.GetValue() == CPUAccuracy::DebugMode) { + return values.cpuopt_fastmem; + } + return true; +} + float Volume() { if (values.audio_muted) { return 0.0f; @@ -115,6 +123,7 @@ void RestoreGlobalState(bool is_powered_on) { values.cpuopt_unsafe_unfuse_fma.SetGlobal(true); values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true); values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true); + values.cpuopt_unsafe_fastmem_check.SetGlobal(true); // Renderer values.renderer_backend.SetGlobal(true); @@ -127,6 +136,7 @@ void RestoreGlobalState(bool is_powered_on) { values.gpu_accuracy.SetGlobal(true); values.use_asynchronous_gpu_emulation.SetGlobal(true); values.use_nvdec_emulation.SetGlobal(true); + values.accelerate_astc.SetGlobal(true); values.use_vsync.SetGlobal(true); values.use_assembly_shaders.SetGlobal(true); values.use_asynchronous_shaders.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 48085b9a9..6198f2d9f 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -125,10 +125,12 @@ struct Values { bool cpuopt_const_prop; bool cpuopt_misc_ir; bool cpuopt_reduce_misalign_checks; + bool cpuopt_fastmem; Setting<bool> cpuopt_unsafe_unfuse_fma; Setting<bool> cpuopt_unsafe_reduce_fp_error; Setting<bool> cpuopt_unsafe_inaccurate_nan; + Setting<bool> cpuopt_unsafe_fastmem_check; // Renderer Setting<RendererBackend> renderer_backend; @@ -145,6 +147,7 @@ struct Values { Setting<GPUAccuracy> gpu_accuracy; Setting<bool> use_asynchronous_gpu_emulation; Setting<bool> use_nvdec_emulation; + Setting<bool> accelerate_astc; Setting<bool> use_vsync; Setting<bool> use_assembly_shaders; Setting<bool> use_asynchronous_shaders; @@ -216,6 +219,7 @@ struct Values { std::string program_args; bool dump_exefs; bool dump_nso; + bool enable_fs_access_log; bool reporting_services; bool quest_flag; bool disable_macro_jit; @@ -249,6 +253,8 @@ void SetConfiguringGlobal(bool is_global); bool IsGPULevelExtreme(); bool IsGPULevelHigh(); +bool IsFastmemEnabled(); + float Volume(); std::string GetTimeZoneString(); |