NaCl cleanup in shared memory gone wrong

19 views
Skip to first unread message

Daniel Cheng

unread,
Aug 22, 2025, 8:34:58 PMAug 22
to sa...@chromium.org, Michael Lippautz, Will Harris, platform-architecture-dev, Lei Zhang, for...@chromium.org, aj...@chromium.org, mpde...@chromium.org
tl;dr
1. Should we require all shared memory creation to internally align size to page size?
2. Can we relax the alignment requirements in v8's address space management to use page alignment?

The much longer version:
On Wednesday, I thought I'd clean up a NaCl-specific branch in the Windows shared memory code. I thought this would be a simple CL to just delete some code. I wrote a CL that I thought should Just Work and sent it to the trybots. The trybots are very unhappy.

It is now Friday afternoon and I've given up on cleaning up this code at all (outside of updating some comments), but I'm also wondering if there's anything we should consider changing here.

There are multiple intersecting problems here. I'll try to go over them in a semi-coherent way, and this is probably going to end up in a doc at some point, but for now... an email.
  1. Windows has the concept of "page size" (4k on x86/x86-64, uncertain about arm) and "allocation granularity" (always 64K for historical reasons).
  2. Windows does not enforce that sizes passed to `CreateFileMapping()` and `MapViewOfFile()` are aligned to allocation granularity.
Of Chrome's 4 non-Windows platforms:
  • two platforms (Apple and Posix) do not align size while creating shared memory regions
  • two platforms (Android and Fuchsia) do align size while creating regions.
In addition, the platform-specific default shared memory mappers (//base/memory/platform_shared_memory_mapper_*.cc) do not enforce any alignment restrictions on *size* or *offset* (though in practice, internal //base code ensures that offset is aligned to the allocation granularity; it does not do anything with the size).

My current attempt (the CL first linked above) does not attempt to align size [2] at all on Windows. Most code in Chrome is perfectly happy with this; however, V8 is quite unhappy.

This is because v8 has its own custom shared memory mapper to integrate with the v8 memory sandbox. The v8 ArrayBuffer mapper aligns up to the allocation granularity; however, the original mapping was created with an *unaligned* size, and Windows internally only rounds to the nearest *page*. So when v8 tries to map in the 64k-rounded size, Windows returns an error–because v8 is trying to map in too much address space.

As an experiment, I deleted pretty much all the alignment checks and removed the alignment in the ArrayBuffer mapper. But this made other system calls fail (it is reasonable for `VirtualFree()`, and presumably `VirtualAlloc()`, to expect page alignment).

I did finally manage to convince everything to work; I changed the ArrayBuffer mapper to align to the nearest page instead of the nearest 64K boundary, and that made everything happy.

With a 64-bit address space, it probably doesn't matter much if a mapping takes up 64K instead of 4K (though smaller isn't necessarily bad either). But I had to spend a lot of time digging into somewhat arcane topics, and I'm hoping we can reduce the surprises for future readers.

Concretely, here's what I propose:
1. Make sure all the shared memory creation paths in //base align size to the nearest page. 
2. Other than exceptional cases, change v8 to align on pages instead of allocation granularity. Even V8 itself is not internally consistent; RegionAllocator::page_size_ is not actually the OS page size; it is the allocation granularity.
3. Delete the internal alignment in the ArrayBuffer mapper; it feels a bit weird to be aligning sizes up when they weren't originally created as such. We can change them to `CHECK()`s, because we should be able to rely on `base::PlatformSharedMemoryRegion` to maintain this invariant.

Daniel

[1]  Even without an aligned size, a shared memory mapping ends up costing 64K of address space in practice; Windows 10 build 1803 should change this so that mappings can be page-aligned instead

Michael Lippautz

unread,
Aug 25, 2025, 4:08:35 AMAug 25
to Samuel Groß, Daniel Cheng, sa...@chromium.org, Michael Lippautz, Will Harris, platform-architecture-dev, Lei Zhang, for...@chromium.org, aj...@chromium.org, mpde...@chromium.org
Sigh...

We introduced the distinction of allocation vs commit granularity years ago specifically for Windows and I'd expect code to use the right alignment for the right operations here. If that's not the case then that's a regular bug.

We needed the distinction for sub-V8-page operations where we'd discard memory. E.g., this was used for optimizing for memory by the garbage collector. At this point the feature is disabled because of serious Windows kernel performance problems. 

