Skip to content

build: drop V8 pointer compression from the iOS lite flavor - #8

Merged
gmaclennan merged 1 commit into
recipefrom
fix/ios-drop-pointer-compression
Aug 22, 2026
Merged

build: drop V8 pointer compression from the iOS lite flavor#8
gmaclennan merged 1 commit into
recipefrom
fix/ios-drop-pointer-compression

Conversation

@gmaclennan

Copy link
Copy Markdown
Member

24.19.0-1 turned on --experimental-enable-pointer-compression for both lite targets. Android is fine. iOS lite cannot start at all — the app aborts about a second into launch, on every physical device, before any embedder JS runs. This drops the flag on iOS only; Android lite is untouched.

Why it cannot work on iOS

V8 needs the cage to be 4 GB-aligned as well as 4 GB long, and it gets the alignment by over-reserving and trimming. OS::Allocate (deps/v8/src/base/platform/platform-posix.cc) requests size + (alignment - page_size) — a single PROT_NONE mmap of just under 8 GB — then unmaps the misaligned head and tail.

An iOS process does not have 8 GB of address space to give. Without the com.apple.developer.kernel.extended-virtual-addressing entitlement the kernel caps a process at 7.375 GB usable on devices with more than 3 GB of RAM, and less below that, after PAGE_ZERO and the shared region are subtracted (the arithmetic, with the kernel constants). The request is larger than the entire address space of the process, so it fails on every device — not marginally, and not only on small ones. VirtualMemoryCage::InitReservation returns false and IsolateGroup::Initialize calls FatalProcessOutOfMemory(... "Failed to reserve virtual memory for process-wide V8 pointer compression cage"), which aborts during Isolate init.

The entitlement would lift the cap, but a runtime library cannot require it: it is a restricted capability every consuming app would have to carry in its own provisioning profile, and it would still spend 4 GB of address space per isolate. Shipping iOS lite uncompressed is the cheaper trade.

I ruled out the two other candidates. Jitless means no CodeRange is ever created (Isolate::RequiresCodeRange() is kPlatformRequiresCodeRange && !jitless_), so the missing JIT entitlement is not involved; and v8_enable_sandbox is 0 in both builds, so the sandbox reservation is not either. Diffing the process.config embedded in the shipped ios-arm64 slices of 24.19.0-0 and 24.19.0-1, the only delta is pointer compression and its two implied knobs (v8_enable_31bit_smis_on_64bit_arch, v8_enable_external_code_space).

How it showed up

Downstream, in comapeo-core-react-native's BrowserStack e2e suite. Every flow failed at the first assertion on both an iPhone 15 (iOS 17.3) and an iPhone 17 (iOS 26.5), across three merge-queue attempts and their internal retries; BrowserStack reports app_crash: true and the device log shows the kernel taking a corpse a second after launch. Android e2e passed on the same commits.

Why CI was green

The lite flavor never runs on a physical iPhone. real-device-smoke-ios fetches the nodejs-mobile-ios artifact — the full flavor — and the lite legs (curated-tests-ios) are simulator-only. A simulator is a macOS process with no such cap, so it exercises none of this and passes on a binary that cannot start on any device.

Worth fixing separately: pointing the iOS device smoke at the lite artifact, or adding a lite leg, would have caught this at #6. I have not bundled that here to keep the revert reviewable.

Changes

tools/ios_framework_prepare.sh drops the flag from LITE_FLAGS. BUILDING.md rescopes the pointer-compression section to Android and documents the iOS constraint (including that iOS simulator green is no evidence about address-space behaviour). The FAQ.md addon note — add -DV8_COMPRESS_POINTERS -DV8_31BIT_SMIS_ON_64BIT_ARCH — now applies to Android lite alone; iOS lite is back on the upstream-standard V8 ABI. expected-tree.txt re-anchored to 7c521dbc, verified by a clean scripts/prepare.sh run.

24.19.0-1 turned on --experimental-enable-pointer-compression for both
lite targets. Android is fine; iOS lite cannot start at all, on any
physical device.

V8 needs the 4GB cage to be 4GB-aligned as well as 4GB long, and gets the
alignment by over-reserving and trimming: OS::Allocate requests
size + (alignment - page_size), one PROT_NONE mmap of just under 8GB.
An iOS process without the extended-virtual-addressing entitlement is
capped well below that -- 7.375GB of usable address space on devices with
more than 3GB of RAM, less below that, once PAGE_ZERO and the shared
region are subtracted. The reservation is larger than the whole address
space of the process, so InitReservation fails and IsolateGroup::Initialize
calls FatalProcessOutOfMemory during Isolate init. The app aborts about a
second into launch, before any embedder JS runs.

The entitlement would lift the cap, but a runtime library cannot require
it: it is a restricted capability each consuming app has to carry in its
own provisioning profile, and it would still spend 4GB of address space
per isolate.

Nothing caught this because the flavor never runs on a physical iPhone:
real-device-smoke-ios fetches the nodejs-mobile-ios artifact, which is
the full flavor, and the lite legs are simulator-only. A simulator is a
macOS process with no such cap, so it exercises none of this.

iOS lite therefore goes back to the upstream-standard V8 ABI, and the
-DV8_COMPRESS_POINTERS build note for addons now applies to Android lite
alone. Android lite is untouched.
@gmaclennan
gmaclennan merged commit afb5146 into recipe Aug 22, 2026
34 checks passed
@gmaclennan
gmaclennan deleted the fix/ios-drop-pointer-compression branch August 22, 2026 22:16
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