Skip to content

feat(patches): make the JS dispatch table reservation a per-isolate CreateParams field - #7

Merged
edusperoni merged 1 commit into
mainfrom
feat/js-dispatch-table-reservation-param
Sep 6, 2026
Merged

feat(patches): make the JS dispatch table reservation a per-isolate CreateParams field#7
edusperoni merged 1 commit into
mainfrom
feat/js-dispatch-table-reservation-param

Conversation

@edusperoni

Copy link
Copy Markdown
Collaborator

Motivation

Without the sandbox (our iOS build is sandbox=false, pointer_compression=false, lite/jitless), the only ExternalEntityTable an isolate creates is the JSDispatchTable. SegmentedTable::Initialize reserves kJSDispatchTableReservationSize256 MB of address space, per isolate — and calls FatalProcessOutOfMemory("SegmentedTable::InitializeTable (subspace allocation)") when the reservation fails.

iOS budgets per-process virtual address space by device RAM, so an app running ~10 worker isolates can hit that abort simply by creating another worker. 256 MB is a maximum the isolate will never approach; nothing forces it to be reserved up front.

What the patch adds

patches/v8_js_dispatch_table_reservation.patch makes the reservation a per-isolate embedder parameter, defaulting to today's behavior:

// include/v8-isolate.h, Isolate::CreateParams
size_t js_dispatch_table_reservation_size = 0;  // 0 selects V8's default

with a feature macro next to the class so embedders can compile against old and new headers:

#define V8_HAS_JS_DISPATCH_TABLE_RESERVATION_PARAM 1

Internally the size is threaded api.ccIsolate::set_js_dispatch_table_reservation_sizeIsolate::InitJSDispatchTable::Initialize(size)ExternalEntityTable::Initialize(size)SegmentedTable::Initialize(size), where it replaces kReservationSize in the subspace allocation, the EmulatedVirtualAddressSubspace fallback and the alignment DCHECK. Every existing caller keeps calling Initialize() and gets the compile-time default.

Files touched (9): include/v8-isolate.h, src/api/api.cc, src/common/segmented-table.h, src/common/segmented-table-inl.h, src/execution/isolate.h, src/execution/isolate.cc, src/sandbox/external-entity-table.h, src/sandbox/external-entity-table-inl.h, src/sandbox/js-dispatch-table.h.

Validation rules

api.cc rejects bad values through Utils::ApiCheck. 0 means "use V8's default"; any other value must be

  • a multiple of JSDispatchTable::kReservationSizeGranularity (the table segment size),
  • at least JSDispatchTable::kMinReservationSize,
  • at most kJSDispatchTableReservationSize (256 MB, or 16 MB in lower-limits mode).

The minimum, and why

static constexpr size_t kMinReservationSize =
    kSegmentSize * (kNumReadOnlySegments + 1);

ExternalEntityTable::Initialize maps kNumReadOnlySegments (64 KB / kSegmentSize, i.e. always 64 KB worth) of read-only segments at offset 0 before anything else; the null entry lives there. A reservation that only covers those leaves nothing to allocate from, so the minimum adds one allocatable segment on top. Concretely: 80 KB where segments are 16 KB (iOS/macOS arm64, Android, and any 4 KB-page POSIX host, which use the 16 KB segment pool) and 128 KB where they are 64 KB (Windows, 64 KB-page Linux).

What is not changed

kJSDispatchHandleShift, kMaxJSDispatchEntries and SegmentedTable::kMaxCapacity are untouched — the handle encoding is baked into generated code and the snapshot. kReservationSize stays the compile-time maximum; only the live reservation becomes configurable. An isolate that outgrows its smaller reservation still aborts in AllocateAndInitializeSegment, exactly as one that outgrows 256 MB does today.

The size is deliberately not stored in a member: sizeof(JSDispatchTable) is pinned by Internals::kExternalEntityTableSize, and every IsolateData field after js_dispatch_table_ (kIsolateApiCallbackThunkArgumentOffset, kContinuationPreservedEmbedderDataOffset, …) is laid out relative to it. Growing the table would break Isolate::CheckIsolateLayout(). All existing uses of kReservationSize for the live reservation were inside Initialize(), so the parameter suffices.

kReservationSize moved out of the V8_TARGET_ARCH_64_BIT block in segmented-table.h so the new default argument also compiles for the 32-bit Android targets, where it stays unused (segments are individually mapped there).

Compile check

Not a full build — include/v8-isolate.h would rebuild the world. Instead each affected translation unit was compiled with its exact ninja -t commands invocation in an existing release host build dir (macOS arm64, is_debug=false, sandbox + pointer compression on, so the sizeof/layout static_asserts are live). All exited 0 with no new diagnostics:

  • obj/v8_base_without_compiler/api.o
  • obj/v8_base_without_compiler/isolate.o
  • obj/v8_base_without_compiler/js-dispatch-table.o
  • obj/v8_base_without_compiler/external-pointer-table.o
  • obj/v8_base_without_compiler/trusted-pointer-table.o
  • obj/v8_base_without_compiler/code-pointer-table.o
  • obj/v8_base_without_compiler/cppheap-pointer-table.o
  • obj/v8_base_without_compiler/wasm-code-pointer-table.o

Plus a standalone embedder TU compiled against include/ with the same flags, which #errors if the macro is missing and sets the field under #ifdef V8_HAS_JS_DISPATCH_TABLE_RESERVATION_PARAM, proving the header contract.

The patch was also verified to apply cleanly with git apply to a pristine 14.9.207.39 tree (the tag config.env pins), and the result byte-compared against the tree that was compiled.

A full matrix build is the real verification — the host compile check covers one platform and one arg set; iOS/visionOS/Android and the 32-bit Android targets are only exercised by CI.

No version bump, tag or release in this PR.

@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: f2b01e34-0d9b-461c-9465-4aa3e551af9f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@edusperoni
edusperoni merged commit 9dfc18b into main Sep 6, 2026
14 checks passed
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