NextBSD swap and paging without a partition — one extent layer, two filesystems Research plan · new sub-project

No swap partition, no dump partition, working crash dumps, and ZFS — all four at once, with one mechanism. The design is not novel: Apple and illumos arrived at it independently. Pin the object's blocks, extract a physical extent map once, and at I/O time bypass the filesystem entirely, writing raw LBAs. Neither writes through a copy-on-write filesystem; both make COW irrelevant by never dirtying the object through the FS after pinning. ZFS shipped this in production — illumos, 2008 to today. OpenZFS stripped it; FreeBSD never compiled it (FreeBSD 12's in-tree ZFS still carried the whole thing under #ifdef illumos). Restoring it is ~2–4k LOC and every hard primitive survives. The UFS half is much cheaper — ~500–800 LOC — and is the same device path swap partitions already use. This also corrects freebsd-apple-userland-cmds §7: modern dynamic_pager is a 114-line shim that sets vm.swapfileprefix, macx_swapon returns ENOTSUP, and the Mach default pager was removed in 10.12 — so the port needs no Mach traps and buys almost nothing. Companion to EPIC E14.

TL;DR

1. Where NextBSD actually is

SourceSaysReality
nextbsd/nextbsd build.sh 801, 953/cow is “tmpfs: dynamic, swap-backedfalse no swap configured anywhere
image-strategy surveylive writes “go to RAM (tmpfs/swap)”false RAM only
srclist build planswapon DEFER, dumpon DEFER, savecore DROPaccurate

Measured consequence, #326: /cow at 97 % of 7 GB, 7032M Laundry, swapctl -l empty, OOM-killer taking daemons.

Sequencing trap. Nothing under launchd runs swapon -a or dumpon, so any fstab entry is dead text — the same failure as closed issue #358, where the installer wrote /etc/myname and nothing read it. The activation path must land before or with anything that creates swap.

2. The design — one extent layer, two feeders

                    swap pager  /  dump wrapper
                            |
                   +--------+--------+
                   |  physical extent |     built once, at swapon / pin time
                   |       map        |     never consulted through the FS
                   +--------+--------+
                            |
                     raw bio / d_dump   --->  GEOM provider
                            |
        +-------------------+-------------------+
        |                                       |
   UFS: ufs_bmaparray / VOP_BMAP        ZFS: swapified object's DVA list
   ~500-800 LOC, low risk               ~2-4k LOC, moderate risk
   (same path swap partitions use)      (illumos dumpify port)

After swapon the filesystem is not on the I/O path. That is the whole safety argument, and it is the same one Linux relies on (swap_activate → extent map → submit_bio straight to sis->bdev).

2.1 What has to change on the UFS side

