Find why the softmmu lookup is hot: memory_mapping runs on every notdirty write
Task #53 ruled out TLB capacity as the cause of flatview_translate 4.46% + find_memory_mapping 3.02%. A probe on notdirty_write's own first line found the real reason, and it is a miss in this project's own earlier fix. notdirty_write calls uc->memory_mapping() UNCONDITIONALLY, before testing whether the region is executable - and that result is used only to test one bit. Measured on device, per 3-second window: hits=3478174 exec_region=1 (startup, all in the 0-1MB bucket) hits=1184522 exec_region=0 hits=902501 exec_region=0 (10MB and 11MB buckets = .data/.bss) Over a million calls per 3 seconds where the lookup ran in full and the answer then did not matter. Task #54 removed the tb_invalidate work for non-executable spans but left this lookup standing in front of it, so most of the cost survived that fix. The map is static once CreateConfiguredEngine has mapped its five spans, so a small per-thread direct-mapped page -> MemoryRegion cache removes the lookup for everything but a cold miss. That cache is implemented here but DEFAULTS OFF (UC_MR_CACHE_ENABLED 0) because it could not be measured: - the Pixel 6a stopped being measurable (mScreenState=OFF within seconds despite a 30-minute timeout and svc power stayon true, with NotificationShade holding focus - it needs physical attention) - the Galaxy A9 cannot run this build at all: "failed to attach / start timeout", the engine's startup work exceeds Android's attach deadline on that hardware Shipping it enabled would repeat exactly the mistake this project has been correcting all session. The measurement that motivates it is solid; the fix is not yet evidence. INVARIANT if it is ever enabled: anyone changing mappings or permissions at runtime must clear the cache, or stale permissions will be used. It is per-thread, so no locking is involved. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#define UC_MR_CACHE_ENTRIES 512
|
||||
#define UC_MR_CACHE_ENABLED 0
|
||||
#include "cpu.h"
|
||||
#include "exec/exec-all.h"
|
||||
#include "exec/memory.h"
|
||||
@@ -1194,7 +1196,63 @@ static void notdirty_write(CPUState *cpu, vaddr mem_vaddr, unsigned size,
|
||||
struct uc_struct *uc = cpu->uc;
|
||||
#endif
|
||||
ram_addr_t ram_addr = mem_vaddr + iotlbentry->addr;
|
||||
MemoryRegion *mr = cpu->uc->memory_mapping(cpu->uc, tlbe->paddr | (mem_vaddr & ~TARGET_PAGE_MASK));
|
||||
|
||||
// PROJECT PATCH (2026-09-20, NFSMW arm64-poc). memory_mapping() -
|
||||
// find_memory_mapping -> address_space_translate -> flatview_translate -
|
||||
// was called here on EVERY notdirty write, unconditionally, even though
|
||||
// its result is used only to test one bit (is this region executable).
|
||||
// Measured: flatview_translate 4.46% + find_memory_mapping 3.02% of a
|
||||
// clean in-game profile, and a probe on this exact line counted over a
|
||||
// million calls per 3 seconds with exec_region=0 - i.e. the lookup ran in
|
||||
// full and then the answer turned out not to matter.
|
||||
//
|
||||
// Task #54 removed the tb_invalidate work for non-executable spans but
|
||||
// left this lookup in front of it, so most of the cost survived. TLB
|
||||
// capacity was separately ruled out as the cause (task #53).
|
||||
//
|
||||
// The map is static here: GuestEngine maps its five spans once in
|
||||
// CreateConfiguredEngine and never calls uc_mem_map/protect again once
|
||||
// guest code is running (guard pages and RELRO are set during that same
|
||||
// setup). So the page -> MemoryRegion answer cannot change, and a small
|
||||
// direct-mapped cache removes the lookup for everything but a cold miss.
|
||||
//
|
||||
// INVARIANT THIS RELIES ON: anyone who starts changing mappings or
|
||||
// permissions at RUNTIME must clear this cache, or stale permissions will
|
||||
// be used. It is per-thread, so no locking is needed.
|
||||
//
|
||||
// NOT YET VERIFIED. Written and built, but the Pixel 6a became
|
||||
// unmeasurable (screen dozing within seconds, NotificationShade holding
|
||||
// focus) and the A9 cannot run this build at all - Android kills it with
|
||||
// "failed to attach / start timeout" because startup work exceeds the
|
||||
// attach deadline on that hardware. Defaults OFF so an unmeasured change
|
||||
// does not reach the hot path. To evaluate: set to 1, then interleaved
|
||||
// A/B with loadtime (engine start -> first OnCarLoaded) plus a profile
|
||||
// check that flatview_translate and find_memory_mapping actually fall.
|
||||
// A profile-share drop alone is NOT a speedup - that has been established
|
||||
// three separate times in this project.
|
||||
MemoryRegion *mr;
|
||||
if (!UC_MR_CACHE_ENABLED) {
|
||||
mr = cpu->uc->memory_mapping(cpu->uc, tlbe->paddr | (mem_vaddr & ~TARGET_PAGE_MASK));
|
||||
} else {
|
||||
target_ulong page = tlbe->paddr | (mem_vaddr & ~TARGET_PAGE_MASK);
|
||||
target_ulong tag = page >> TARGET_PAGE_BITS;
|
||||
unsigned idx = (unsigned)(tag & (UC_MR_CACHE_ENTRIES - 1));
|
||||
static __thread target_ulong mr_cache_tag[UC_MR_CACHE_ENTRIES];
|
||||
static __thread MemoryRegion *mr_cache_val[UC_MR_CACHE_ENTRIES];
|
||||
static __thread int mr_cache_ready;
|
||||
if (!mr_cache_ready) {
|
||||
for (unsigned i = 0; i < UC_MR_CACHE_ENTRIES; i++) mr_cache_tag[i] = (target_ulong)-1;
|
||||
mr_cache_ready = 1;
|
||||
}
|
||||
if (mr_cache_tag[idx] == tag) {
|
||||
mr = mr_cache_val[idx];
|
||||
} else {
|
||||
mr = cpu->uc->memory_mapping(cpu->uc, page);
|
||||
mr_cache_tag[idx] = tag;
|
||||
mr_cache_val[idx] = mr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (mr && (mr->perms & UC_PROT_EXEC) != 0) {
|
||||
struct page_collection *pages
|
||||
|
||||
Reference in New Issue
Block a user