I think we can either follow the API and fix the sites accordingly or go through a (possibly painful) deprecation process here. We definitely shouldn't leave the APIs around and ignore them.

What's 

On Mon, Aug 25, 2025 at 9:36 AM Samuel Groß <sa...@google.com> wrote:
Hi Daniel!

Thanks for digging into this and doing this write-up! Is this only relevant for shared memory or also for private memory? As far as I am aware, Windows is the only platform where there's a distinction between the page size and the allocation granularity, I think everywhere else these two are the same. I think it would be really great if we could get rid of this distinction altogether since it's a bit confusing, but probably that is not possible? You mentioned that Windows doesn't actually enforce the alignment for the sizes, but presumably we still need to be aware of it for other reasons?
To your original question, I don't see any problems with changing V8's enforcement of the alignment. I think it would be fine to always enforce alignment to page_size() instead, although that might feel a bit unintuitive if we still carry the allocation_granularity() around as well.

Cheers!
Samuel

Daniel Cheng

unread,
Aug 26, 2025, 12:50:23 AMAug 26
to Michael Lippautz, Samuel Groß, sa...@chromium.org, Will Harris, platform-architecture-dev, Lei Zhang, for...@chromium.org, aj...@chromium.org, mpde...@chromium.org
On Mon, 25 Aug 2025 at 01:08, Michael Lippautz <mlip...@chromium.org> wrote:
Sigh...

We introduced the distinction of allocation vs commit granularity years ago specifically for Windows and I'd expect code to use the right alignment for the right operations here. If that's not the case then that's a regular bug.

It's quite messy here. What was originally a NaCl-only edge case has become a requirement for v8's virtual address space management.

Right now, we're internally quite inconsistent about when and how we round size when creating shared memory.

On POSIX, we do not align size in //base. gin::ArrayBufferSharedMemoryMapper does align though–so strictly speaking, we end up mmaping() more memory than we originally asked for. But this works out on all supported platforms, because POSIX manages things on page boundaries–so even if we only ask for 100 bytes, it's OK to try to mmap() in 4096 bytes later.

On Windows, we currently align to 64K boundaries. But this isn't actually required by any of the Windows APIs, as far as I can tell: CreateFileMapping(), MapViewOfFile(), and friends don't make any mention of alignment requirements for `size`. If I delete the alignment in //base, pretty much all the tests still pass–except the ones that go through v8's custom shared memory mapper.

The root cause for this turns out to be related to MSDN comment about mapping a view:
The number of bytes of a file mapping to map to the view. All bytes must be within the maximum size specified by CreateFileMapping. If this parameter is 0 (zero), the mapping extends from the specified offset to the end of the file mapping.

If we do not align in //base, but we still align to 64k in v8's shmem mapper, Windows does *not* like it if we ask for 100 bytes initially but then try to map in 65536 bytes later: IIRC, a call to `VirtualAlloc()` (or maybe `VirtualAlloc2()`?) fails and complains about invalid parameters.
What does work, even without //base alignment, is if v8's shmem wrapper only aligns to page size, i.e. 4k.
Not using any alignment at all, even in v8's shmem wrapper, leads to failures when calling `VirtualFree()`. I don't know if all `VirtualFree()` calls were problematic, but https://source.chromium.org/chromium/chromium/src/+/main:v8/src/base/platform/platform-win32.cc;l=1379;drc=8993d4b0c376ece145995d7604d3b97f7d33e38e specifically complains if `size` is not aligned to a page boundary..

To me, this is an (indirect) indication that Windows can manage memory on a page size boundary–it's just that the system allocator itself hands out things at 64k boundaries.

What I can't figure out is if v8 has a hard dependency on aligning to allocation granularity (i.e. there's some Windows API I missed somewhere that requires this), or if we can simplify the code and remove the need to consider allocation granularity at all.
 

We needed the distinction for sub-V8-page operations where we'd discard memory. E.g., this was used for optimizing for memory by the garbage collector. At this point the feature is disabled because of serious Windows kernel performance problems. 

I think we can either follow the API and fix the sites accordingly or go through a (possibly painful) deprecation process here. We definitely shouldn't leave the APIs around and ignore them.

Painful because we'd be removing APIs from v8 that embedders might be using, right?
Reply all
Reply to author
Forward
0 new messages