Files
nfsmw-online/ostream_repro/ostream_stress.cpp
T
megboyzzandClaude 725ffbd8ed arm64: flat guest mapping, audio, crash reporting, self-extracting data
The run of work that took the translated build from "boots" to "playable".

Engine:
- Flat guest mapping replaces the software MMU on aarch64 hosts. A 4 GiB
  PROT_NONE reservation lets a 32-bit guest address zero-extend safely, so
  tcg_out_qemu_ld/st short-circuit before tcg_out_tlb_read and the prologue
  materialises the base into X28. Measured 1.24x (51 vs 41 fps, interleaved
  A/B). Note the base must be set BEFORE UC_INIT - Unicorn inits lazily, and
  setting it after uc_open silently falls back to softmmu.
- num_get<char> facet implemented, which was the real cause of the crash after
  the prologue race; a full race is now playable end to end.
- Thread-stack free list + ReleaseThreadEngine, fixing the arena leak that
  showed up as a black screen when entering a race. kMaxGuestThreads 16 -> 64.
- Real ARM32 FMOD now runs in-engine via the Java FMODAudioDevice bridge, with
  a per-thread JNIEnv. Two of the three blockers were our own single-image-era
  guards.

Host/app:
- Native crash handler: async-signal-safe, decodes the host fault back to a
  guest address, writes a report file and nothing else. CrashReportActivity
  picks it up on the NEXT launch, zips it, and offers to share. No backend, no
  automatic upload.
- Game data ships inside the APK and self-extracts on first launch, so a tester
  installs one file and plays. Copy-to-.part-then-rename, with a free-space
  check up front.
- EGL context preserved across pause, fixing black textures on resume.
- Navigation bar hidden and re-hidden on focus gain; volume keys reported as
  system keys, checked before the loading-state gate.
- x86_64 added to abiFilters: the ARM32 guest runs under tcg/i386 with no
  houdini in the path. The flat mapping is aarch64-only, so that host falls
  back to the software MMU - commented at the abiFilters line.

Ignore rules added for app/translated/ (611 MB of signed release APK, which
also carries the bundled OBB) and ostream_repro/build/.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-22 23:17:47 +03:00

80 lines
4.0 KiB
C++

// Standalone native ARM32 EXECUTABLE (not a .so, unlike ostream_repro.cpp's
// own SHARED library target - see this directory's CMakeLists.txt) for the
// 2026-09-17 "does real hardware ever see runaway basic_stringbuf::overflow()
// growth" check (ARM64_TRANSLATION_LAYER.md). Built c++_static (same as
// ostream_repro.cpp's own build.sh switched to - real, compiled
// basic_string<char>/basic_stringbuf<char> logic baked directly into this
// binary's own .text, not external imports), meant to run DIRECTLY on the
// Galaxy A9 via `adb shell` - no Unicorn, no GuestEngine, no shims of any
// kind involved. Pure ground truth: does the SAME growth pattern observed
// under GuestEngine (push_back-driven capacity doubling via
// basic_stringbuf<char>::overflow(), real disasm at sub_2700E4/sub_27036C/
// sub_27003C) ever get stuck, or does it complete normally no matter how far
// it's pushed?
#include <cstdio>
#include <cstdint>
#include <sstream>
#include <string>
#include <chrono>
using Clock = std::chrono::steady_clock;
int main(int argc, char** argv) {
// Default: push well past the ~8MB (0x800000) point where GuestEngine's
// own trace showed the crash - if real hardware sails through this same
// magnitude with no trouble, that's strong evidence the growth mechanism
// itself is fine and the bug is specific to GuestEngine (its own malloc
// shim, or something else in its translation of these particular
// instructions), not a genuine bug in the shipped game/libc++ pairing.
long targetChars = (argc > 1) ? atol(argv[1]) : 20L * 1024 * 1024; // 20M
printf("ostream_stress: appending %ld chars one at a time via push_back\n", targetChars);
fflush(stdout);
auto t0 = Clock::now();
std::string s;
for (long i = 0; i < targetChars; i++) {
s.push_back((char)('a' + (i % 26)));
// Progress heartbeat every 1M chars, and explicitly flag whenever
// capacity crosses the same doubling milestones GuestEngine's own
// register trace captured (0xfffff, 0x1fffff, 0x3fffff, 0x7fffff) -
// lets a hang be diagnosed by "last milestone reached" even if the
// process needs to be killed rather than exiting cleanly.
if (i != 0 && (i % (1L * 1024 * 1024)) == 0) {
auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0).count();
printf(" [%ldms] i=%ld cap=%zu size=%zu\n", (long)elapsedMs, i, s.capacity(), s.size());
fflush(stdout);
}
}
auto totalMs = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t0).count();
printf("ostream_stress: push_back loop done in %ldms - final size=%zu cap=%zu\n",
(long)totalMs, s.size(), s.capacity());
fflush(stdout);
// Second phase: the ACTUAL crashing pattern is via basic_stringbuf's own
// xsputn/overflow chain (operator<<), not raw std::string::push_back -
// exercise that path too, via repeated small ostringstream writes,
// mirroring sub_4702D8's own "several separate writes before one
// extraction" shape but looped enough times to force the same magnitude
// of reallocation.
printf("ostream_stress: now stress-testing via ostringstream operator<<\n");
fflush(stdout);
auto t1 = Clock::now();
std::ostringstream oss;
long chunkCount = targetChars / 16; // ~16 chars per write, same order of magnitude
for (long i = 0; i < chunkCount; i++) {
oss << "0123456789ABCDEF";
if (i != 0 && (i % (65536)) == 0) {
auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t1).count();
std::string cur = oss.str();
printf(" [oss %ldms] i=%ld size-so-far=%zu\n", (long)elapsedMs, i, cur.size());
fflush(stdout);
}
}
std::string result = oss.str();
auto ossMs = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - t1).count();
printf("ostream_stress: ostringstream loop done in %ldms - final size=%zu\n",
(long)ossMs, result.size());
fflush(stdout);
return 0;
}