HasanH47 opened PR #14357 from HasanH47:pagemap-reset-complete-traversal to bytecodealliance:main:
reset_with_pagemapcollects dirty regions into a stack buffer of 32 and
treats the kernel'swalk_endas the end of the memory it will ever reset
in place: everything after it is decommitted withmadvise(DONTNEED).Two consequences we hit running a QuickJS-based guest with a fresh
instance per request (pooling allocator,memory_init_cow,pagemap_scan):
Throughput. A JS heap dirties far more than 32 disjoint runs, so
the scan stopped early on every reset and the rest of the heap was
decommitted. Each new instance then re-faulted its heap (95–315 minor
faults per request measured withperf stat), and themadvise+
mprotectstorm capped multi-core scaling with TLB-shootdown IPIs.
Resuming the scan fromwalk_endwhenever the buffer filled, and
stopping early only when thekeep_residentpage budget is spent (which
is whatwalk_endis for), brought a request from 2.1 ms to 1.0 ms on
one core and ×5 at 16 concurrent instances on a 16-core host, with 0
faults per request.Freshness. The mask requires
PRESENT. A dirty page the kernel has
swapped out isWRITTEN | SWAPPED, notPRESENT; it is neither reset in
place nor decommitted (it sits beforewalk_end), so the next instance in
the slot reads the previous instance's bytes. We reproduced this with a
swapfile andMADV_PAGEOUTbetween two instantiations of the same slot:
the second instance observed the first's heap and trapped in the
allocator. RequiringWRITTENand notPFNZERO/FILEregardless of
residency fixes it — resetting a swapped page pages it in and overwrites
it, which is the cost of correctness. With 32 regions the accidental
decommit of everything afterwalk_endhid most of this; with a complete
traversal it would be exposed on every host with swap, so the two changes
belong together.The patch keeps the fixed stack buffer (64 regions) and loops the ioctl,
so there is still no allocation on the reset path. Unit tests in
pagemap.rs(they skip wherePAGEMAP_SCANis unavailable):
reset_resumes_past_the_region_buffer: 200 disjoint dirty pages in a
400-page mapping,keep_resident= everything — all 200 reset in place,
nothing decommitted, the mapping reads zero;
reset_stops_at_the_page_budget: same mapping,keep_resident= 100
pages — exactly 100 reset, the rest decommitted;
reset_covers_dirty_pages_that_were_paged_out: 64 dirty pages,
MADV_PAGEOUT, then the reset — every page reset (on a host with swap
this fails with the oldPRESENTmask; elsewhere it is the ordinary
reset).
HasanH47 requested dicej for a review on PR #14357.
HasanH47 requested wasmtime-core-reviewers for a review on PR #14357.
HasanH47 updated PR #14357.
github-actions[bot] added the label wasmtime:api on PR #14357.
Last updated: Sep 20 2026 at 18:08 UTC