2 Commits
Author SHA1 Message Date
megboyzzandClaude 7af48860eb Survey dynamic_cast call shapes + native rate counter: 84% need no hierarchy walk
Task #58 asked whether the engine induces the __dynamic_cast flood or the
game does. A native LD_PRELOAD interposer added to trace_agent answers it:
native ARM32 on the A9 calls it up to 824,200/sec, against this engine's
284,986/sec. The game is simply that RTTI-heavy, and native absorbs it
because a real call costs tens of nanoseconds. Our rate is not demand, it is
supply - the shim boundary is throttling the guest.

(Interposing __dynamic_cast collides with libc++abi.a, which the NDK links
statically into the agent; resolved with --allow-multiple-definition scoped
to that target, since our object precedes the archive and wins.)

That makes a guest-side implementation interesting: Shim_dynamic_cast is
already a pure guest-memory walk - it reads the object's vtable, vtable[-1]
(dynamic type) and vtable[-2] (offset-to-top), then compares type_info
records. Nothing comes from the host, so the same algorithm could run as
emulated ARM32 with NO boundary crossing at all.

Whether that is worth doing depends on how big it would have to be, so this
adds a temporary shape survey. Measured over a prologue load, 2.7M calls:

    exact (depth 0)  84.2%
    one base         0.4%
    deeper           0.0%     (max depth reached all run: 3)
    not found       15.4%

84% of calls need NO hierarchy walk - the object's dynamic type already IS
the target. A guest fast path of roughly six instructions (load vtable,
compare vtable[-1] to the target, return ptr + vtable[-2]) with a fallback
to this shim would eliminate 84% of dynamic_cast crossings, which is ~41% of
ALL shim crossings given dynamic_cast is 49% of them.

Also confirms there is no implementation to reuse inside libapp.so: not only
__dynamic_cast but the type_info vtables themselves (__class_type_info,
__si_class_type_info, __vmi_class_type_info, __pointer_type_info) are all
UND - this engine supplies them from rtti_shims.cpp.

Survey left in the tree behind kSurveyDynamicCast, default off.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-19 20:28:01 +03:00
megboyzzandClaude d5e6037fc7 Stable checkpoint: game reaches playable 3D gameplay on ARM64
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>
2026-09-19 02:12:03 +03:00