From 00285925faebcd9fce6f12b4ebfaccdab491203c Mon Sep 17 00:00:00 2001 From: snowstormxd <214507868+snowstormxd@users.noreply.github.com> Date: Mon, 2 Nov 2026 19:07:54 +0000 Subject: core: skip empty bitmap ranges during cache and scheduler traversal Adapted bitmap scans from sylvieistaken/selevet-eglantine performance build 4. Keep the wide-priority fallback and fix the first live slot after empty bitmap words. Include randomized lifecycle and ordering checks. Assisted by Codex. --- src/core/hle/kernel/k_priority_queue.h | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'src/core') diff --git a/src/core/hle/kernel/k_priority_queue.h b/src/core/hle/kernel/k_priority_queue.h index 89a0c5bbea..5d93455d33 100644 --- a/src/core/hle/kernel/k_priority_queue.h +++ b/src/core/hle/kernel/k_priority_queue.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "common/alignment.h" #include "common/assert.h" @@ -196,12 +197,7 @@ public: constexpr Member* GetFront(s32 core) const { ASSERT(IsValidCore(core)); - const s32 priority = s32([](auto const& e) { - for (size_t i = 0; i < e.size(); ++i) - if (e[i]) - return i; - return e.size(); - }(m_available_priorities[core])); + const s32 priority = s32(GetNextSet(m_available_priorities[core], size_t(-1))); if (priority <= LowestPriority) { return m_queues[priority].GetFront(core); } else { @@ -222,7 +218,17 @@ public: template constexpr size_t GetNextSet(std::bitset const& bit, size_t n) const { - for (size_t i = n + 1; i < bit.size(); i++) + const size_t first = n + 1; + if (first >= bit.size()) { + return bit.size(); + } + if constexpr (N <= 64) { + if (!std::is_constant_evaluated()) { + const u64 remaining = bit.to_ullong() >> first; + return remaining != 0 ? first + size_t(std::countr_zero(remaining)) : bit.size(); + } + } + for (size_t i = first; i < bit.size(); i++) if (bit[i]) return i; return bit.size(); -- cgit v1.2.3 From 43490c0417f6ef07a5999c2f8cf1c18e4e4a8520 Mon Sep 17 00:00:00 2001 From: snowstormxd <214507868+snowstormxd@users.noreply.github.com> Date: Mon, 2 Nov 2026 19:31:11 +0000 Subject: kernel: define thread-local state out of line for LTO Give the implementation-local TLS object one definition so ThinLTO retains its dynamic initializer. The state and its lifetime are unchanged. Assisted by Codex. --- src/core/hle/kernel/kernel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/core') diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index f006d129a7..9e6ccc2d89 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -79,7 +79,7 @@ struct KernelCore::Impl { // And we have the guarantee that the data won't move out of the way so we can safely // take a reference to it. This isn't always universally true but this is "global" data // so it will be statically given a TLS slot anyways. - static inline thread_local ThreadLocalData tls_data = {}; + static thread_local ThreadLocalData tls_data; explicit Impl(Core::System& system_, KernelCore& kernel_) : system{system_} { tls_data.lock = true; @@ -861,6 +861,8 @@ struct KernelCore::Impl { Core::System& system; }; +thread_local ThreadLocalData KernelCore::Impl::tls_data{}; + KernelCore::KernelCore(Core::System& system) : impl{std::make_unique(system, *this)} {} KernelCore::~KernelCore() = default; -- cgit v1.2.3 From 6cdf4c28a77af1f3a4431c129bed11895630d83f Mon Sep 17 00:00:00 2001 From: snowstormxd <214507868+snowstormxd@users.noreply.github.com> Date: Mon, 2 Nov 2026 19:42:01 +0000 Subject: loader: find mixed-case loose RomFS directories consistently Linux missed base version metadata for Base/RomFS while Windows included it. Use a case-insensitive fallback for the loose directory and check the version and resulting archive checksum. Assisted by Codex. --- src/core/loader/deconstructed_rom_directory.cpp | 22 ++++++++++++++++++++-- src/tests/core/nup_writer.cpp | 5 ++++- 2 files changed, 24 insertions(+), 3 deletions(-) (limited to 'src/core') diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 4b1168a59b..49352a5b49 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -81,6 +81,24 @@ struct PatchCollection { namespace { +FileSys::VirtualDir FindLooseRomFSDirectory(const FileSys::VirtualDir& parent) { + if (parent == nullptr) { + return nullptr; + } + if (const auto dir = parent->GetSubdirectory("romfs")) { + return dir; + } + for (const auto& dir : parent->GetSubdirectories()) { + const auto name = dir->GetName(); + if (name.size() == 5 && std::equal(name.begin(), name.end(), "romfs", [](char a, char b) { + return std::tolower(static_cast(a)) == b; + })) { + return dir; + } + } + return nullptr; +} + // RomFS for a loose ExeFS: the first .romfs file next to main, else a romfs/ directory next to // the exefs/ directory (the usual layout of an extracted title). FileSys::VirtualFile FindLooseRomFS(const FileSys::VirtualDir& exefs_dir) { @@ -93,7 +111,7 @@ FileSys::VirtualFile FindLooseRomFS(const FileSys::VirtualDir& exefs_dir) { } } if (const auto parent = exefs_dir->GetParentDirectory()) { - if (const auto romfs_dir = parent->GetSubdirectory("romfs")) { + if (const auto romfs_dir = FindLooseRomFSDirectory(parent)) { // Building it walks every file and keeps an object for each (title 1: 206k files, ~150 // MiB), so loaders of the same directory share one build while any of them holds it. static std::mutex cache_mutex; @@ -268,7 +286,7 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys romfs_exefs_dir = file_dir; if (const auto title_dir = file_dir->GetParentDirectory()) { - romfs_version = RomFSProductVersion(title_dir->GetSubdirectory("romfs")); + romfs_version = RomFSProductVersion(FindLooseRomFSDirectory(title_dir)); } } diff --git a/src/tests/core/nup_writer.cpp b/src/tests/core/nup_writer.cpp index c0230d8694..79a4015e2c 100644 --- a/src/tests/core/nup_writer.cpp +++ b/src/tests/core/nup_writer.cpp @@ -197,9 +197,11 @@ TEST_CASE("NUP packs base, updates and add-ons and reads back through NupContain REQUIRE(packages[0].kind == NupContainer::PackageKind::Base); REQUIRE(packages[0].title_id == TitleId); REQUIRE(packages[0].version == 0); + REQUIRE(packages[0].display_version == "1.0.0"); REQUIRE(packages[0].npdm.size() == 0x100); REQUIRE(packages[1].kind == NupContainer::PackageKind::Update); REQUIRE(packages[1].version == 65536); + REQUIRE(packages[1].display_version == "1.1.0"); REQUIRE(packages[1].title_id == TitleId); REQUIRE_FALSE(packages[1].standalone_update); REQUIRE(packages[2].kind == NupContainer::PackageKind::Update); @@ -388,7 +390,8 @@ TEST_CASE("NUP output is reproducible", "[nup]") { for (const u8 b : Sha256(ReadAll(archive))) { hex += fmt::format("{:02x}", b); } - REQUIRE(hex == "df8446903ab2c39a51af0304935eb4aa5d8ead105440976ceae7ab97133f6143"); + // The mixed-case Base/RomFS directory supplies base version 1.0.0 on every platform. + REQUIRE(hex == "718231e0974ca04c7be7bd6ed4334015b777c6e5f1ed51de23f899f81ee004ab"); } TEST_CASE("NUP writes metadata overrides to the base and the newest update", "[nup]") { -- cgit v1.2.3 From 06e211e0d0a1634d16f1d74d75682092fc92a5de Mon Sep 17 00:00:00 2001 From: snowstormxd <214507868+snowstormxd@users.noreply.github.com> Date: Sun, 1 Nov 2026 14:21:03 +0000 Subject: nx2: rearm clean read areas when the GPU writes them again Port the coherency changes from alarok/Eglantine 77fc365f2 without its diagnostic framework or shutdown changes. Only GPU-dirty CPU reads become permanently preflushable, clean areas can be trapped on the next write, non-core readers avoid indexing core arrays, and collected CPU writes precede readback. Assisted-by: Codex (source review, selective integration and verification) (cherry picked from commit b64e211957180aa218ec6d9a3bcb020332279c3f) --- src/core/memory.cpp | 15 +++++++++++++++ src/video_core/buffer_cache/buffer_cache.h | 13 +++++++++++-- src/video_core/gpu.cpp | 4 +++- 3 files changed, 29 insertions(+), 3 deletions(-) (limited to 'src/core') diff --git a/src/core/memory.cpp b/src/core/memory.cpp index cfc39916db..1f7315f3c1 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -549,6 +549,8 @@ struct Memory::Impl { } void RasterizerArmReadTraps(u64 vaddr, u64 size) { + // Clean cached read areas also need invalidation when reactive flushing is enabled. + read_trap_generation.fetch_add(1, std::memory_order_release); if (vaddr == 0 || size == 0 || !AddressSpaceContains(*current_page_table, vaddr, size) || !ReadTrapsEnabled()) { return; @@ -781,6 +783,19 @@ struct Memory::Impl { gpu_device_memory = &system.Host1x().MemoryManager(); } const size_t core = system.GetCurrentHostThreadID(); + if (core >= Core::Hardware::NUM_CPU_CORES) { + // A service thread has no slot in the arrays below, and it does not use a slot of a + // core. It keeps no area, so it asks on each read. + if (static std::atomic_flag logged; !logged.test_and_set(std::memory_order_relaxed)) { + LOG_INFO(HW_Memory, "Read of cached guest memory from a thread that is not a core"); + } + Common::ScratchBuffer scratch; + gpu_device_memory->ApplyOpOnPointer(p, scratch, [&](DAddr address) { + DisarmReadTraps(v_address, size); + (void)system.GPU().OnCPURead(address, size); + }); + return; + } auto& current_area = rasterizer_read_areas[core]; if (const u64 generation = read_trap_generation.load(std::memory_order_acquire); read_area_generations[core] != generation) { diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 481b2f0f32..f6d394ce81 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -234,8 +234,17 @@ std::optional BufferCache

