Skip to content

Comments

Wasm: workaround for huge safepoint table#2165

Draft
vouillon wants to merge 1 commit intomasterfrom
wasm-allocations
Draft

Wasm: workaround for huge safepoint table#2165
vouillon wants to merge 1 commit intomasterfrom
wasm-allocations

Conversation

@vouillon
Copy link
Member

@vouillon vouillon commented Feb 4, 2026

Problem

V8 uses a safepoint table to track which stack entries contain GC references. For each function, all safepoint entries share the same size, which must accommodate the stack at its deepest point.

When using array.new_fixed, each array element requires a stack slot during initialization. Large arrays cause excessive safepoint table growth.

I have made some measurement on PRT (partial render table benchmark). For this program, a single 6913-element array (a parser transition table) caused the safepoint table to balloon to 8.5 MB.

Solution

Introduce a size threshold above which array.new + explicit stores is used instead of array.new_fixed. This avoids stack pressure from inline element initialization.

Current threshold: 100 elements

Measurements (PRT benchmark)

Configuration Code Size Stack Slots Safepoint Entries Safepoint Size
Before (no limit) 2.18 MB 7156 9463 8.49 MB
Limit ≤ 200 2.24 MB 381 9463 473 KB
Limit ≤ 100 2.25 MB 345 9463 435 KB
No array.new_fixed 2.39 MB 280 4463 170 KB

Key observations

  • 18× safepoint reduction: Limiting to ≤200 reduces safepoint table from 8.5 MB to 473 KB
  • Modest code size increase: Only 2.5-3% larger with limits
  • Entry count drop: The "no array.new_fixed" case has 5000 fewer entries (4463 vs 9463). Successive identical safepoint entries are deduplicated; without array.new_fixed, the stack has more constant depth, leading to more deduplication
  • Nested allocations: OCaml lists compile to nested array.new_fixed calls, which compound stack depth (inner arrays remain on the stack while outer arrays are initialized)

Array Size Distribution

Analysis of ~36,500 array allocations in the test program:

Size Range Count Cumulative %
0-10 ~34,000 93%
11-63 ~2,400 99%
64-100 ~25 99.9%
101-136 ~15 99.97%
137-256 1 (size 257)
257+ 2 (257, 6913) 100%

The distribution is heavily skewed toward small arrays. Only ~1% of arrays exceed size 63, and only ~25 arrays exceed size 100.

There is a gap between sizes 137 and 257 in this program, meaning the difference between threshold 100 and 200 is smaller than it might be for other programs.

Threshold Choice

Threshold Pros Cons
64 Captures 99% of arrays; maximum safety margin Slightly more arrays use slower path
100 (current) Captures 99.9%; round number Programs with arrays in 65-100 range add stack pressure

The current threshold of 100 is reasonable. A lower threshold (64) would provide additional safety margin for programs with different distributions, but the benefit is marginal given how skewed the distribution is toward small arrays.

Notes

  • Safepoint table size can be obtained via node --print-wasm-code
  • Safepoint size scales as: entries × ceil(stack_slots / 8)

@vouillon vouillon force-pushed the wasm-allocations branch 2 times, most recently from e3ab097 to 6cf2375 Compare February 4, 2026 15:58
V8 uses a safepoint table to track which stack entries contain
references. For each function, all safepoint entries share the same
size, which must accommodate the stack at its deepest point. Large
functions that allocate large arrays via `array.new_fixed` can cause the
safepoint table to grow excessively, since each element requires a stack
slot during initialization. This change introduces a threshold above
which `array.new` is used instead, avoiding the stack pressure from
inline element initialization.
@jakobkummerow
Copy link

FYI, It would be very interesting to repeat the measurements with a Chrome build that contains this commit, i.e. the 147.0.7687.0 Canary or later. Hopefully this workaround is then no longer necessary.

@vouillon
Copy link
Member Author

FYI, It would be very interesting to repeat the measurements with a Chrome build that contains this commit, i.e. the 147.0.7687.0 Canary or later. Hopefully this workaround is then no longer necessary.

@jakobkummerow Indeed, this makes a huge difference!

Before:

Safepoints (stack slots = 7156, entries = 9463, byte size = 8488323)

After:

Safepoints (stack slots = 7156, byte size = 65401)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants