diff --git a/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp b/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp index 34834673..1b54326a 100644 --- a/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp +++ b/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp @@ -274,10 +274,17 @@ namespace MobileGL::MG_Remote::Client { // their blocks are pushed unconditionally at every push (at most two). const uintptr_t rawBegin = reinterpret_cast(shadow + rangeBegin); const uintptr_t rawEnd = reinterpret_cast(shadow + rangeEnd); - const uintptr_t base = (rawBegin + kPageBytes - 1) & - ~static_cast(kPageBytes - 1); - const uintptr_t end = rawEnd & ~static_cast(kPageBytes - 1); - if (end <= base) return false; // sub-page range: the hash arm has it + uintptr_t base = (rawBegin + kPageBytes - 1) & + ~static_cast(kPageBytes - 1); + uintptr_t end = rawEnd & ~static_cast(kPageBytes - 1); + // A SUB-PAGE range is not refused; it is registered with an EMPTY interior + // (no pages are protected, no bitmap is needed) and hasEdges set, so the + // whole range is served by the two edge hashes - bounded at 8 KB per push - + // instead of falling to the hash arm, where as an "untracked" member it + // would veto the epoch skip for every tracked buffer in the process and + // force the full binding walk at every draw (measured on device). end is + // clamped up to base so the [base, end) arithmetic below never underflows. + if (end < base) end = base; const SizeT pageCount = (end - base) >> kPageShift; if (pageCount > kMaxTrackedPages) { MGLOG_W("MGPipe: persistent write map of %zu pages exceeds the mprotect " @@ -295,7 +302,7 @@ namespace MobileGL::MG_Remote::Client { } // Claimed. From here until the final base store the slot reads as // (base=1, end=0), which the handler skips on both tests. - if (g_trackedMaps[i].pageBits == nullptr) { + if (pageCount > 0 && g_trackedMaps[i].pageBits == nullptr) { g_trackedMaps[i].pageBits = new std::atomic[kPageBitsWords]; } g_trackedMaps[i].pageCount = pageCount; @@ -321,7 +328,8 @@ namespace MobileGL::MG_Remote::Client { bitsInWord == 64 ? ~0ull : ((1ull << bitsInWord) - 1), std::memory_order_relaxed); } - if (mprotect(reinterpret_cast(base), end - base, PROT_READ) != 0) { + if (end > base && + mprotect(reinterpret_cast(base), end - base, PROT_READ) != 0) { g_trackedMaps[i].base.store(0, std::memory_order_release); return false; } @@ -353,7 +361,9 @@ namespace MobileGL::MG_Remote::Client { g_trackedMaps[i].base.store(0, std::memory_order_release); g_trackedMaps[i].end.store(0, std::memory_order_release); g_trackedMaps[i].lifetimeId = 0; - mprotect(reinterpret_cast(base), end - base, PROT_READ | PROT_WRITE); + if (end > base) { + mprotect(reinterpret_cast(base), end - base, PROT_READ | PROT_WRITE); + } return; } } @@ -369,7 +379,9 @@ namespace MobileGL::MG_Remote::Client { g_trackedMaps[i].base.store(0, std::memory_order_release); g_trackedMaps[i].end.store(0, std::memory_order_release); g_trackedMaps[i].lifetimeId = 0; - mprotect(reinterpret_cast(base), end - base, PROT_READ | PROT_WRITE); + if (end > base) { + mprotect(reinterpret_cast(base), end - base, PROT_READ | PROT_WRITE); + } } } diff --git a/MobileGL/MG_State/GLState/BufferState/PipeResource.h b/MobileGL/MG_State/GLState/BufferState/PipeResource.h index e0d8ca96..3bd6f1a0 100644 --- a/MobileGL/MG_State/GLState/BufferState/PipeResource.h +++ b/MobileGL/MG_State/GLState/BufferState/PipeResource.h @@ -27,7 +27,18 @@ namespace MobileGL::MG_State::GLState { // MG_Util/ShaderTranspiler/Types.h. inline constexpr SizeT MIN_MAP_BUFFER_ALIGNMENT = 64; - // Allocator that gives every allocation MIN_MAP_BUFFER_ALIGNMENT. Deliberately minimal: the + // The shadow's ALLOCATION alignment is a full page, not the advertised 64: a + // page-aligned shadow owns every byte of every page it occupies, which is what lets + // the persistent-map mprotect tracker write-protect the mapped interior without ever + // covering a neighbour allocation's byte (a foreign thread with signals blocked + // faulting on a shared page is a process kill). GL_MIN_MAP_BUFFER_ALIGNMENT keeps + // answering 64 - over-aligning an allocation is invisible to the application, and a + // glMapBufferRange base that is page-aligned is 64-aligned. The cost is allocator + // rounding on small buffers, paid so the tracker never has to make the unsafe choice. + inline constexpr SizeT SHADOW_ALLOCATION_ALIGNMENT = 4096; + + // Allocator that gives every allocation SHADOW_ALLOCATION_ALIGNMENT. Deliberately + // minimal: the // vectors it backs hold raw bytes and are only ever sized, so allocate/deallocate plus the // rebinding and equality boilerplate std::vector requires is the whole interface. template @@ -41,10 +52,10 @@ namespace MobileGL::MG_State::GLState { T* allocate(SizeT count) { if (count == 0) return nullptr; return static_cast( - ::operator new(count * sizeof(T), std::align_val_t{MIN_MAP_BUFFER_ALIGNMENT})); + ::operator new(count * sizeof(T), std::align_val_t{SHADOW_ALLOCATION_ALIGNMENT})); } void deallocate(T* pointer, SizeT) noexcept { - ::operator delete(pointer, std::align_val_t{MIN_MAP_BUFFER_ALIGNMENT}); + ::operator delete(pointer, std::align_val_t{SHADOW_ALLOCATION_ALIGNMENT}); } template