FreeBSD refuses local swapfiles today. sys/vm/swap_pager.c:2680 on releng/15.1, the NextBSD kernel base (2679 on main):

	} else if (vp->v_type == VREG &&
	    (vp->v_mount->mnt_vfc->vfc_flags & VFCF_NETWORK) != 0 &&
	    (error = VOP_GETATTR(vp, &attr, td->td_ucred)) == 0) {
		/*
		 * Allow direct swapping to NFS regular files in the same
		 * way that nfs_mountroot() sets up diskless swapping.

So: relax that test for filesystems that can return a full, hole-free block map; walk VOP_BMAP at swapon as Linux's generic_swapfile_activate() does; store the extents in struct swdevt; add a VV_SWAP vnode flag that makes ffs_write, ffs_truncate and ufs_remove refuse with ETXTBSY, mirroring xnu's VSWAP and Linux's S_SWAPFILE; and teach swapon(8) to stop creating an md(4) device for local files.

Preallocation on UFS is already correct if slow: FFS has no vop_allocate of its own, so vop_stdallocate() writes real zero blocks — no holes, which is exactly what the extent walk requires. Note also that ffs_reallocblks only ever relocates dirty, not-yet-written clusters, so a synced file does not move.

3. How it runs — phase by phase

Everything below happens with no partition. The files live at the canonical Darwin path /private/var/vm — NextBSD ships /var -> private/var, matching classic Mac OS X's /private/var/vm/swapfile0. On ZFS it is the zroot/private/var/vm dataset, which inherits that mountpoint with no override (§5.1). (NextBSD has no /System/Volumes, so it models the pre-Big Sur layout, not Apple's separate VM volume.)

3.1 The lifecycle

Swapfile lifecycle Six phases. Install creates /private/var/vm. Boot pins and maps each file. Pageout writes through the extent map. Grow adds a file when swap runs low and returns to pageout. Reclaim removes a file when pressure ends, and the cycle returns to pageout on the next build. Panic can happen at any time and writes the dump through the same extent mechanism. 3 · Grow swap_pager_almost_full → devctl VM/swap LOWSPACE next file, 256 MiB → 1 GiB swapd 0 · Install create /private/var/vm ZFS: zroot/private/var/vm checksum+compress off no partition, no size installer 1 · Boot: pin & map preallocate — no holes swapon(2) on the file walk VOP_BMAP / DVAs set VV_SWAP — immutable swapd kernel 2 · Pageout pagedaemon → laundry swap_pager_putpages() extent map → raw bio FS never entered kernel 4 · Reclaim free swap high for N min swapoff(2) newest file pages back in — slow unlink; space returns swapd 5 · Panic: dump doadump → wrapper dumper split at extent edges leaf d_dump — polled kernel pressure ends next build swap low resume panic — any time

installer userland (swapd, launchd) kernel

3.2 The write path — why it cannot deadlock

This is the whole design in one picture. Both paths share everything down to sw_strategy; they differ only in what that pointer calls.

Swap write path, today versus E14 Both paths go from the pagedaemon through the laundry worker to swap_pager_putpages and sw_strategy. Today's md-backed swapfile then enters the filesystem, which allocates memory, sleeps in vm_wait waiting for the pagedaemon, and deadlocks because the pagedaemon is waiting on that same write. The E14 path translates the block through the extent map and issues a raw bio to the disk, never entering the filesystem, and completes. pagedaemon_wakeup() — free count below target vm_pageout_laundry_worker() · curproc == pageproc swap_pager_putpages() blist bitmap · preallocated swwbuf pbuf · M_NOWAIT — already allocation-free sp->sw_strategy(bp, sp) — the only thing that changes TODAY — md(4) swapfile E14 — extent-mapped FILESYSTEM md kthread — not pageproc, no reserve vn_start_write · vn_lock VOP_WRITE → ffs_write → getblk / allocbuf vm_wait() — sleeps until pages are freed DEADLOCK pagedaemon waits on this write; this write waits on pagedaemon swapfile_strategy() extent map: b_blkno → physical block g_new_bio() — non-sleeping; ENOMEM, never blocks g_io_request() → disk driver (same path as a partition) COMPLETES nothing on this path allocates → pages freed → waiters wake After swapon, the filesystem's only remaining job is to have told us where the blocks are.

3.3 Step by step

PhaseStepsOwner
0 InstallCreate /private/var/vm. On ZFS, create zroot/private/var/vm (inheriting that mountpoint) with checksum=off compression=off dedup=off copies=1 recordsize=128K, no snapshots, no encryption. Do it early, while the pool is clean — a preallocation on a fragmented pool can gang, and gang blocks are fatal to the extent walk (EFRAGS). No partition. No size decision.installer
1 Bootlaunchd starts swapd. Per file: open(O_CREAT, 0600) → preallocate (UFS vop_stdallocate writes real zeros; ZFS dmu_prealloc allocates DVAs with DB_NOFILL) → swapon(2) on the path → kernel walks VOP_BMAP / traverse_dataset into struct swdevt, rejecting holes and gang blocks → sets VV_SWAP → registers swapfile_strategy on a GEOM consumer of the underlying provider → blist_create, swapon_check_swzone. The corefile is pinned the same way and registered with dumper_insert. Rebuilt every boot; never cached.swapd + kernel
2 PageoutFree count drops below vmd_free_targetpagedaemon_wakeup → inactive scan moves dirty anon pages to laundry → vm_pageout_laundry_worker (in pageproc) → vm_pageout_flushswap_pager_putpages: bitmap block allocation, preallocated pbuf, M_NOWAIT metadata → swapfile_strategy → extent lookup → g_new_biog_io_request → disk. Completion frees the pages.kernel
3 Growswp_sizecheck sees swap_pager_avail < nswap_lowat (128 pages) → EVENTHANDLERdevctl_notify("VM","swap","LOWSPACE")swapd creates the next file on xnu's ladder, then runs Phase 1 for it. Back off 15 s on ENOSPC. Refuse past vm.swap_maxpages/2 (4× RAM) unless kern.maxswzone was raised.swapd
4 ReclaimFree swap above a high-water mark for N minutes → swapoff(2) the newest file → swap_pager_swapoff pages everything on it back in, synchronously → clear VV_SWAP → unlink. Expensive, and it can fail with ENOMEM if vm_free_count + swap_pager_avail < nblks + nswap_lowat; never default to SWAPOFF_FORCE.swapd
5 Panicdoadumpdumpsys → wrapper dumper → locate extent, split at extent edges (arm64 minidump writes are unaligned) → leaf d_dump (e.g. ndadump) polls — no interrupts, no allocation → (NULL,0,0) flush fans out. Next boot, savecore in file mode fstat()s the corefile and extracts to /private/var/crash.kernel
The weak point is Phase 3 in userland. swapd must open and preallocate a file at exactly the moment memory is short; if it is paged out or blocks, growth stalls. Mitigate by mlocking the daemon and always keeping one pre-created file of headroom so growth is never on the critical path. xnu's real answer is to create files in the kernel (vm_swapfile_create_thread) — that is Increment 5's territory.

4. Core dumps — no contract change needed

The dumper contract is just “a callback taking an absolute byte offset, block-aligned, plus a (NULL, 0, 0) flush”. mediaoffset/mediasize are used only for bounds checking and header placement — and netdump already ignores them, in two places (sys/netinet/netdump/netdump_client.c:584 and :745):

	dumper.mediaoffset = 0;
	dumper.mediasize   = 0;

A file-backed wrapper therefore sets mediaoffset = 0, mediasize = file length, blocksize = 4096, maxiosize = min leaf d_maxsize, and on each write locates the extent, splits at extent boundaries, and calls the leaf d_dump. EKCD encryption and zstd compression layer above dump_write and need nothing. Splitting is mandatory, not optional: arm64 minidump_machdep.c emits writes that are not extent-aligned.

Userland is small: dumpon(8) gains an S_ISREG path routing DIOCSKERNELDUMP through VOP_IOCTL (~100 LOC); savecore(8) gains a file mode using fstat() in place of DIOCGMEDIASIZE (~150 LOC).

Apple's reference implementation is kern_open_file_for_direct_io() (bsd/kern/kern_symfile.c), run at boot: vnode_setsize(IO_NOZEROFILL), FSCTL_FREEZE_EXTENTS (fsctl.h: “Mark file's extents as 'frozen' because someone has references to physical address”), DKIOCLOCKPHYSICALEXTENTS, VNOP_BLOCKMAP(VNODE_WRITE), DKIOCGETPHYSICALEXTENT. The same map backs hibernation.

3.1 Polled I/O on the real targets

DriverDumpNote
NVMe (nda)worksndadump() nvme_da.c:583, d_dump at 1011; polls via cam_periph_runccb when dumping. The Pi 500+ boots from NVMe.
virtio-blkworksvtblk_dump — test the whole design in a VM first
SDHCIworkssdhci_generic_request spins when dumping
dwmmc~30 LOCno dumping path; would hang
USB (umass)fragileworks in principle; historically least reliable at panic

5. ZFS — restoration, not invention

illumos does not make ZFS COW-safe. It makes COW irrelevant: once ZVOL_DUMPIFIED is set, every read and write bypasses the DMU, so nothing ever dirties the object again, so no block pointer is rewritten, so the captured DVAs stay valid.

Stepillumos
1. Discarddmu_free_long_range + txg_wait_synced
2. Freeze propertiescompression=off checksum=off dedup=off refreservation=0, 128 K fixed blocks
3. Preallocatezvol_prealloc()dmu_prealloc()dmu_buf_will_not_fill() per block — real DVAs, no data written (DB_NOFILL)
4. Mapzvol_get_lbas()traverse_dataset(), coalesce adjacent DVAs, reject gang blocks (EFRAGSdumpadm's “too fragmented”)
5. I/Ozvol_dumpio()vdev_lookup_topvdev_op_dumpio — no DMU, no tx, no ARC, no ZIL, no allocation

Step 5 is why this solves swap and not only dumps. The deadlock is allocation-under-pressure; removing allocation from the write path removes the deadlock.

The one difference from Apple: APFS does not checksum file data, so raw overwrites read back normally. ZFS does — hence checksum=off, which illumos already handled, and which the surviving DB_NOFILL assert still enforces.

4.1 Hazards illumos never faced

Each needs a refusal check at pin time, and these are the substance of the ZFS work beyond the port itself:

device removal / indirect vdevsDVAs get remapped — refuse, or re-walk
raidz expansionrelocates data — refuse while pinned
block cloning / BRTFICLONE on a pinned file must be refused
dRAIDno dumpio precedent — unsupported
encrypted datasetsillumos already refuses; encrypt in the pager instead, as xnu does
snapshots / clonesa bypass write mutates the snapshot's view — refuse on the dump/swap dataset
resilvercopies to the same offset; needs ordering against concurrent bypass writes unverified

5.1 Dataset layout

NextBSD has no ZFS dataset hierarchy yet — the only name in use anywhere is the boot environment, zroot/ROOT/default (image-strategy survey). So this plan sets the precedent, and it should follow FreeBSD's own bsdinstall convention translated to the Darwin /private tree:

zroot/ROOT/default         mountpoint=/                  <- boot environment
zroot/private              mountpoint=/private  canmount=off
zroot/private/var          canmount=off          (inherits /private/var)
zroot/private/var/vm       real dataset          (inherits /private/var/vm)
zroot/private/var/crash    real dataset          (inherits /private/var/crash)

The dataset name matches the real path, so one explicit mountpoint on zroot/private places everything beneath it. zroot/var/vm would name the /var symlink rather than the directory — the same mistake as writing /var/vm.

canmount=off parents keep the boot environment intact. They exist to provide the naming and mountpoint hierarchy without mounting anything, so /private/etc and the bulk of /private/var stay inside zroot/ROOT/default and roll back with it; only vm and crash are split out. This is exactly bsdinstall's zroot/var + zroot/var/crash/log pattern. The pinned dataset must live outside ROOT in any case — bectl snapshots the boot environment, and a pinned dataset under it would be hit on every BE create.

vm and crash are deliberately separate. The pinned corefile lives in vm with checksum=off; savecore extracts it into crash as an ordinary file that should be checksummed, and that takes bsdinstall's exec=off setuid=off.

Properties on zroot/private/var/vm: checksum=off compression=off dedup=off copies=1 recordsize=128K, no encryption, and com.sun:auto-snapshot=false (which the OpenZFS FAQ already recommends for swap).

Recursive snapshots are atomic. zfs snapshot -r zroot/private@backup is all-or-nothing, so if Z4 simply refused a snapshot of the pinned child, the entire recursive snapshot would fail — silently breaking backup scripts, sanoid and zfs-auto-snapshot. Z4 must instead skip pinned datasets during recursive snapshot and send -R, and refuse only a snapshot aimed at the pinned dataset directly. This is the real reason Apple keeps VM on a separate volume: it is excluded from Data's snapshots by construction. Nesting it gives the right name and path, at the cost of handling that exclusion explicitly.

6. Why not the memory-reserve route

It is the obvious idea, it is what xnu appears to do, and it is not the safety argument.

xnu's TH_OPT_VMPRIV is real — an exclusive page reserve that only privileged threads may drain, with privileged waiters woken first. But it is only sufficient because preallocation and VSWAP remove allocation from the pageout path in the first place. The privilege cannot help if the swap thread blocks on a lock held by an unprivileged thread parked in vm_page_wait().

illumos built the full reserve design for ZFS — NOMEMWAIT(), KM_PUSHPAGE, pageout_reserve sized “at least 4MB for use by ZFS”, the arc_memory_throttle pageout branch — and still ships pageout_deadman, which panics after 90 seconds of stuck pageout. That is what “narrows but does not close” looks like in production.

On FreeBSD it is weaker again:

/* include/os/freebsd/spl/sys/kmem.h */
#define	KM_SLEEP		M_WAITOK
#define	KM_PUSHPAGE		M_WAITOK   /* the illumos reserve semantic is gone */

/* module/os/freebsd/zfs/arc_os.c:124 */
arc_memory_throttle(spa_t *spa, uint64_t reserve, uint64_t txg)
{
	return (0);
}

And a structural point no privilege can fix: with any DMU write the data stays dirty in ARC until txg sync, so a swap write through the DMU cannot free memory faster than txg sync.

Take only the cheap parts as defence in depth: a TDP_VMPRIV per-thread flag (~300–500 LOC in sys/vm) to replace the blunt curproc == pageproc identity check, synchronous dispatch for swap objects, and the arc_memory_throttle pageout branch. Do not let any of it carry the correctness argument. Tracked as K8.
Pre-existing exposure, no swap required. zfs_putpages takes a rangelock and then calls dmu_tx_assign(tx, DMU_TX_WAIT) in pageout context for dirty mmap'd pages — and zfs_vnops_os.c:131–155 documents that precise pattern as the deadlock shape in its own header comment. Every NextBSD system on ZFS root has this today. Worth its own ticket upstream.

7. Increments

IncrementEffortBuys
1Extent-map swap pager + native file swapon on UFS; VV_SWAP guard; swapon(8) drops the md detour~500–800 LOCsafe swapfiles on UFS, no partition, same device path partitions already use
2File-backed dumper wrapper + dumpon/savecore file mode~400 kernel
~250 userland
crash dumps with no dump device; reuses Increment 1's map
3ZFS swapify: vdev_op_dumpio for geom/mirror/raidz, reinstate dmu_prealloc, extent walk, DSL guards~2–4k LOCthe same two features on ZFS root
4Userland swapd: grow/reclaim files on pressure, xnu size ladder, 15 s backoff, GC1–2 wkdynamic sizing; the dynamic_pager behaviour, minus Mach
5Decouple reclaim from swap-I/O completion (compressor-style staging)researcha stalled swap write slows the system instead of wedging the pagedaemon
skipFull Mach external pagermonthsnothing — xnu abandoned it in 10.12

8. What happens to the sizing question

It mostly dissolves. With files grown on demand there is no install-time number to get wrong — which is the real lesson of the Debian incident where 384 GB of RAM on a 512 GB SSD produced a 400 GB swap partition. The installer creates a dataset or directory, not a sized slice.

Two bounds remain, and swapd must respect both:

For reference, FreeBSD's own installer never used a RAM multiple: part_wizard.c:54 is MIN(available/20, 4 GB), disk only. The Handbook's “double the size of physical memory” and tuning(7)'s page-scanner claim are contradicted by that code and cite no mechanism.

9. Risks

R1 — fragmentation at pin time. A preallocation on a fragmented pool can gang, and gang blocks are fatal to the extent walk. Create the dump/swap objects early, at install, while the pool is clean.
R2 — ZFS self-healing is lost for swap data. checksum=off is mandatory for bypass writes. Acceptable for swap and dumps; must be documented, and confined to a dedicated dataset so nothing else inherits it.
R3 — the extent map must never outlive its validity. Re-derive it every boot, as illumos re-runs zvol_get_lbas on DKIOCDUMPINIT. Never cache it across boots.
R4 — resilver ordering is unverified. Resilver copies to the same offset; how illumos orders that against concurrent bypass writes was not established. Settle before ZFS swap is enabled on redundant pools.
R5 — raidz needs parity-aware physio. illumos writes data columns and no parity, which is why checksum=noparity exists. Port carefully, or restrict to single/mirror top-levels first; NextBSD's realistic targets are single NVMe or a mirror.
R6 — activation still does not exist. None of this matters until something under launchd applies fstab, swapon and dumpon. That work is not in this plan's tracks; it is #171's.
R7 — resolved: soft-updates cannot see bypass writes. virt-10-0 mounts its root with soft-updates, so this had to be settled before K1. On releng/15.1, soft-updates' write hook (buf_start on a buffer's b_dep) and UFS snapshot copy-on-write (ffs_copyonwrite, called only at ffs_vfsops.c:2366 and :2378) both live in ffs_geom_strategy (ffs_vfsops.c:2334), so they run only for writes through the buffer cache; a bypass bio has no buffer and no dependency list. fsync(MNT_WAIT) flushes every inode dependency, every dirty buffer and then softdep_fsync, looping until nothing is dirty or in flight (ffs_vnops.c:226-258), and SU+J takes the same path (DOINGSOFTDEP). Two requirements follow for pin time: fsync(MNT_WAIT) and then vinvalbuf(), which drops the file's cached buffers and pages (bufobj_invalbuf calls vm_object_page_remove). Bypass writes also skip snapshot copy-on-write, so a UFS snapshot's image of the swapfile holds live swap contents — harmless, since nothing that uses snapshots interprets swap or dump data.

