Skip to content

[LLVM] Fix JITLink excessive VMA count on AArch64#61418

Open
alecloudenback wants to merge 1 commit intoJuliaLang:masterfrom
alecloudenback:fix-jitlink-mmap-count
Open

[LLVM] Fix JITLink excessive VMA count on AArch64#61418
alecloudenback wants to merge 1 commit intoJuliaLang:masterfrom
alecloudenback:fix-jitlink-mmap-count

Conversation

@alecloudenback
Copy link
Copy Markdown
Contributor

Summary

  • Adds LLVM patch to fix excessive VMA (Virtual Memory Area) count when using JITLink, which is enabled by default on AArch64 since Julia 1.10
  • MapperJITLinkMemoryManager now uses per-MemProt memory pools so same-permission pages are contiguous, allowing the kernel to coalesce VMAs
  • InProcessMemoryMapper now tracks per-segment ranges for precise deinitialization instead of a single min-max span

Fixes llvm/llvm-project#63236

Problem

JITLink's MapperJITLinkMemoryManager uses a single memory pool for all allocations. Each allocation's segments (RX for code, RW for data) get interleaved: [RX1][RW1][RX2][RW2].... Every permission boundary creates a new VMA in the Linux kernel. With thousands of JIT allocations (e.g., loading LinearAlgebra), this can exceed the default vm.max_map_count of 65530, causing ENOMEM ("Cannot allocate memory") errors.

The current workaround is sysctl -w vm.max_map_count=262144, documented in Julia's AArch64 platform notes.

Fix

Per-MemProt memory pools ensure same-permission pages cluster together: [RX1][RX2]...[RW1][RW2].... The kernel coalesces these into just a few VMAs regardless of allocation count.

Measurements (AArch64)

Workload Unpatched (1.12.5) Patched (1.12.0) Reduction
using LinearAlgebra + light LA +57 VMAs +1 VMA 57x
4 types x 4 sizes x 7 LA ops +641 VMAs +5 VMAs 128x
LLVM standalone (500 allocs) ~1000 VMAs 3 VMAs 333x

Test plan

  • LLVM ORC JIT tests: 230/230 pass, 0 fail
  • LLVM JITLink tests: 48/48 pass, 0 fail
  • Julia 1.12.0 builds from source with patch
  • Julia LinearAlgebra workloads succeed with default vm.max_map_count=65530
  • Julia CI

Generated with Claude Code

MapperJITLinkMemoryManager previously used a single memory pool for all
allocations regardless of protection flags. This caused each allocation's
segments (typically RX for code, RW for data) to be interleaved in the
address space, creating a new VMA at every permission boundary. With many
JIT allocations this exceeded Linux's default vm.max_map_count of 65530.

This patch introduces per-MemProt memory pools so that pages with the
same protection flags are allocated from contiguous address ranges. The
kernel coalesces adjacent same-permission pages into single VMAs.

Additionally, InProcessMemoryMapper::Allocation now tracks individual
segment ranges instead of a single min-max span, so that deinitialize()
resets permissions precisely per-segment rather than over a span that
could cover unrelated allocations.

Measurements on AArch64 with LinearAlgebra workload:
- Unpatched Julia 1.12.5: 641 added VMAs
- Patched Julia 1.12.0: 5 added VMAs (128x reduction)

Upstream LLVM issue: llvm/llvm-project#63236

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@xal-0
Copy link
Copy Markdown
Member

xal-0 commented Mar 27, 2026

I expect this to cause problems when the region starts to fill up (see #60915, and the referenced earlier PRs that were merged and reverted).

@vchuravy
Copy link
Copy Markdown
Member

As a matter of procedure, right now the patch is not tested in CI, since CI uses the prebuilt binaries from Yggdrasil. Additionally, it would be great to have this patch land first in LLVM upstream (or at least be discussed there first)

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.

orc::InProcessMemoryMapper makes too many memory mappings

3 participants