::GetFlushArea(DA }; area->preemtive = !IsRegionGpuModified(device_addr_start_aligned, device_addr_end_aligned - device_addr_start_aligned); - memory_tracker.MarkRegionAsPreflushable(device_addr_start_aligned, - device_addr_end_aligned - device_addr_start_aligned); + // Mark only data that the GPU wrote. A mark stays for the session and sends later GPU writes + // in the range to a download at each fence. + if (!area->preemtive) { + memory_tracker.MarkRegionAsPreflushable(device_addr_start_aligned, + device_addr_end_aligned - device_addr_start_aligned); + } else { + // The core keeps this area and has no mark. The next GPU write here must arm the range + // again, which ends the area and sets the trap. + read_trapped_ranges.Subtract(device_addr_start_aligned, + device_addr_end_aligned - device_addr_start_aligned); + } return area; } diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 53f8f063f7..681a4a232d 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -228,7 +228,9 @@ struct GPU::Impl { raster_area.start_address, raster_area.end_address, reads.load(std::memory_order_relaxed)); } - const u64 fence = RequestSyncOperation([this, &raster_area]() { + const u64 fence = RequestSyncOperation([this, raster_area]() { + // Apply collected guest writes before downloading GPU data over guest memory. + InvalidateGPUCache(); renderer->ReadRasterizer()->FlushRegion(raster_area.start_address, raster_area.end_address - raster_area.start_address); }); gpu_thread.TickGPU(is_async); -- cgit v1.2.3 From 85436a5acd36108f8163efbb638945feba567ab4 Mon Sep 17 00:00:00 2001 From: snowstormxd <214507868+snowstormxd@users.noreply.github.com> Date: Mon, 2 Nov 2026 21:14:39 +0000 Subject: kernel: keep migrating fiber TLS lookups outside link-time optimization The game aborted during early thread synchronization with cross-module kernel TLS lookups. Compiling the kernel TLS implementation without LTO restores successful game startup while the other targets retain their build options. Assisted by Codex. --- src/core/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/core') diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index eeb3202d12..1ff9f271d2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1290,4 +1290,9 @@ else() target_sources(core PRIVATE hle/service/ssl/ssl_backend_none.cpp) endif() +if (ENABLE_LTO AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT MSVC) + # Fibers can migrate between host threads; keep kernel TLS lookups outside LTO callers. + set_source_files_properties(hle/kernel/kernel.cpp PROPERTIES COMPILE_OPTIONS -fno-lto) +endif() + create_target_directory_groups(core) -- cgit v1.2.3