10. Open questions

Resolved — the path is /private/var/vm. NextBSD ships /var -> private/var (verified on virt-10-0) and has no /System/Volumes, so it follows the classic Mac OS X layout (/private/var/vm/swapfile0) rather than Big Sur's separate VM volume. Swapfiles and the corefile share it; on ZFS it is the zroot/private/var/vm dataset (§5.1). Stored paths (vm.swapfileprefix, fstab) use the canonical form, not the symlink.
Q2. Pager-level encryption: port xnu's per-segment AES, or accept unencrypted swap initially? geli cannot cover a sub-range of a provider, so the usual FreeBSD answer does not apply here.
Q6. The rest of the NextBSD ZFS hierarchy is undefined. §5.1 fixes zroot/private, zroot/private/var, vm and crash; whether log, tmp, audit and /private/tmp get datasets is an installer decision beyond this plan, but it should be made once, with this layout as the template.
Resolved — Q3: no local changes. none of the 50 patches in nextbsd-kernel (at ddad2c1) touch sys/vm/, sys/dev/md/, UFS/FFS, the VFS layer, GEOM, kern_shutdown.c or the bundled OpenZFS, and src-overlay/ only adds new files. The kernel builds from releng/15.1, not stable/15 as first assumed. K1 is written against stock code.
Q4. Is the pre-existing zfs_putpages exposure worth an upstream OpenZFS report before any of this lands?
Q5. Does OpenZFS' zfs_prop.c still accept noparity as a checksum index? Only matters if raidz is supported.

