Repository navigation
feat(libc): reserve the guest arena on macOS - #6124
Conversation
macOS has no /proc/self/maps or MAP_FIXED_NOREPLACE, so the arena never came up and every guest allocation threw. Enumerate host regions with mach_vm_region and reserve free chunks with a fixed mach_vm_map, which fails instead of replacing. This skips dyld, the Rosetta runtime, and ranges the kernel never lets a process map (0x10_0000_0000-0x70_0000_0000). The POSIX commit/protection paths now build on macOS unchanged. Adds standalone guest_arena tests (x86_64 under Rosetta and arm64).
Move the explanatory comments into docs/dev/debt/libc.md, as the conventions require.
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.
|
|
Checked the Linux side: |
|
@yripper I have an Intel Mac running i7-4820HQ and macOS Monterey (v12). If you could set up the things I need to do to test it on an Intel Mac, do so here. Thanks! |
|
Tested #6124 (head 913adca) on an Apple M4 (16 GB), macOS 27.2 (26B5112a). Standalone guest_arena project, Release, built per architecture: available, unmappable, host (12 / 33 protected host regions), commit all pass, both as x86-64 under Rosetta 2 and native arm64; ctest 4/4. So the arena also comes up on macOS 27 and M4. ccleste (PPSA59322, catalog 1.00, sha256 checked, no sce_module) on mugurc/ci/macos-series-2 + #6124 + #6448 + #6580 (without it libc does not compile with the macOS SDK) + #6928 + #6668 + #6667 + #6235, with the three DirectMemory.cpp arena guards widened to APPLE as @DavidSamir described: it creates the MoltenVK 1.4.2 device and swapchain, opens audio, loads its sounds and songs and dispatches compute work, then stops at a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric needs a vertex shader before it. The draw is a rect list whose pixel shader reads raw per-vertex parameters, so there is no path for it on macOS (no geometry shaders, barycentric extension disabled on MoltenVK). AI-assisted: yes |
mugurc's series (17 topic branches, updated 2026-10-17) supersedes the macos-port branch merged earlier and is what goes upstream. It brings module lists, loader scope and unwind information on macOS, direct memory backed by named Mach memory entries instead of POSIX shm (whose mappings can be aliased and made executable), Mach-O image unregistration on dlclose, and strto* errno. Conflicts: the series wins where it covers the same code (it also drops explanatory comments, as the conventions require). This branch keeps the guest arena on macOS from boykopovar#6124, which the series predates, the conditional VK_KHR_portability_enumeration request, the O_EVTONLY metadata descriptors statvfs relies on, and the socket and app0.zar checks in the macOS getdirentries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the guest arena on macOS (@yripper) Taken from the open upstream pull request so the macOS integration branch carries every piece of the port that is already under review, instead of re-implementing it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mugurc's series (17 topic branches, updated 2026-10-17) supersedes the macos-port branch merged earlier and is what goes upstream. It brings module lists, loader scope and unwind information on macOS, direct memory backed by named Mach memory entries instead of POSIX shm (whose mappings can be aliased and made executable), Mach-O image unregistration on dlclose, and strto* errno. Conflicts: the series wins where it covers the same code (it also drops explanatory comments, as the conventions require). This branch keeps the guest arena on macOS from boykopovar#6124, which the series predates, the conditional VK_KHR_portability_enumeration request, the O_EVTONLY metadata descriptors statvfs relies on, and the socket and app0.zar checks in the macOS getdirentries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Tested on an Apple M4 Pro (MacBook Pro, 24 GB), macOS 27.0.1 (26A434), Rosetta 2, Apple clang 21.0.0, CMake 4.4.4, Ninja 1.13.2. PR head
Address space on this host. A separate program that walks
What the tests do not cover. With the
Native arm64 gives the same pattern with a host region at 0x74_AC00_0000 and a free gap at 0x74_8000_0000. Without the walk, the guest loses the whole GiB around dyld and the one around the Rosetta runtime. A test case like this one would keep the walk from regressing unnoticed. Check sourceBuilt with #include "prx/libc/include/GuestArena.hpp"
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <vector>
using namespace GuestArena;
namespace {
constexpr std::uintptr_t ArenaStart = 0x0000000200000000ull;
constexpr std::uintptr_t ApplicationAreaEnd = 0x000000FC00000000ull;
constexpr std::uintptr_t Chunk = 1ull << 30;
constexpr std::size_t Bytes = 0x10000;
struct Region {
std::uintptr_t start;
std::uintptr_t end;
};
std::vector<Region> HostRegions() {
std::vector<Region> regions;
mach_vm_address_t address = ArenaStart;
while (address < ApplicationAreaEnd) {
mach_vm_size_t size = 0;
vm_region_basic_info_data_64_t info{};
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t object = MACH_PORT_NULL;
if (mach_vm_region(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO_64, reinterpret_cast<vm_region_info_t>(&info), &count, &object) != KERN_SUCCESS) break;
if (address >= ApplicationAreaEnd) break;
regions.push_back({static_cast<std::uintptr_t>(address), static_cast<std::uintptr_t>(address + size)});
address += size;
}
return regions;
}
bool Overlaps(const std::vector<Region>& regions, std::uintptr_t start, std::uintptr_t end) {
for (const auto& region : regions)
if (region.start < end && start < region.end) return true;
return false;
}
const std::vector<Region> RegionsBeforeArena = HostRegions();
}
int main() {
const auto& regions = RegionsBeforeArena;
std::printf("regions before the arena: %zu, after: %zu\n", regions.size(), HostRegions().size());
for (const auto& host : regions) {
const auto chunk = host.start & ~(Chunk - 1);
for (auto gap = std::max(chunk, ArenaStart); gap + Bytes <= chunk + Chunk; gap += Bytes) {
if (Overlaps(regions, gap, gap + Bytes)) continue;
std::printf("host region 0x%llx-0x%llx, free gap 0x%llx in the same 1 GiB chunk 0x%llx\n",
static_cast<unsigned long long>(host.start), static_cast<unsigned long long>(host.end),
static_cast<unsigned long long>(gap), static_cast<unsigned long long>(chunk));
try {
GuestArenaMarkUsed_nid_postfix(reinterpret_cast<const void*>(host.start), host.end - host.start);
std::puts("fixed guest mapping over the host region: accepted");
} catch (const std::exception& error) {
std::printf("fixed guest mapping over the host region: rejected (%s)\n", error.what());
}
try {
GuestArenaMarkUsed_nid_postfix(reinterpret_cast<const void*>(gap), Bytes);
std::puts("fixed guest mapping in the free gap: accepted");
GuestArenaRelease_nid_postfix(reinterpret_cast<const void*>(gap), Bytes);
} catch (const std::exception& error) {
std::printf("fixed guest mapping in the free gap: rejected (%s)\n", error.what());
}
return 0;
}
}
std::puts("no free gap shares a 1 GiB chunk with a host region");
return 0;
}Not checked: Linux and Windows, the full macOS libc build (it does not link on AI-assisted: yes (Claude Code). I ran all of the above on my machine. |
…a keeps free space beside host regions (@imellag) Brings imellag's follow-up to boykopovar#6124: a guest_arena test that fails if the arena stops reading the host map, and the measurement that the unmappable range starts at 0xFC00000000 on macOS 27, which matches the Rosetta reservation the AGC driver memory runs into. The five standalone guest_arena tests pass on the M1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What
The guest arena never came up on macOS, so every guest allocation threw: the arena seeds its used ranges from
/proc/self/mapsand reserves the rest withMAP_FIXED_NOREPLACE, and macOS has neither. This keeps the sameArenaclass and constants and swaps only those two host primitives under__APPLE__:mach_vm_regionwalk fromArenaStarttoApplicationAreaEnd.mach_vm_mapwithVM_FLAGS_FIXED,VM_PROT_NONEand max protectionVM_PROT_ALL, in the same 1 GiB chunks. A fixedmach_vm_mapfails instead of replacing, andKERN_NO_SPACEmaps toEEXIST, so the loop that skips occupied chunks is unchanged.The POSIX commit and protection paths (
PlaceAt,GuestArenaSetProtection) are enabled for__APPLE__as well. On Linux the code is split intoHostMappingsandReserveNoReplacewith the same calls and error mapping.The ranges macOS never lets a process map (0x10_0000_0000–0x70_0000_0000), and the dyld and Rosetta runtime regions inside the application area, are recorded as debt in
docs/dev/debt/libc.md.This is the arena step agreed in #1, so @mugurc's direct-memory branch can be based on it.
Tested
guest_arenatest (core/libs/tests/guest_arena, macOS only), 4 modes:available,unmappable,host,commit. All pass on macOS 26 on an M1 Pro, both as x86-64 under Rosetta 2 and as arm64./proc/self/mapsparsing andmmap(MAP_FIXED_NOREPLACE)handling; Windows is untouched.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)