- Board renamed docs/plan/00-kanban.md -> docs/00-status.md and rebuilt: ▶ NEXT PLAN pointer (iteration 4 — emitter, corpus, `woc build`) then six buckets — stories, in progress, done, pending, discarded, learnings. It covered only the Rust runtime before, so the whole OOP track was invisible. All 16 inbound refs repointed; `Kanban:` banners renamed to `Status:`. - New discarded.md (settled rejections with reasons: inheritance, `abstract`, Money/SKU/Float, Dynamic/cast/macro/extern, AOT-to-C, Menhir, shared engine state) and learnings.md (plumbed≠enforced, vacuous goldens, exit-0-wrong- output, malloc-path ASan trick, deferred checks that never reach the VM). - RECOVERED docs/plan/exploration/blue-green-vm/00-vision.md — gone from disk, never committed (gitignored path), cited by five docs incl. principle 12. Root cause was broader: all seven forward-roadmap plans in docs/superpowers/plans/ were untracked and ignored, on one disk only. Dropped the docs ignore rules with a do-not-re-add note; added __pycache__/*.pyc. - Repaired broken links across docs/, 270 -> 36: fixes a regression from the earlier reference/ -> .dev/reference/ move (relative paths at ../../ and deeper were skipped), plus depth and reorg drift. The 36 residual point at content that does not exist and need decisions, not paths. - New spec docs/superpowers/specs/2026-08-10-logwatcher-gap-closure-design.md, applied: `and`/`or` verdict row; Part 3 gains `env` (six modules), swaps time.mono for iso/local, adds 22 bare core builtins; throw/time.mono/is cut (0 uses in the sample). Plan 8: Task 2 gains and/or, Task 5 drops throw, abstract+`is` task deleted, 8/9 renumber to 7/8. Plan 9 gains core builtins. Plan 10 gains the 307 -> 0 diagnostic gate. WO-E205 re-filed unreachable-by- design. types.ml header drops its false satisfaction-set claim. 00-code- review.md reduced to a stub — its rival Phase 1-4 roadmap retired.
5.5 KiB
08 — mmap + madvise
Memory-map a file (or anonymous region) into the process's address space. The kernel manages the page cache; your code sees a &[u8] slice. madvise hints the kernel about access patterns so it can pre-fetch sequentially, evict aggressively after scans, or map huge pages.
Central to Phase 3's storage engine: segment files are mmaped read-only for O(1)/O(log n) indexed lookups without copying bytes into heap memory.
Kernel source
| Path | What |
|---|---|
reference/linux/mm/mmap.c |
VMA creation, SYSCALL_DEFINE6(mmap, ...), SYSCALL_DEFINE2(munmap, ...). |
reference/linux/mm/madvise.c |
SYSCALL_DEFINE3(madvise, ...) + every MADV_* handler. |
reference/linux/include/uapi/linux/mman.h |
MAP_* flags, huge-page sizing macros. |
POSIX <sys/mman.h> |
The other half of the constants (PROT_*, MADV_*). Usually folded into linux/mman.h by libc. |
Man pages
man 2 mmap, man 2 madvise, man 2 munmap, man 2 msync, man 2 mprotect.
Rust FFI via libc
use libc::{mmap, munmap, madvise, msync, mprotect};
use libc::{PROT_READ, PROT_WRITE, PROT_NONE, PROT_EXEC};
use libc::{MAP_SHARED, MAP_PRIVATE, MAP_ANONYMOUS, MAP_FIXED};
use libc::{MAP_POPULATE, MAP_HUGETLB, MAP_HUGE_2MB, MAP_HUGE_1GB};
use libc::{MADV_SEQUENTIAL, MADV_RANDOM, MADV_WILLNEED, MADV_DONTNEED};
use libc::{MADV_HUGEPAGE, MADV_NOHUGEPAGE, MS_SYNC, MS_ASYNC};
Direct-syscall example
unsafe {
// 1. Map a segment file read-only. Use MAP_POPULATE to pre-fault all pages
// so lookups don't hit a minor page fault mid-request.
let fd = libc::open(path.as_ptr(), libc::O_RDONLY);
let len = libc::lseek(fd, 0, libc::SEEK_END) as usize;
let ptr = libc::mmap(
std::ptr::null_mut(),
len,
libc::PROT_READ,
libc::MAP_SHARED | libc::MAP_POPULATE,
fd,
0,
);
if ptr == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
}
// 2. Hint access pattern — sequential scan for a full compaction pass,
// random for indexed lookups. MADV_DONTNEED after a scan releases page cache pressure.
libc::madvise(ptr, len, libc::MADV_RANDOM);
// 3. Use it as a byte slice
let slice: &[u8] = std::slice::from_raw_parts(ptr as *const u8, len);
let record = &slice[offset..offset + record_len];
// 4. Clean up
libc::munmap(ptr, len);
libc::close(fd);
}
Key flags
| Flag | Meaning |
|---|---|
PROT_READ / PROT_WRITE |
Obvious. Combine as needed. PROT_NONE makes a guard page. |
MAP_SHARED |
Writes go back to the file. Required for write-through semantics (WAL staging into a mmaped region). |
MAP_PRIVATE |
Copy-on-write. Writes never hit the file. Use for read-only segments where you want CoW safety. |
MAP_POPULATE |
Pre-fault the whole mapping at mmap time. Trades boot latency for zero-fault request path. Use it for hot segments. |
MAP_HUGETLB / MAP_HUGE_2MB |
Back with huge pages. 512× fewer TLB entries for a 64 GB arena. Requires vm.nr_hugepages configured. |
MAP_ANONYMOUS |
Not file-backed — just zero-initialised pages. Used for arenas the engine allocates internally. |
MAP_FIXED |
Place at the exact requested address. Dangerous — will silently overwrite existing mappings. Only when you know what you're doing (e.g. placing guard pages). |
madvise |
Meaning |
|---|---|
MADV_SEQUENTIAL |
"I'll read sequentially." Kernel prefetches ahead, drops pages behind. Full scans, compaction. |
MADV_RANDOM |
"Lookups will be random." Kernel disables read-ahead. Index lookups. |
MADV_WILLNEED |
"Bring these pages in now." Async prefetch for an upcoming working set. |
MADV_DONTNEED |
"I'm done; drop these pages." Frees page-cache slots immediately — good after a scan to avoid polluting the cache. |
MADV_HUGEPAGE |
Opt this range into Transparent Huge Pages. |
Gotchas
- Shared writable mappings and
fsync. Writes to aMAP_SHAREDregion are not durable until youmsync(MS_SYNC)orfsyncthe underlying fd. For write paths that need durability, prefer explicitpwrite— don't rely onmsyncfor the hot path. SIGBUSon truncated files. If the file shrinks beneath your mapping, accesses past the new end raiseSIGBUS. Arrange for sealed files (memfd_create(MFD_ALLOW_SEALING)+F_SEAL_SHRINK) or just don't truncate.- Page faults block the single thread. In a single-threaded runtime, a minor fault during a request freezes the whole loop.
MAP_POPULATEat boot sidesteps this for hot data. Usemlockall(MCL_CURRENT \| MCL_FUTURE)if faults must never happen — but that requiresCAP_IPC_LOCKorRLIMIT_MEMLOCKheadroom. madvisehints are advice, not commands. The kernel may ignore them under memory pressure. Don't rely on them for correctness; only for perf.- Huge pages need config.
vm.nr_hugepageshas to have enough entries for your arenas. Startup-time check, not request-time.
Used by
Phase 3 of the database series — see docs/runtime/database/03-inmemory-engine.md § Linux Tuning Checklist. The relational B+ tree, the LSM memtables' on-disk segments, and the document store's arenas all live behind mmap.
v1 port source
None directly — v1's wo-seg reads with pread, not mmap. This is new code territory for the new storage engine.