11. E14 ticket map

EPIC E14 — VM, paging & filesystems, label area:vm (new, in four repos). Full bodies for every ticket are on the E14 ticket drafts page, which is generated from the same source as this table. All of them were filed on 2026-09-20 under epic nextbsd#468.

#TicketRepoDepends onIssue
K1Extent-map swap pager: accept local VREG swapon on filesystems with a real VOP_BMAPnextbsd-kernelnextbsd-kernel#214
K2VV_SWAP: refuse write, truncate and unlink on a pinned swap or dump filenextbsd-kernelK1nextbsd-kernel#215
K3swapon(8): stop creating an md(4) device for local swapfilesnextbsd-freebsd-compatK1nextbsd-freebsd-compat#51
K4File-backed kernel dumper: write crash dumps through a pinned file's extent mapnextbsd-kernelK1nextbsd-kernel#216
K5dumpon(8)/savecore(8): regular-file modenextbsd-freebsd-compatK4nextbsd-freebsd-compat#52
K6g_part: allow GEOM::kerneldump on freebsd-ufs/freebsd-zfs partitions for the file dumpernextbsd-kernelK4nextbsd-kernel#217
K7Swap low-space event: surface swp_sizecheck transitions to userland via devctlnextbsd-kernelnextbsd-kernel#218
K8Defence-in-depth VM privileges: TDP_VMPRIV, sync dispatch for swap objects, ARC pageout throttlenextbsd-kernelK1nextbsd-kernel#219
Z0kernel: compile ZFS into NEXTBSD (options ZFS, no module tree) and ship the ZFS userlandnextbsd-kernelnextbsd-kernel#230
Z1vdev_op_dumpio for vdev_geom, mirror and raidznextbsd-kernelK6nextbsd-kernel#220
Z2Reinstate dmu_prealloc()nextbsd-kernelnextbsd-kernel#221
Z3Swapify: pin a ZFS file object -- property freeze, preallocation, DVA extent walk, DMU bypassnextbsd-kernelZ1, Z2nextbsd-kernel#222
Z4DSL guards for pinned objects -- and skip, not fail, them in recursive snapshot/sendnextbsd-kernelZ3nextbsd-kernel#223
Z5Pager-level swap encryption (geli cannot cover a sub-range of a provider)nextbsd-kernelK1nextbsd-kernel#224
P1rpi5: verify NVMe polled crash dump on the Pi 500+nextbsd-kernelK4nextbsd-kernel#225
P2dwmmc: add a dumping path so d_dump does not hangnextbsd-kernelnextbsd-kernel#226
P3CI: virtio-blk crash-dump harness so the dump path is tested before hardwarenextbsdK4nextbsd#469
U1swapd: grow and reclaim swapfiles under memory pressurenextbsd-userlandK1, K7nextbsd-userland#178
U2installer: create /private/var/vm (ZFS: zroot/private/var/vm) -- no partition, no sizingnextbsd-userlandU1nextbsd-userland#179
U3vm_stat / pagesize report real numbers from vm.stats instead of the 'feature not available' stubnextbsd-userlandnextbsd-userland#180
U4installer: ZFS install path -- F8 on the disk picker, pool layout, options and review (preliminary design)nextbsd-userlandZ0, U2nextbsd-userland#201
S1Spike: decouple page reclaim from swap-I/O completion (compressor-style staging)nextbsd-kernelK1nextbsd-kernel#227
X1Report upstream: zfs_putpages calls dmu_tx_assign(DMU_TX_WAIT) in pageout contextnextbsdnextbsd#470

