Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,20 @@ platform), dispatch the workflow with `platforms: android` or `platforms: ios`.
|---|---|
| `v8_resurrecting_finalizers.patch` | Restores `WeakCallbackType::kFinalizer`, removed upstream immediately after 10.3.22. Both runtimes' object managers depend on resurrecting finalizers. It also lifts, for the finalizer drain only, the `DisallowJavascriptExecution` scope that `Heap::CollectGarbage` now holds across the whole collection: 14.9 turns entering JS from a GC callback into a `GRACEFUL_FATAL`, and the iOS runtime reaches JS whenever a released ObjC object's `-dealloc` hits a JS-backed override. |
| `android_build.patch` | Lowers chromium's minimum SDK to 21 (that floor is for Java/dex tooling; this builds only the native target), makes `android_ndk_root` a gn arg, and widens the hard Linux-host assert so macOS can build the 64-bit ABIs. |
| `v8_catalyst_jit_write_protect.patch` | Mac Catalyst compiles as iOS (`TARGET_OS_IPHONE`), so V8 picks its iOS JIT-memory model, which assumes `MAP_JIT` pages are writable and executable at once. A Catalyst process runs on macOS, where Apple silicon makes `MAP_JIT` pages per-thread W^X, so the first JIT code write faults (`SIGBUS`). The patch gives Catalyst the macOS model: `pthread_jit_write_protect_np` around code writes (resolved with `dlsym`, since the SDK marks it unavailable on Catalyst) and the macOS code-range pre-commit. Applied to iOS fetches only; it is a no-op outside Catalyst. |

## The gn args are load-bearing

They are commented at their definition in `scripts/matrix/build-android.sh` and
`scripts/matrix/build-ios.sh`. Three worth knowing:
`scripts/matrix/build-ios.sh`. Four worth knowing:

- `v8_enable_lite_mode=false` (Catalyst only) — `gni/v8.gni` turns lite mode
on for every `target_platform="iphoneos"` build, and Catalyst is only a
`target_environment` of that platform, so without the explicit override the
Catalyst archives ship without Sparkplug, Maglev or TurboFan. Catalyst keeps
the full tier set so apps entitled to JIT (`com.apple.security.cs.allow-jit`)
get it; the iOS runtime passes `--jitless` when the process cannot map JIT
memory. This adds about 9.6 MB to the stripped arm64 Catalyst framework.

- `cppgc_enable_caged_heap=false` (iOS only) — it defaults to **true on arm64**
and, contrary to how the flags read, does *not* track
Expand Down
89 changes: 89 additions & 0 deletions patches/v8_catalyst_jit_write_protect.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
diff --git a/src/base/build_config.h b/src/base/build_config.h
index ee745539dfd..4182c592472 100644
--- a/src/base/build_config.h
+++ b/src/base/build_config.h
@@ -36,8 +36,16 @@
#endif
#define V8_HAS_PKU_JIT_WRITE_PROTECT V8_HAS_PKU_SUPPORT

+// Mac Catalyst compiles as iOS (TARGET_OS_IPHONE) but runs on macOS, so its
+// JIT pages follow the macOS MAP_JIT + pthread_jit_write_protect_np rules.
+#if defined(V8_OS_IOS) && defined(TARGET_OS_MACCATALYST) && \
+ TARGET_OS_MACCATALYST
+#define V8_OS_MACCATALYST 1
+#endif
+
// pthread_jit_write_protect is only available on arm64 Mac.
-#if defined(V8_HOST_ARCH_ARM64) && defined(V8_OS_MACOS)
+#if defined(V8_HOST_ARCH_ARM64) && \
+ (defined(V8_OS_MACOS) || defined(V8_OS_MACCATALYST))
#define V8_HAS_PTHREAD_JIT_WRITE_PROTECT 1
#else
#define V8_HAS_PTHREAD_JIT_WRITE_PROTECT 0
diff --git a/src/base/platform/platform-darwin.cc b/src/base/platform/platform-darwin.cc
index 41fc5ec3302..004ae7c002b 100644
--- a/src/base/platform/platform-darwin.cc
+++ b/src/base/platform/platform-darwin.cc
@@ -234,6 +234,24 @@ V8_BASE_EXPORT void SetJitWriteProtected(int enable) {
}

