Verified live on a Pixel 6a: the game passes its EULA, loads the prologue and renders real 3D gameplay, with zero heap exhaustion, zero faults and zero rejected frees over a full session. Root causes fixed in this state, each backed by a measurement (details and the list of refuted theories live in ARM64_TRANSLATION_LAYER.md): * JNI varargs float->double promotion. C promotes float to double in any varargs call and every Call*Method form is varargs, so reading one 4-byte slot yielded the double's always-zero low half. EVERY float argument passed to Java was silently becoming 0; text was just where it showed. * GuestHeap ~4x memory overhead. Power-of-two size classes carving the full class, plus segregated free lists that could never share memory between sizes. Reworked to exact sizing with O(log n) best-fit reuse and splitting (deliberately not a linear scan - this allocator already had an O(n) perf cliff in its history). Peak live now 207MB against the real A9's 199MB, fragmentation ~2.5MB. Also fixed: realloc reading past the old block on shrink, a 32-bit overflow in calloc, and drifting payload alignment. * Unbounded FMOD fake-handle leak into the never-freeing permanent arena, which is why enlarging that arena had not helped. * Frame presentation, corrected against A9 ground truth: the real frame has three default-framebuffer colour clears and ONE present at the end; this engine had been presenting on each of them. Load-time acceleration (zlib_accel.cpp): host zlib now serves inflate and crc32, the latter measured by the block profiler as the single hottest guest routine at 17.7%. Streams are only taken over when this layer saw their own inflateInit2_, so unknown streams (libpng's, among others) still run the original emulated path. name_lookup_accel.cpp is present but its hook is NOT registered - it crashed on bad assumptions about guest table lifetime and is kept as a starting point, with both mistakes recorded in its comments. Co-Authored-By: Claude <noreply@anthropic.com>
68 lines
2.4 KiB
C++
68 lines
2.4 KiB
C++
#include "profiler.h"
|
|
#include "../util/util.h"
|
|
#include <atomic>
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
|
|
std::atomic<bool> g_profilingEnabled{false};
|
|
std::mutex g_histMutex;
|
|
std::unordered_map<uint64_t, uint64_t> g_hist;
|
|
std::atomic<uint64_t> g_totalSamples{0};
|
|
std::atomic<bool> g_dumpThreadStarted{false};
|
|
|
|
} // namespace
|
|
|
|
void EnableProfiling() { g_profilingEnabled.store(true, std::memory_order_relaxed); }
|
|
bool ProfilingEnabled() { return g_profilingEnabled.load(std::memory_order_relaxed); }
|
|
|
|
void ProfileBlockHookCb(uc_engine*, uint64_t address, uint32_t, void*) {
|
|
// thread_local, not a shared "last sample" - each real host thread has
|
|
// its own uc_engine (see guest_engine.h's "Multithreading model") and
|
|
// fires this independently; gating per-thread avoids one busy thread's
|
|
// sampling starving another's.
|
|
static thread_local std::chrono::steady_clock::time_point lastSample{};
|
|
auto now = std::chrono::steady_clock::now();
|
|
if (now - lastSample < std::chrono::milliseconds(2)) return;
|
|
lastSample = now;
|
|
|
|
std::lock_guard<std::mutex> lock(g_histMutex);
|
|
g_hist[address]++;
|
|
g_totalSamples.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
|
|
void StartProfileDumpThread() {
|
|
bool expected = false;
|
|
if (!g_dumpThreadStarted.compare_exchange_strong(expected, true)) return; // already running
|
|
|
|
std::thread([]() {
|
|
while (true) {
|
|
std::this_thread::sleep_for(std::chrono::seconds(3));
|
|
|
|
std::vector<std::pair<uint64_t, uint64_t>> top;
|
|
{
|
|
std::lock_guard<std::mutex> lock(g_histMutex);
|
|
top.assign(g_hist.begin(), g_hist.end());
|
|
}
|
|
if (top.empty()) continue;
|
|
|
|
std::sort(top.begin(), top.end(),
|
|
[](const auto& a, const auto& b) { return a.second > b.second; });
|
|
uint64_t total = g_totalSamples.load(std::memory_order_relaxed);
|
|
|
|
Log("PROFILE: %llu total samples across %zu distinct block addresses - top hot PCs:",
|
|
(unsigned long long)total, top.size());
|
|
for (size_t i = 0; i < top.size() && i < 15; i++) {
|
|
Log("PROFILE: 0x%llx - %llu samples (%.1f%%)",
|
|
(unsigned long long)top[i].first, (unsigned long long)top[i].second,
|
|
total ? 100.0 * (double)top[i].second / (double)total : 0.0);
|
|
}
|
|
}
|
|
}).detach();
|
|
}
|