Order: K1 -> K2 / K3 / K4 / K7 -> K5 / K6 -> U1 -> U2. Z0 first for everything ZFS, then Z2 and Z1 (after K6) in parallel, then Z3 -> Z4. K8, Z5, S1 follow K1. U4 after Z0 and U2, once its design is refined. P2, U3 and X1 are independent.

#393 (wired memory) was moved here from E11 on 2026-09-20, and is labelled area:vm. Linked, not sub-issues: nextbsd#326, nextbsd#329, nextbsd-userland#171, nextbsd-userland#54, nextbsd#410, nextbsd#428, nextbsd#66, nextbsd#350.

12. References and confidence

FreeBSD from freebsd/freebsd-src main, with the swap-pager lines re-checked on releng/15.1 (the NextBSD kernel base, nextbsd-kernel/.github/workflows/build.yml); ZFS from openzfs/zfs master; illumos from illumos/illumos-gate; xnu from apple-oss-distributions/xnu.

Verified directly from source while writing this: swap_pager.c:2680 on releng/15.1 (VFCF_NETWORK), netdump_client.c:584,745 (mediaoffset/mediasize zero), nvme_da.c:583,1011 (ndadump), dbuf.c:2973,5500 (dmu_buf_will_not_fill + NOPARITY assert), zfeature_common.c:383 (multi_vdev_crash_dump), kmem.h:49 (KM_PUSHPAGE), arc_os.c:124 (throttle stub), zfs_vnops_os.c:131–155 (the documented deadlock pattern), part_wizard.c:54, rc.d/growfs:66, RPI.conf.

Observed on hardware: macOS 27.0 — vm.swapfileprefix = /System/Volumes/VM/swapfile, VM is APFS volume disk3s6 (noexec,noatime,nobrowse), vm.swapusage 0.00M (encrypted) with vm.compressor_bytes_used 4.07 GB, dynamic_pager usage [-F filename]. virt-10-0 — NextBSD arm64, 4 GB, 62 GB UFS root, swapinfo empty, no dynamic_pager.

Unverified — do not rely on without checking: APFS internals (closed source; FSCTL_FREEZE_EXTENTS semantics inferred from the XNU call sequence and fsctl.h, and HFS does not implement it); how illumos orders resilver against concurrent dumpio writes; illumos mirror/raidz vdev_op_dumpio details; whether OpenZFS' zfs_prop.c still accepts noparity; whether the mmc bus layer is dump-safe on all arm64 hosts; FreeBSD wiki and Bugzilla (PR 181565, 254721) are behind anti-bot protection and were read only via search snippets.