From 93df6774709278978be0a2935f55268dda13cb2f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 5 Oct 2026 15:22:17 +0000 Subject: [PATCH] Fix the guest page size at compile time for the Steam Frame build Every translated guest load and store tests RequiresCheckedAccess(). On x86-64 and Windows that is a compile-time false, but on AArch64 it read a global flag, which under PIE is a GOT load plus a byte load and a branch on every access, re-read after every guest store. The flag is only true on kernels with pages larger than 4 KiB. The Steam Frame's SteamOS kernel uses 4 KiB pages (getconf PAGESIZE on the headset reports 4096), so the native --headset steam_frame build now folds the test away like x86-64 does. GuestFlat::Initialize checks the page size at startup in that build and refuses to start with a clear message if it is ever anything else. Android and other AArch64 builds still probe at runtime. Checked with clang --target=aarch64-linux-gnu -O2 -fPIE: a FlatRead32 loop loses both flag loads and the checked Memory::Read32 fallback. Follows the finding from nx-mod/wiicompiled-nx 18ef16f6, which made the flag hidden instead; a compile-time constant removes it entirely. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_018NKSMYZU43wUfYtm1sxGsC --- runtime/include/guest_flat_memory.h | 9 +++++++-- runtime/src/guest_flat_memory.cpp | 13 +++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/runtime/include/guest_flat_memory.h b/runtime/include/guest_flat_memory.h index 6c0bdd3..1217696 100644 --- a/runtime/include/guest_flat_memory.h +++ b/runtime/include/guest_flat_memory.h @@ -71,10 +71,15 @@ bool IsActive(); // accesses must use the checked Memory::* path. // Windows user mode and x86-64 always use a 4 KiB base page, so those builds // fold this to a compile-time false: it appears in every flat access and must -// not become a hot-path load. Only AArch64, where the page size is a kernel -// configuration (4/16/64 KiB), has to probe it at runtime. +// not become a hot-path load. On AArch64 the page size is a kernel +// configuration (4/16/64 KiB), so it is probed at runtime, except in the native +// Steam Frame build: that build only runs on the headset, whose SteamOS kernel +// uses 4 KiB pages, and Initialize() refuses to start on any other page size. #if defined(_WIN32) || defined(__x86_64__) #define MKW_GUEST_FLAT_FIXED_PAGE_SIZE 1 +#elif defined(MKW_HEADSET_STEAM_FRAME) && !defined(__ANDROID__) +#define MKW_GUEST_FLAT_FIXED_PAGE_SIZE 1 +#define MKW_GUEST_FLAT_VERIFY_PAGE_SIZE 1 #endif #if defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE) diff --git a/runtime/src/guest_flat_memory.cpp b/runtime/src/guest_flat_memory.cpp index 9cab9f6..271b488 100644 --- a/runtime/src/guest_flat_memory.cpp +++ b/runtime/src/guest_flat_memory.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "memory.h" @@ -61,8 +62,9 @@ constexpr size_t kAllocationGranularity = 0x10000; // 64 KiB constexpr size_t kHostPageSize = 0x1000; // Only hosts that can expose a page larger than 4 KiB need to discover their -// size at runtime; see RequiresCheckedAccess() in guest_flat_memory.h. -#if !defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE) +// size at runtime, or check it when the build assumes 4 KiB; see +// RequiresCheckedAccess() in guest_flat_memory.h. +#if !defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE) || defined(MKW_GUEST_FLAT_VERIFY_PAGE_SIZE) size_t HostPageSize() { const long size = sysconf(_SC_PAGESIZE); @@ -521,6 +523,13 @@ void Initialize(const std::vector& regions) { #if !defined(MKW_GUEST_FLAT_FIXED_PAGE_SIZE) g_requiresCheckedAccess = HostPageSize() > kGuestPageSize; +#elif defined(MKW_GUEST_FLAT_VERIFY_PAGE_SIZE) + if (HostPageSize() != kGuestPageSize) { + throw std::runtime_error( + "This is a Steam Frame build, which assumes the headset's 4 KiB memory pages, but this " + "kernel uses " + std::to_string(HostPageSize()) + "-byte pages. Build without " + "--headset steam_frame for this device."); + } #endif if (g_initialized) {