Skip to content

feat(watchos): arm64_32 device support (Apple Watch Series 4-8 / SE)#5060

Merged
proggeramlug merged 2 commits into
mainfrom
investigate/arm64_32-watch
Jun 13, 2026
Merged

feat(watchos): arm64_32 device support (Apple Watch Series 4-8 / SE)#5060
proggeramlug merged 2 commits into
mainfrom
investigate/arm64_32-watch

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Why

Real Apple Watches before the Series 9 use the arm64_32 architecture (64-bit ISA, 32-bit pointers). Perry has only ever produced arm64 watch binaries (S9+/watchOS 26), and an internal note claimed arm64_32 was architecturally impossible because NaN-boxing needs a 64-bit usize.

That's overstated. A 32-bit pointer fits trivially in the 48-bit NaN payload; only a few heap-range guard heuristics and literal constants assumed a 64-bit address space. With those handled, the whole stack builds and links for arm64_32.

What

Runtime (32-bit literal fixes — also fix wasm32):

  • array/from_concat: (1usize << 53) saturates via try_from(...).unwrap_or(usize::MAX) on 32-bit.
  • box.rs, builtins/formatting.rs, value/dynamic_object.rs: the 2^47 / 2^48 pointer-guard upper bounds are compared in u64 so the literal isn't out-of-range for 32-bit usize (a no-op on 32-bit — no address reaches that high).
  • The heap-min floor guards already select 0x1000 on target_os = "watchos", so no value changes were needed there.

Compile driver (opt-in via PERRY_WATCHOS_ARM64_32, leaving the default watchos=arm64/S9+ path untouched):

  • Codegen emits the C entry directly via PERRY_ENTRY_SYMBOL (e.g. _perry_user_main) instead of renaming _main with rust-objcopy after the fact — objcopy's MachOWriter segfaults on arm64_32 Mach-O (both rustup- and Homebrew-shipped llvm-objcopy). The link-time objcopy rename is skipped on arm64_32.
  • helpers.rs / app_metadata.rs / link/mod.rs / platform_cmd.rs / bundle_apple.rs resolve the arm64_32-apple-watchos triple consistently (codegen object arch, auto-optimize runtime rebuild, native-lib build, swiftc, final link) and a low MinimumOSVersion (PERRY_WATCHOS_MIN, default 11.0).

Validation

Built a real game (Bloom Jump) end to end:

  • perry-runtime + perry-ui-watchos + the engine native lib all compile for arm64_32-apple-watchos (nightly -Z build-std).
  • The game links to a Mach-O architecture: arm64_32, platform WATCHOS, minos 11.0 with __perry_user_main and _main both defined and no undefined refs.
  • Host (aarch64) perry-runtime still builds clean — the runtime changes are no-ops on 64-bit.

Note: arm64_32 binaries cannot run on the watchOS simulator (the sim is arm64), so on-device validation requires real pre-S9 hardware; that test is pending.

Notes / follow-ups

  • The opt-in is env-gated to keep the diff small and the arm64 path risk-free. A dedicated watchos-arm64_32 target name (or a fat arm64+arm64_32 build, which would let a single App Store upload cover Series 4 through 11 and drop the watchOS-27 minimum) is a natural follow-up — happy to take direction.
  • arm64_32-apple-watchos is tier-3: runtime libs need cargo +nightly build -Z build-std. Wiring it into release-packages.yml would let PERRY_RUNTIME_DIR users skip the local build.

Ralph Küpper added 2 commits June 13, 2026 07:49
Real pre-S9 Apple Watches use the arm64_32 architecture (64-bit ISA,
32-bit pointers). Perry never produced a working binary for them, and an
old note claimed it was architecturally impossible (NaN-boxing needs
64-bit usize). That's overstated: a 32-bit pointer fits trivially in the
48-bit NaN payload — only a few heap-range guard heuristics and literal
constants assumed a 64-bit address space.

Runtime (32-bit literal fixes; also benefit wasm32):
- array/from_concat: (1usize << 53) saturates via try_from on 32-bit
- box.rs / formatting.rs / value/dynamic_object.rs: 2^47/2^48 pointer-
  guard upper bounds compared in u64 so the literal isn't out-of-range
  for 32-bit usize (no-op on 32-bit — no addresses reach that high)
The heap-min *value* guards already pick the low (0x1000) floor on
target_os=watchos, so no value changes were needed there.

Compile driver (opt-in via PERRY_WATCHOS_ARM64_32, so the default
watchos=arm64/S9+ path is untouched):
- codegen emits the C entry via PERRY_ENTRY_SYMBOL (e.g. _perry_user_main)
  instead of renaming _main with rust-objcopy afterwards — objcopy's
  MachOWriter segfaults on arm64_32 Mach-O. The link-time objcopy rename
  is skipped on arm64_32 accordingly.
- helpers.rs / app_metadata.rs / link/mod.rs / platform_cmd.rs /
  bundle_apple.rs resolve the arm64_32-apple-watchos triple (codegen,
  auto-optimize runtime rebuild, native-lib build, swiftc, link) and a
  low MinimumOSVersion (PERRY_WATCHOS_MIN, default 11.0).

Validated end to end: built a real game (Bloom Jump) — perry-runtime +
perry-ui-watchos + engine native lib all compile for arm64_32-apple-
watchos; the game links to a Mach-O 'architecture: arm64_32, platform
WATCHOS, minos 11.0' with __perry_user_main and _main both defined and
no undefined refs. (Testable only on real pre-S9 hardware — the
simulator is arm64.)

Co-authored from a parked WIP exploring this target.
@proggeramlug proggeramlug merged commit 3b460cd into main Jun 13, 2026
13 checks passed
@proggeramlug proggeramlug deleted the investigate/arm64_32-watch branch June 13, 2026 07:11
proggeramlug added a commit that referenced this pull request Jun 13, 2026
…#5063)

The watchOS platform page predated arm64 device support (#5060) and
claimed `--target watchos` produces arm64_32. Correct and expand it:

- Architecture matrix: watchos = arm64 (S9+/watchOS 26) by default,
  arm64_32 (Series 4-8/SE) via PERRY_WATCHOS_ARM64_32; sim is arm64-only.
- Device builds are tier-3: build perry-runtime/-ui-watchos from source
  with nightly -Z build-std + PERRY_RUNTIME_DIR (rustup target add only
  covers the simulator).
- Build env vars (PERRY_WATCHOS_ARM64_32 / _MIN / PERRY_ENTRY_SYMBOL) and
  the rust-objcopy-crashes-on-arm64_32 workaround.
- App rendering modes (default UI tree vs watchos-swift-app / -game-loop).
- Contributor note: 32-bit usize portability in perry-runtime.

New page platforms/watchos-app-store.md: the fat arm64+arm64_32 binary,
the required iOS-stub wrapper for watch-only apps, the WKWatchOnly /
ITSWatchOnlyContainer / arch+minOS validation rules, the arm64-only/
minOS-27 processing stall, signing, Transporter upload, and devicectl
development install (Developer Mode + DDI).

Co-authored-by: Ralph Küpper <ralph@skelpo.com>
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.

1 participant