Skip to content

android(v7a): armeabi-v7a (32-bit ARM) build + ILP32 + CI#247

Closed
JRickey wants to merge 12 commits into
mainfrom
codex/fix-android-v7a-review
Closed

android(v7a): armeabi-v7a (32-bit ARM) build + ILP32 + CI#247
JRickey wants to merge 12 commits into
mainfrom
codex/fix-android-v7a-review

Conversation

@JRickey

@JRickey JRickey commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Validates the armeabi-v7a (32-bit ARM) support on the current branch tip (66277a40).

What this is

  • 32-bit ARM coroutine backend (coroutine_armv7.S, ILP32 ptr-split fixes in coroutine_android.cpp / coroutine_posix.cpp)
  • SSB64_32BIT (ILP32) CMake option; LP64 offset asserts in DisableStageHazards.cpp gated to 64-bit
  • ILP32 decomp fixes applied via tools/patches/android-v7a-ilp32-decomp.patch at CI time (decomp submodule armeabi-v7a-ilp32 @ 9c39f63a0b)
  • android-armeabi-v7a CI 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). The d41d755b "Address Android v7a review feedback" commit then reworked CMakeLists.txt, DisableStageHazards.cpp, and the ILP32 decomp patch (110 lines) — none of which has been through CI. This PR runs android-armeabi-v7a on the actual mergeable tip.

Note: the branch's decomp line (9c39f63a0b, ILP32 off 68755588f) diverges from main's port-patches (76a034d8b); this branch needs a decomp-line reconciliation before it can merge to main. This PR is for CI validation of the v7a build.

🤖 Generated with Claude Code

claude and others added 12 commits June 24, 2026 00:01
…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>
@JRickey JRickey closed this Jul 6, 2026
@JRickey JRickey deleted the codex/fix-android-v7a-review branch July 6, 2026 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants