[PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers

From: Kairui Song via B4 Relay

Date: Mon Sep 07 2026 - 14:15:20 EST


The folio split path handles anon, page cache and swap cache folios in
one routine. That mixing is what makes the swap cache split
restrictions hard to lift and to review. We now support uniform split to
order-0 only, and no mappingless swap cache folios. And it has left a fair
number of dead or redundant checks behind.

This series prepares for lifting those restrictions by cleaning up the
code first: split the routine into an anon and a file helper, and keep
all swap cache handling in the anon helper. The file helper never sees
a swap cache folio; folio_check_splittable() rejects them up front.

Apart from two bug fixes (patch 1 and 2) and a slight adjustment of anon
splitting (patch 12), this is a pure cleanup.

Testing:

The in-tree split_huge_page_test selftest (uniform, non-uniform and
in-folio-offset splits of anon and pagecache folios) passes 62/62 over
600 runs on the patched kernel.

ftrace function_graph tracing filtered on __folio_split() was used to
compare per-call durations between the base and the patched kernel on
the same x86-64 box (interleaved runs across alternating reboots;
135 split calls per run, 600 test runs):

Before: 68.52 us, stddev: 1.58
After: 67.38 us, stddev: 1.33

The patched kernel is slightly faster. The stack usage and object size
change as the config and compiler change, but in general the stack
usage is reduced and object size is basically unchanged.

Signed-off-by: Kairui Song <kasong@xxxxxxxxxxx>
---
Changes in v4:
- Rebase on top of mm-unstable, also mergable on mm-new now.
- Patch 12 might need some attention as we can skip anon_vma locking for
unmapped anon folio now due to further cleanup, including unmapped anon
swap cache (not mappingless swap cache).
- Drop the two swap cache behavior changes, will be sent separately once
this lands, so it gets its own attention. Also drop related doc
updates. [ David Hildenbrand ]
- Rename the split helpers to __folio_freeze_split_anon() and
__folio_freeze_split_file(), frozen implies unmapped.
[ Kiryl Shutsemau, David Hildenbrand ]
- Keep zone_device_private_split_cb() in the anon helper only, and add
VM_WARN_ON_ONCE_FOLIO(folio_is_device_private()) to the file helper:
device private folios can only back anonymous memory.
[ Kiryl Shutsemau ]
- Replace the "bool unmap" parameter with a folio_mapped(), rename
remap_page() to remap_folio() and document it. [ David Hildenbrand ]
- Move the racy folio_expected_ref_count() check into unmap_folio()
instead of duplicating it.
- Make more locals const.
- Reword the mm/swap off-by-one fix: non-uniform swap cache split is not
enabled yet, so the bogus sanity check is currently dead code.
- Collect Reviewed-by/Acked-by tags.
- Link to v3: https://patch.msgid.link/20260821-swap-thp-cleanup-v3-0-9b43f5163238@xxxxxxxxxxx

Changes in v3:
- Get rid of for_each_folio_safe and open code it.
- Check if the folio is mapped before freeing it swap cache to avoid
potential performance lose.
- Initial test and binary analyze showed everything is very similiar to
previously series.
- Drop the redundant mapping argument of __split_frozen_folio
- Link to v2: https://patch.msgid.link/20260813-swap-thp-cleanup-v2-0-d2ee48c6aa49@xxxxxxxxxxx

Changes in v2:
- Return -EBUSY instead of -EINVAL for swap cache & shmem folio split
attempt.
- Introduce a for_each_folio_safe macro to dedupliate the code and
hightlight the reason we need to keep the iterate safe from folio
freeing. [ Zi Yan ]
- Rename __split_unmapped_folio() to __split_frozen_folio [ Zi Yan ]
- Rename __folio_freeze_split_unmap_anon. [ Zi Yan ]
- Several comment improments [ Zi Yan ]
- Drop an unused do_lru argument.
- Previouse test results are basically unchanged, stack usage reduced,
object very slightly larger.
- Link to v1: https://patch.msgid.link/20260808-swap-thp-cleanup-v1-0-689939a7ccc3@xxxxxxxxxxx

---
Kairui Song (17):
mm/swap: fix off-by-one in swap cache replace sanity check
mm/huge_memory: fix rejection of swap cache folios with a mapping
mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
mm/huge_memory: split the routine for splitting anon and file folio
mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio()
mm/huge_memory: consolidate irq and locking for folio split
mm/huge_memory: move EOF trimming into the file split helper
mm/huge_memory: move unmap and remap into the split helpers
mm/huge_memory: rename remap_page() to remap_folio()
mm/huge_memory: move the racy refcount check into unmap_folio()
mm/huge_memory: move filemap management into the file split helper
mm/huge_memory: move anon_vma handling into the anon split helper
mm/huge_memory: move memcg switch into the file split helper
mm/huge_memory: drop the unused do_lru argument of the file split helper
mm/huge_memory: clean up after-split folio freeing in __folio_split
mm/huge_memory: count only swap cache refs in anon folio split
mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio

mm/huge_memory.c | 585 ++++++++++++++++++++++++++++---------------------------
mm/swap_state.c | 3 +-
2 files changed, 303 insertions(+), 285 deletions(-)
---
base-commit: d118502628f8b673be9023db8bdf878f64a7ed45
change-id: 20260804-swap-thp-cleanup-6ce2be6cf3b8

Best regards,
--
Kairui Song <kasong@xxxxxxxxxxx>