From 37cb74ce8ef329c9c70422a3e9618ea3c47e6c8b Mon Sep 17 00:00:00 2001 From: Ribbit Date: Mon, 29 Sep 2025 18:01:08 -0700 Subject: [PATCH 1/4] [nce] Fix NCE signal chaining and cache invalidation --- src/core/arm/nce/arm_nce.cpp | 96 +++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 877e8ac3c7..e676035b72 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -12,6 +12,7 @@ #include "core/arm/nce/interpreter_visitor.h" #include "core/arm/nce/patcher.h" #include "core/core.h" +#include "core/device_memory.h" #include "core/memory.h" #include "core/hle/kernel/k_process.h" @@ -173,12 +174,40 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi return HandleFailedGuestFault(guest_ctx, raw_info, raw_context); } +namespace { + +void ChainSignalHandler(const struct sigaction& action, int sig, void* raw_info, + void* raw_context) { + if ((action.sa_flags & SA_SIGINFO) != 0) { + if (action.sa_sigaction) { + action.sa_sigaction(sig, static_cast(raw_info), raw_context); + } + return; + } + + if (action.sa_handler == SIG_IGN) { + return; + } + + if (action.sa_handler == SIG_DFL) { + signal(sig, SIG_DFL); + raise(sig); + return; + } + + if (action.sa_handler) { + action.sa_handler(sig); + } +} + +} // namespace + void ArmNce::HandleHostAlignmentFault(int sig, void* raw_info, void* raw_context) { - return g_orig_bus_action.sa_sigaction(sig, static_cast(raw_info), raw_context); + ChainSignalHandler(g_orig_bus_action, sig, raw_info, raw_context); } void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) { - return g_orig_segv_action.sa_sigaction(sig, static_cast(raw_info), raw_context); + ChainSignalHandler(g_orig_segv_action, sig, raw_info, raw_context); } void ArmNce::LockThread(Kernel::KThread* thread) { @@ -322,7 +351,7 @@ void ArmNce::Initialize() { alignment_fault_action.sa_sigaction = reinterpret_cast(&ArmNce::GuestAlignmentFaultSignalHandler); alignment_fault_action.sa_mask = signal_mask; - Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, nullptr); + Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, &g_orig_bus_action); struct sigaction access_fault_action {}; access_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART; @@ -385,41 +414,52 @@ void ArmNce::SignalInterrupt(Kernel::KThread* thread) { } } -const std::size_t CACHE_PAGE_SIZE = 4096; - void ArmNce::ClearInstructionCache() { -#if defined(__GNUC__) || defined(__clang__) - void* start = (void*)((uintptr_t)__builtin_return_address(0) & ~(CACHE_PAGE_SIZE - 1)); - void* end = - (void*)((uintptr_t)start + CACHE_PAGE_SIZE * 2); // Clear two pages for better coverage - // Prefetch next likely pages - __builtin_prefetch((void*)((uintptr_t)end), 1, 3); - __builtin___clear_cache(static_cast(start), static_cast(end)); -#endif -#ifdef __aarch64__ - // Ensure all previous memory operations complete - asm volatile("dmb ish" ::: "memory"); +#if defined(__aarch64__) + // Invalidate the entire instruction cache to the point of unification. + asm volatile("ic iallu" ::: "memory"); asm volatile("dsb ish" ::: "memory"); asm volatile("isb" ::: "memory"); +#else + // Fallback: nothing to do on unsupported architectures since NCE is AArch64-only. #endif } void ArmNce::InvalidateCacheRange(u64 addr, std::size_t size) { - #if defined(__GNUC__) || defined(__clang__) - // Align the start address to cache line boundary for better performance - const size_t CACHE_LINE_SIZE = 64; - addr &= ~(CACHE_LINE_SIZE - 1); + if (size == 0) { + return; + } - // Round up size to nearest cache line - size = (size + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1); +#if defined(__aarch64__) + constexpr std::size_t CACHE_LINE_SIZE = 64; - // Prefetch the range to be invalidated - for (size_t offset = 0; offset < size; offset += CACHE_LINE_SIZE) { - __builtin_prefetch((void*)(addr + offset), 1, 3); + const u64 start = addr & ~(static_cast(CACHE_LINE_SIZE) - 1ULL); + const u64 end = (addr + size + CACHE_LINE_SIZE - 1ULL) & + ~(static_cast(CACHE_LINE_SIZE) - 1ULL); + + auto* const virtual_base = m_system.DeviceMemory().buffer.VirtualBasePointer(); + if (virtual_base == nullptr) { + // Fall back to full invalidation if the direct mapping is unavailable. + ClearInstructionCache(); + return; + } + + for (u64 line = start; line < end; line += CACHE_LINE_SIZE) { + if (line < Core::DramMemoryMap::Base) { + continue; } - #endif - this->ClearInstructionCache(); + const u64 offset = line - Core::DramMemoryMap::Base; + const void* line_ptr = virtual_base + offset; + asm volatile("ic ivau, %0" : : "r"(line_ptr) : "memory"); + } + + asm volatile("dsb ish" ::: "memory"); + asm volatile("isb" ::: "memory"); +#else + (void)addr; + (void)size; +#endif } -} // namespace Core +} // namespace Core \ No newline at end of file From dfca07f4e3df72b243828268d0a3c4a49279ff9f Mon Sep 17 00:00:00 2001 From: xbzk Date: Wed, 1 Oct 2025 00:10:59 +0200 Subject: [PATCH 2/4] Initial a9 (minsdk=28) support (#2600) Minimal changes to make android 10 installable and emulationFragment not immediately crashable. Testers (mainly android 10) NEEDED!!! Co-authored-by: Allison Cunha Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2600 Reviewed-by: Lizzie Co-authored-by: xbzk Co-committed-by: xbzk --- src/android/app/build.gradle.kts | 2 +- .../org/yuzu/yuzu_emu/views/CarouselRecyclerView.kt | 5 +++-- src/common/host_memory.cpp | 10 ++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index d3a05cf3e2..e8d8141711 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -59,7 +59,7 @@ android { defaultConfig { // TODO If this is ever modified, change application_id in strings.xml applicationId = "dev.eden.eden_emulator" - minSdk = 30 + minSdk = 28 targetSdk = 36 versionName = getGitVersion() diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/CarouselRecyclerView.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/CarouselRecyclerView.kt index 8a66ebf11f..2c35e7349a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/CarouselRecyclerView.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/views/CarouselRecyclerView.kt @@ -19,6 +19,7 @@ import org.yuzu.yuzu_emu.adapters.GameAdapter import androidx.core.view.doOnNextLayout import org.yuzu.yuzu_emu.YuzuApplication import androidx.preference.PreferenceManager +import androidx.core.view.WindowInsetsCompat /** * CarouselRecyclerView encapsulates all carousel logic for the games UI. @@ -205,8 +206,8 @@ class CarouselRecyclerView @JvmOverloads constructor( if (enabled) { useCustomDrawingOrder = true - val insets = rootWindowInsets - val bottomInset = insets?.getInsets(android.view.WindowInsets.Type.systemBars())?.bottom ?: 0 + val insets = rootWindowInsets?.let { WindowInsetsCompat.toWindowInsetsCompat(it, this) } + val bottomInset = insets?.getInsets(WindowInsetsCompat.Type.systemBars())?.bottom ?: 0 val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1) val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn( 0f, diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp index 2e36d59569..3838c12903 100644 --- a/src/common/host_memory.cpp +++ b/src/common/host_memory.cpp @@ -56,6 +56,16 @@ #include "common/host_memory.h" #include "common/logging/log.h" +#if defined(__ANDROID__) && __ANDROID_API__ < 30 +#include +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif +static int memfd_create(const char* name, unsigned int flags) { + return syscall(__NR_memfd_create, name, flags); +} +#endif + namespace Common { constexpr size_t PageAlignment = 0x1000; From 43a7470a7d09ad412c7d9815ae23402f4bb7acb0 Mon Sep 17 00:00:00 2001 From: Gamer64 Date: Wed, 1 Oct 2025 01:21:12 +0200 Subject: [PATCH 3/4] [Maxwell]: Fix shaders compilation memory leak (#2606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: collecting "The ObjectPool was never being cleared after use. When compiling complex shaders, this would allocate gigabytes of memory, causing the emulator to run out of RAM and be killed by the operating system. This is a critical fix that prevents out-of-memory crashes on all operating systems when playing games with complex shaders." Co-authored-by: Gamer64 <76565986+Gamer64ytb@users.noreply.github.com> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2606 Reviewed-by: MaranBr Reviewed-by: Shinmegumi Co-authored-by: Gamer64 Co-committed-by: Gamer64 --- .../frontend/maxwell/structured_control_flow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp index b5e1e70b4c..6d325b4aad 100644 --- a/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp +++ b/src/shader_recompiler/frontend/maxwell/structured_control_flow.cpp @@ -991,6 +991,7 @@ IR::AbstractSyntaxList BuildASL(ObjectPool& inst_pool, ObjectPool Date: Mon, 29 Sep 2025 18:01:08 -0700 Subject: [PATCH 4/4] [nce] Fix NCE signal chaining and cache invalidation --- src/core/arm/nce/arm_nce.cpp | 96 +++++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/src/core/arm/nce/arm_nce.cpp b/src/core/arm/nce/arm_nce.cpp index 877e8ac3c7..e676035b72 100644 --- a/src/core/arm/nce/arm_nce.cpp +++ b/src/core/arm/nce/arm_nce.cpp @@ -12,6 +12,7 @@ #include "core/arm/nce/interpreter_visitor.h" #include "core/arm/nce/patcher.h" #include "core/core.h" +#include "core/device_memory.h" #include "core/memory.h" #include "core/hle/kernel/k_process.h" @@ -173,12 +174,40 @@ bool ArmNce::HandleGuestAccessFault(GuestContext* guest_ctx, void* raw_info, voi return HandleFailedGuestFault(guest_ctx, raw_info, raw_context); } +namespace { + +void ChainSignalHandler(const struct sigaction& action, int sig, void* raw_info, + void* raw_context) { + if ((action.sa_flags & SA_SIGINFO) != 0) { + if (action.sa_sigaction) { + action.sa_sigaction(sig, static_cast(raw_info), raw_context); + } + return; + } + + if (action.sa_handler == SIG_IGN) { + return; + } + + if (action.sa_handler == SIG_DFL) { + signal(sig, SIG_DFL); + raise(sig); + return; + } + + if (action.sa_handler) { + action.sa_handler(sig); + } +} + +} // namespace + void ArmNce::HandleHostAlignmentFault(int sig, void* raw_info, void* raw_context) { - return g_orig_bus_action.sa_sigaction(sig, static_cast(raw_info), raw_context); + ChainSignalHandler(g_orig_bus_action, sig, raw_info, raw_context); } void ArmNce::HandleHostAccessFault(int sig, void* raw_info, void* raw_context) { - return g_orig_segv_action.sa_sigaction(sig, static_cast(raw_info), raw_context); + ChainSignalHandler(g_orig_segv_action, sig, raw_info, raw_context); } void ArmNce::LockThread(Kernel::KThread* thread) { @@ -322,7 +351,7 @@ void ArmNce::Initialize() { alignment_fault_action.sa_sigaction = reinterpret_cast(&ArmNce::GuestAlignmentFaultSignalHandler); alignment_fault_action.sa_mask = signal_mask; - Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, nullptr); + Common::SigAction(GuestAlignmentFaultSignal, &alignment_fault_action, &g_orig_bus_action); struct sigaction access_fault_action {}; access_fault_action.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART; @@ -385,41 +414,52 @@ void ArmNce::SignalInterrupt(Kernel::KThread* thread) { } } -const std::size_t CACHE_PAGE_SIZE = 4096; - void ArmNce::ClearInstructionCache() { -#if defined(__GNUC__) || defined(__clang__) - void* start = (void*)((uintptr_t)__builtin_return_address(0) & ~(CACHE_PAGE_SIZE - 1)); - void* end = - (void*)((uintptr_t)start + CACHE_PAGE_SIZE * 2); // Clear two pages for better coverage - // Prefetch next likely pages - __builtin_prefetch((void*)((uintptr_t)end), 1, 3); - __builtin___clear_cache(static_cast(start), static_cast(end)); -#endif -#ifdef __aarch64__ - // Ensure all previous memory operations complete - asm volatile("dmb ish" ::: "memory"); +#if defined(__aarch64__) + // Invalidate the entire instruction cache to the point of unification. + asm volatile("ic iallu" ::: "memory"); asm volatile("dsb ish" ::: "memory"); asm volatile("isb" ::: "memory"); +#else + // Fallback: nothing to do on unsupported architectures since NCE is AArch64-only. #endif } void ArmNce::InvalidateCacheRange(u64 addr, std::size_t size) { - #if defined(__GNUC__) || defined(__clang__) - // Align the start address to cache line boundary for better performance - const size_t CACHE_LINE_SIZE = 64; - addr &= ~(CACHE_LINE_SIZE - 1); + if (size == 0) { + return; + } - // Round up size to nearest cache line - size = (size + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1); +#if defined(__aarch64__) + constexpr std::size_t CACHE_LINE_SIZE = 64; - // Prefetch the range to be invalidated - for (size_t offset = 0; offset < size; offset += CACHE_LINE_SIZE) { - __builtin_prefetch((void*)(addr + offset), 1, 3); + const u64 start = addr & ~(static_cast(CACHE_LINE_SIZE) - 1ULL); + const u64 end = (addr + size + CACHE_LINE_SIZE - 1ULL) & + ~(static_cast(CACHE_LINE_SIZE) - 1ULL); + + auto* const virtual_base = m_system.DeviceMemory().buffer.VirtualBasePointer(); + if (virtual_base == nullptr) { + // Fall back to full invalidation if the direct mapping is unavailable. + ClearInstructionCache(); + return; + } + + for (u64 line = start; line < end; line += CACHE_LINE_SIZE) { + if (line < Core::DramMemoryMap::Base) { + continue; } - #endif - this->ClearInstructionCache(); + const u64 offset = line - Core::DramMemoryMap::Base; + const void* line_ptr = virtual_base + offset; + asm volatile("ic ivau, %0" : : "r"(line_ptr) : "memory"); + } + + asm volatile("dsb ish" ::: "memory"); + asm volatile("isb" ::: "memory"); +#else + (void)addr; + (void)size; +#endif } -} // namespace Core +} // namespace Core \ No newline at end of file