#pragma clang diagnostic pop
+#elif V8_HAS_PTHREAD_JIT_WRITE_PROTECT && defined(V8_OS_MACCATALYST)
+// The SDK marks pthread_jit_write_protect_np unavailable on Mac Catalyst, but
+// a Catalyst process runs against macOS's libpthread, which exports it.
+namespace {
+using JitWriteProtectFn = void (*)(int);
+
+JitWriteProtectFn ResolveJitWriteProtect() {
+ auto fn = reinterpret_cast<JitWriteProtectFn>(
+ dlsym(RTLD_DEFAULT, "pthread_jit_write_protect_np"));
+ CHECK_NOT_NULL(fn);
+ return fn;
+}
+} // namespace
+
+V8_BASE_EXPORT void SetJitWriteProtected(int enable) {
+ static const JitWriteProtectFn jit_write_protect = ResolveJitWriteProtect();
+ jit_write_protect(enable);
+}
#endif

} // namespace base
diff --git a/src/heap/code-range.cc b/src/heap/code-range.cc
index ca0b1742b38..b979862d76f 100644
--- a/src/heap/code-range.cc
+++ b/src/heap/code-range.cc
@@ -382,8 +382,8 @@ bool CodeRange::InitReservation(v8::PageAllocator* page_allocator,
// Don't pre-commit the code cage on Windows since it uses memory and it's not
// required for recommit.
// iOS cannot adjust page permissions for MAP_JIT'd pages, they are set as RWX
-// at the start.
-#if !defined(V8_OS_WIN) && !defined(V8_OS_IOS)
+// at the start. Mac Catalyst runs on macOS and needs the pre-commit.
+#if !defined(V8_OS_WIN) && (!defined(V8_OS_IOS) || defined(V8_OS_MACCATALYST))
if (params.page_initialization_mode ==
base::PageInitializationMode::kRecommitOnly) {
void* base = reinterpret_cast<void*>(page_allocator_->begin() +
@@ -605,7 +605,7 @@ uint8_t* CodeRange::RemapEmbeddedBuiltins(Isolate* isolate,
if (V8_HEAP_USE_PTHREAD_JIT_WRITE_PROTECT ||
V8_HEAP_USE_BECORE_JIT_WRITE_PROTECT || ThreadIsolation::Enabled()) {
// iOS code pages are already RWX and don't need to be modified.
-#if !defined(V8_TARGET_OS_IOS)
+#if !defined(V8_TARGET_OS_IOS) || defined(V8_OS_MACCATALYST)
if (!page_allocator()->RecommitPages(embedded_blob_code_copy, code_size,
PageAllocator::kReadWriteExecute)) {
V8::FatalProcessOutOfMemory(isolate,
diff --git a/src/heap/code-range.h b/src/heap/code-range.h
index b42081b8aa1..acb2f57d420 100644
--- a/src/heap/code-range.h
+++ b/src/heap/code-range.h
@@ -177,7 +177,8 @@ class CodeRange final : public VirtualMemoryCage {
// Red zones that we should not allocate in.
RedZones red_zones_;

-#if !defined(V8_OS_WIN) && !defined(V8_OS_IOS) && defined(DEBUG)
+#if !defined(V8_OS_WIN) && \
+ (!defined(V8_OS_IOS) || defined(V8_OS_MACCATALYST)) && defined(DEBUG)
bool immutable_ = false;
#endif
};
10 changes: 8 additions & 2 deletions scripts/matrix/build-ios.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,13 @@ MODULES=(
)

# iOS proper cannot JIT, so it is built in lite mode. Catalyst runs on macOS,
# where JIT is permitted, and is deliberately not lite -- keeping the two arg
# sets distinct preserves what each currently ships.
# where an app holding com.apple.security.cs.allow-jit may JIT, so it ships the
# full tier set (Sparkplug, Maglev, TurboFan) and the runtime passes --jitless
# when the process may not map JIT memory. lite mode must be turned off
# explicitly: gni/v8.gni defaults it on for target_platform "iphoneos", and
# Catalyst is only a target_environment of that platform. The Catalyst JIT also
# needs patches/v8_catalyst_jit_write_protect.patch; without it V8 writes JIT
# code without macOS's per-thread W^X toggle and faults on Apple silicon.
#
# v8_expose_public_symbols marks the V8_EXPORT API default-visibility. Plugins
# (e.g. @nativescript/canvas) compile against the vendored V8 headers and
Expand Down Expand Up @@ -128,6 +133,7 @@ if [ "$TARGET_ENV" = "catalyst" ]; then
GN_ARGS="$GN_ARGS
is_official_build=true
use_thin_lto=false
v8_enable_lite_mode=false
ios_deployment_target=\"$CATALYST_DEPLOYMENT_TARGET\""
else
GN_ARGS="$GN_ARGS
Expand Down
6 changes: 6 additions & 0 deletions scripts/matrix/fetch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ git -C v8 apply "$ROOT_DIR/patches/v8_resurrecting_finalizers.patch"
# reserved once per isolate.
git -C v8 apply "$ROOT_DIR/patches/v8_js_dispatch_table_reservation.patch"

if [ "$PLATFORM" = "ios" ]; then
# Mac Catalyst compiles as iOS but runs on macOS; this gives its JIT build
# the macOS MAP_JIT + pthread_jit_write_protect_np handling.
git -C v8 apply "$ROOT_DIR/patches/v8_catalyst_jit_write_protect.patch"
fi

if [ "$PLATFORM" = "android" ]; then
# API 21 and a selectable android_ndk_root are needed on every host; the
# host-assert change in the same patch is a no-op on Linux.
Expand Down
4 changes: 4 additions & 0 deletions tests/apple-args.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,9 @@ def test_ios_and_catalyst_do_not_receive_tvos_overrides(self):
self.assertIn('is_debug=false', args)
self.assertEqual('v8_enable_lite_mode=true' in args, 'catalyst' not in variant)

def test_catalyst_is_built_with_the_jit(self):
for variant in ['arm64-catalyst', 'x64-catalyst']:
self.assertIn('v8_enable_lite_mode=false', self.args_for(variant))

if __name__ == '__main__':
unittest.main()
Loading