android(v7a): armeabi-v7a (32-bit ARM) build + ILP32 + CI#247
Closed
JRickey wants to merge 12 commits into
Closed
Conversation
…BIT option Assessment of building the port for 32-bit x86. The N64 is itself a 32-bit machine, so the port's pointer-heavy infrastructure (Fast3D's uintptr_t Gfx word, the lbReloc token-pointer table) re-collapses to the native N64 layout on ILP32 and is clean by construction. The real cost is a 32-bit build of the whole dependency stack, not the port code. - port/coroutine_posix.cpp: the makecontext pointer split used ptr >> 32 / hi << 32, which is UB when uintptr_t is 4 bytes. This is the core green-thread scheduler, so it is mandatory. Guard with UINTPTR_MAX so it is a no-op on 64-bit and passes the pointer as a single int on ILP32. Compiles clean on both -m32 and -m64. - CMakeLists.txt: add SSB64_32BIT option (-m32 compile/link flags, forces DISABLE_SCRIPTING since funchook/TinyCC have no x86-32 backend). - docs/32bit_x86_feasibility_2026-06-20.md: full write-up, including the caveat that "play on a TV" most likely means an ARM Android TV (existing arm64 port) rather than 32-bit x86 hardware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U53AS38FUpMgojw7TyqAzW
The actual target behind the 32-bit request is Android armeabi-v7a (32-bit ARM), not 32-bit x86. armeabi-v7a is ILP32, so the port's pointer-width infrastructure (Fast3D uintptr_t Gfx word, lbReloc token table) is already clean; the one real blocker was the coroutine backend, which was hardcoded to arm64-v8a (coroutine_android.cpp #error'd on non-aarch64; only an AAPCS64 asm shim existed). - port/coroutine_armv7.S: new AArch32 AAPCS context-switch mirroring coroutine_aarch64.S. Saves r4-r11, sp, lr, and VFP d8-d15 (armeabi-v7a mandates VFPv3-D16). Assembled in ARM state; interworks with Android's default Thumb-2 C via the linker. sp/lr stored via str to avoid the ARMv7-deprecated stm-of-r13 form. - port/coroutine_aarch64.S: body guarded with #if defined(__aarch64__) so it is inert on ARM32. - port/coroutine_android.cpp: multi-arch. Accept __aarch64__ or __arm__; add the ARM32 CoroCtx layout + static_assert offset checks; #if-branch the trampoline decl and the initial-context setup in port_coroutine_create. - CMakeLists.txt: add coroutine_armv7.S beside coroutine_aarch64.S in the Android library target and the optional coroutine_test. Both .S bodies are arch-guarded, so only the file matching ANDROID_ABI emits code. - docs/android_armeabi_v7a_2026-06-20.md: implementation + verification notes. - docs/32bit_x86_feasibility_2026-06-20.md: correct the "N64 is a 32-bit machine" wording (64-bit VR4300 CPU, 32-bit o32 ABI / pointers) and point at the ARM doc. Verified with clang/arm-linux-gnueabihf/qemu-arm: both .S assemble for both arches and are inert on the non-matching one; ARM32 CoroCtx offsets match the asm; and an end-to-end qemu-arm round-trip preserves callee-saved GP and VFP registers across yields for both Thumb and ARM callers. On-device validation on real armeabi-v7a hardware is the remaining step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U53AS38FUpMgojw7TyqAzW
The dev sandbox blocks dl.google.com, so the NDK can't be fetched locally to build/emulate armeabi-v7a. GitHub's runners can, so drive the real-NDK build there instead. - android/app/build.gradle.kts: make the ABI list configurable via the -Pssb64.abis Gradle property (default arm64-v8a, unchanged). Pass -Pssb64.abis=armeabi-v7a to build the 32-bit ARM APK. - .github/workflows/android-v7a.yml: new job that installs NDK r29 and runs `gradlew assembleDebug -Pssb64.abis=armeabi-v7a`, exercising the per-ABI CMake build of the whole dependency stack (libultraship, SDL2, StormLib, …) for 32-bit ARM. Triggers on branch pushes + workflow_dispatch; uploads the APK artifact. - docs/android_armeabi_v7a_2026-06-20.md: document the Gradle property, the CI job, and why local NDK/emulator isn't reachable in the sandbox. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U53AS38FUpMgojw7TyqAzW
First failure from the real-NDK armeabi-v7a CI build: the decomp shim header decomp/include/stdlib.h declares malloc/calloc/realloc with `unsigned long` size params. On LP64 that matches size_t, but on ILP32 size_t is `unsigned int`, so clang's -Werror=incompatible-library-redeclaration fires against the builtin void *(unsigned int). The mismatch is ABI-identical on ARM32 (both 32-bit, same calling convention), so demote that one diagnostic to a warning for CMAKE_SIZEOF_VOID_P EQUAL 4 while keeping it -Werror on LP64. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U53AS38FUpMgojw7TyqAzW
Run #2 reached 429/1205 then hit decomp/src/sys/taskman.c's PORT-only _Static_assert(sizeof(uintptr_t)==8) — a blanket 64-bit guardrail. The fix (allow 4- or 8-byte uintptr_t) is committed on the isolated decomp branch agent/armeabi-v7a-ilp32, but this environment can't push to the ssb-decomp-re fork (proxy + GitHub tools are scoped to BattleShip), so the submodule pointer can't be bumped to it from here. Bridge: apply the same diff (tools/patches/android-v7a-ilp32-decomp.patch) to the checked-out decomp tree in the android-v7a workflow before building. The submodule stays pinned at the existing SHA. Remove the patch step once someone with decomp push access lands the branch and bumps the pointer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U53AS38FUpMgojw7TyqAzW
Run #3 reached 466/1205, then n_env.c's PORT layout asserts failed: the audio structs ALWhatever8009EE0C / ...siz34 hold pointers, so LP64 widened them (asserts at 0x50/0x38/0x48/0x43/0x68/0x70) while ILP32 reverts to the N64-native offsets the field names encode (0x30/0x28/0x34/0x2F/0x44/0x48). Gate the assert set and lbcommon.c's lbCommonMakePositionFGM byte-poke (0x43 vs N64-native 0x2F) on __SIZEOF_POINTER__. n_env.c accesses fields by name, so it's correct on both ABIs. Contained to these two files; the LP64 layout cluster is small. Folded into the isolated decomp branch agent/armeabi-v7a-ilp32 (now one commit, all 3 fixes) and the CI patch tools/patches/android-v7a-ilp32-decomp.patch. The submodule stays pinned; CI applies the patch before building. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U53AS38FUpMgojw7TyqAzW
The decomp ILP32 fixes ride in tools/patches/, but that path was not in the workflow's paths filter, so patch-only commits did not trigger a build. Add tools/patches/** so future decomp-patch updates re-run the v7a build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U53AS38FUpMgojw7TyqAzW
Run #4 reached 1082/1205 (whole game lib compiled) then failed on 7 layout static_asserts in port/enhancements/DisableStageHazards.cpp. The enhancement's mirror structs (GObj/GObjProcess/ITStructPrefix/WPStructPrefix) use real pointer/function-pointer fields, so they auto-size to the game's actual layout on any ABI and field access stays correct — only the hardcoded offset asserts (LP64 values 0x08/0x28/0xE0/0x20/0x25/0x18) are wrong on ILP32. Gate the pointer-affected asserts on __SIZEOF_POINTER__ == 8; the pointer-free SCBattleStatePrefix asserts stay unconditional. Also widen the workflow paths filter from port/coroutine_* to port/** so any port-layer change re-runs the v7a build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U53AS38FUpMgojw7TyqAzW
The armeabi-v7a decomp fixes already live in tools/patches/, but add a README so anyone can apply them to the decomp submodule and land them on the ssb-decomp-re fork without prior context. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U53AS38FUpMgojw7TyqAzW
The "Address review feedback" commit bumped the decomp submodule to a commit that already contains the ILP32 fixes, but left the CI "Apply decomp ILP32 patches" step in place — so CI applied the same diff on top of an already-fixed tree and failed with "patch does not apply" before the build. Reconcile properly: the ILP32 decomp commit is rebased onto port-patches (ssb-decomp-re armeabi-v7a-ilp32 = port-patches + the four sizeof(uintptr_t)- guarded changes), the submodule pointer is bumped to it (9c39f63a0b -> ae12a4c5ac), and the now-redundant CI patch step + tools/patches/ + doc/paths references are removed. This also makes the branch mergeable to main without dropping port-patches (the css/training decomp fixes), since the v7a decomp is now a descendant of main's decomp. LP64 builds are unaffected (guards reduce to the prior offsets). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Validates the armeabi-v7a (32-bit ARM) support on the current branch tip (
66277a40).What this is
coroutine_armv7.S, ILP32 ptr-split fixes incoroutine_android.cpp/coroutine_posix.cpp)SSB64_32BIT(ILP32) CMake option; LP64 offset asserts inDisableStageHazards.cppgated to 64-bittools/patches/android-v7a-ilp32-decomp.patchat CI time (decomp submodulearmeabi-v7a-ilp32@9c39f63a0b)android-armeabi-v7aCI workflow (real NDK r29 on GitHub runners — the dev sandbox can't fetch the NDK)Why this PR
The workflow last passed on 2026-06-21 but on the pre-review-feedback remote tip (
aa1af7cb). Thed41d755b "Address Android v7a review feedback"commit then reworkedCMakeLists.txt,DisableStageHazards.cpp, and the ILP32 decomp patch (110 lines) — none of which has been through CI. This PR runsandroid-armeabi-v7aon the actual mergeable tip.🤖 Generated with Claude Code