All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH RFC 00/13] mm/huge_memory: clean up folio split and lift swapcache split limits
@ 2026-08-07 21:17 ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

This series clean up the split code, add better swap cache split support
for mappingless, large order, uniform and non-uniform split.  Generic
performance is on par or slightly better, and stack usage is reduced.

The swap cache infrastructure can handle non-uniform or high order folio
replace, so there is no reason for either restriction from the THP side.
What stands in the way is the mixed anon/file folio split routine,
which makes lifting the restrictions hard to follow, and it already
carries some buggy or redundant checks.

So this series cleans up the split path and separates anon and file
splitting into two helpers.  The file split path never sees a swap
cache folio, and that is now enforced up front: a folio that is both
in the page cache and the swap cache can only be a shmem folio, which
remains unsupported and is rejected early.  That helps to rule out swap
cache handling in that part completely.  Only the anon split path
handles swap cache folios, with an anon mapping or mappingless:
either way the splitting is similar, and non-uniform split is
supported as well.

Order-1 is still forbidden for swap cache splitting.  In theory it is
doable for shmem swap cache folios, but a mappingless swap cache
folio cannot currently be told apart from a shmem one, so forbid it
for all swap cache folios for now.

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 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;
mean +- stddev of the per-run averages, 135 split calls per run):

  base:    24 runs, 69.6 +- 0.7 us per __folio_split()
  patched: 26 runs, 68.8 +- 1.3 us per __folio_split()

The patched kernel is consistently ~1% faster; with this sample
count the difference is outside run-to-run noise.

On x86-64 with gcc 12 (-fstack-usage), the stack frame of
__folio_split() shrinks from 240 to 96 bytes, and the worst-case
split call chain from ~544 to ~384 (anon) or ~464 (file) bytes.

Bloat-o-meter shows a tiny growth of huge_memory.o:
before=58419 after=58446, chg +0.05% (+27 bytes).

Signed-off-by: Kairui Song <kasong@tencent.com>
---
Kairui Song (13):
      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: 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: move anon_vma and filemap management into split helpers
      mm/huge_memory: move memcg switch into the file split helper
      mm/huge_memory: allow splitting mappingless swap cache folios
      mm/huge_memory: clean up after-split folio freeing in __folio_split
      mm/huge_memory: lift order-0 restriction for swapcache split
      mm/huge_memory: count only swap cache refs in anon folio split

 mm/huge_memory.c | 583 ++++++++++++++++++++++++++++---------------------------
 mm/swap_state.c  |   3 +-
 2 files changed, 298 insertions(+), 288 deletions(-)
---
base-commit: 7e4ead2558f28da16d82a8f5845eee44555ff6ba
change-id: 20260804-swap-thp-cleanup-6ce2be6cf3b8

Best regards,
--  
Kairui Song <kasong@tencent.com>




^ permalink raw reply	[flat|nested] 45+ messages in thread

* [PATCH RFC 00/13] mm/huge_memory: clean up folio split and lift swapcache split limits
@ 2026-08-07 21:17 ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

This series clean up the split code, add better swap cache split support
for mappingless, large order, uniform and non-uniform split.  Generic
performance is on par or slightly better, and stack usage is reduced.

The swap cache infrastructure can handle non-uniform or high order folio
replace, so there is no reason for either restriction from the THP side.
What stands in the way is the mixed anon/file folio split routine,
which makes lifting the restrictions hard to follow, and it already
carries some buggy or redundant checks.

So this series cleans up the split path and separates anon and file
splitting into two helpers.  The file split path never sees a swap
cache folio, and that is now enforced up front: a folio that is both
in the page cache and the swap cache can only be a shmem folio, which
remains unsupported and is rejected early.  That helps to rule out swap
cache handling in that part completely.  Only the anon split path
handles swap cache folios, with an anon mapping or mappingless:
either way the splitting is similar, and non-uniform split is
supported as well.

Order-1 is still forbidden for swap cache splitting.  In theory it is
doable for shmem swap cache folios, but a mappingless swap cache
folio cannot currently be told apart from a shmem one, so forbid it
for all swap cache folios for now.

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 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;
mean +- stddev of the per-run averages, 135 split calls per run):

  base:    24 runs, 69.6 +- 0.7 us per __folio_split()
  patched: 26 runs, 68.8 +- 1.3 us per __folio_split()

The patched kernel is consistently ~1% faster; with this sample
count the difference is outside run-to-run noise.

On x86-64 with gcc 12 (-fstack-usage), the stack frame of
__folio_split() shrinks from 240 to 96 bytes, and the worst-case
split call chain from ~544 to ~384 (anon) or ~464 (file) bytes.

Bloat-o-meter shows a tiny growth of huge_memory.o:
before=58419 after=58446, chg +0.05% (+27 bytes).

Signed-off-by: Kairui Song <kasong@tencent.com>
---
Kairui Song (13):
      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: 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: move anon_vma and filemap management into split helpers
      mm/huge_memory: move memcg switch into the file split helper
      mm/huge_memory: allow splitting mappingless swap cache folios
      mm/huge_memory: clean up after-split folio freeing in __folio_split
      mm/huge_memory: lift order-0 restriction for swapcache split
      mm/huge_memory: count only swap cache refs in anon folio split

 mm/huge_memory.c | 583 ++++++++++++++++++++++++++++---------------------------
 mm/swap_state.c  |   3 +-
 2 files changed, 298 insertions(+), 288 deletions(-)
---
base-commit: 7e4ead2558f28da16d82a8f5845eee44555ff6ba
change-id: 20260804-swap-thp-cleanup-6ce2be6cf3b8

Best regards,
--  
Kairui Song <kasong@tencent.com>


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [PATCH RFC 01/13] mm/swap: fix off-by-one in swap cache replace sanity check
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

The DEBUG_VM sanity check in __swap_cache_replace_folio() iterates
the old folio's range with "while (ci_off++ < ci_end)", so the loop
body runs on the already-incremented offset: the first entry is
skipped and one entry past the range is read.  For a folio split
that entry belongs to the first after-split folio and was just
repointed by the replacement loop above, so the check would warn
spuriously whenever sub-folio orders differ from the head folio's,
as non-uniform swapcache splits now do.

Use the same do-while pattern as the replacement loop.

Fixes: 8578e0c00dcf ("mm, swap: use the swap table for the swap cache and switch API")
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/swap_state.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/mm/swap_state.c b/mm/swap_state.c
index 5be825911e64..f1405e5b813e 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -388,8 +388,9 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
 	    folio_order(old) != folio_order(new)) {
 		ci_off = swp_cluster_offset(old->swap);
 		ci_end = ci_off + folio_nr_pages(old);
-		while (ci_off++ < ci_end)
+		do {
 			WARN_ON_ONCE(swp_tb_to_folio(__swap_table_get(ci, ci_off)) != old);
+		} while (++ci_off < ci_end);
 	}
 }
 

-- 
2.55.0




^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 01/13] mm/swap: fix off-by-one in swap cache replace sanity check
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

The DEBUG_VM sanity check in __swap_cache_replace_folio() iterates
the old folio's range with "while (ci_off++ < ci_end)", so the loop
body runs on the already-incremented offset: the first entry is
skipped and one entry past the range is read.  For a folio split
that entry belongs to the first after-split folio and was just
repointed by the replacement loop above, so the check would warn
spuriously whenever sub-folio orders differ from the head folio's,
as non-uniform swapcache splits now do.

Use the same do-while pattern as the replacement loop.

Fixes: 8578e0c00dcf ("mm, swap: use the swap table for the swap cache and switch API")
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/swap_state.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/mm/swap_state.c b/mm/swap_state.c
index 5be825911e64..f1405e5b813e 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -388,8 +388,9 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
 	    folio_order(old) != folio_order(new)) {
 		ci_off = swp_cluster_offset(old->swap);
 		ci_end = ci_off + folio_nr_pages(old);
-		while (ci_off++ < ci_end)
+		do {
 			WARN_ON_ONCE(swp_tb_to_folio(__swap_table_get(ci, ci_off)) != old);
+		} while (++ci_off < ci_end);
 	}
 }
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 02/13] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

A folio in the swap cache cannot be split if it has a mapping (shmem).
The split code only checks for this in __folio_freeze_and_split_unmapped,
after the folio ref has been frozen and the NR_SHMEM_THPS/NR_FILE_THPS
counters have been decremented, and returns -EINVAL without unfreezing
the folio or restoring the counters. That error path is fragile: if it
is ever taken, the folio is left frozen and stuck, the counters are
skewed, and the VM_WARN_ON_ONCE_FOLIO would fire for a state that is
actually legitimate.

Check for this case up front in folio_check_splittable and return
-EINVAL before any state is modified. Under DEBUG_VM, the existing
"Tried to split an unsplittable folio" warning in __folio_split
reports the rejection.

Fixes: 00527733d0dc ("mm/huge_memory: add two new (not yet used) functions for folio_split()")
Fixes: 714b056c8321 ("mm/huge_memory: convert VM_BUG* to VM_WARN* in __folio_split")
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 26 ++++++++++++++++----------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ced400f72d43..2fa72158e063 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3878,6 +3878,9 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 int folio_check_splittable(struct folio *folio, unsigned int new_order,
 			   enum split_type split_type)
 {
+	bool is_anon = folio_test_anon(folio);
+	bool is_swapcache = folio_test_swapcache(folio);
+
 	VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
 	/*
 	 * Folios that just got truncated cannot get split. Signal to the
@@ -3886,11 +3889,11 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	 * TODO: this will also currently refuse folios without a mapping in the
 	 * swapcache (shmem or to-be-anon folios).
 	 */
-	if (!folio->mapping && !folio_test_anon(folio))
+	if (!folio->mapping && !is_anon)
 		return -EBUSY;
 
 	/* order-1 is not supported for anonymous THP. */
-	if (folio_test_anon(folio) && new_order == 1)
+	if (is_anon && new_order == 1)
 		return -EINVAL;
 
 	/*
@@ -3901,7 +3904,7 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	 * swapcache folio split. Only uniform split to order-0 can be used
 	 * here.
 	 */
-	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && folio_test_swapcache(folio)) {
+	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && is_swapcache) {
 		return -EINVAL;
 	}
 
@@ -3911,6 +3914,15 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	if (folio_test_writeback(folio))
 		return -EBUSY;
 
+	/*
+	 * A non-anon swapcache folio that still has a mapping should only
+	 * be a shmem folio under IO, there is little benefit in splitting
+	 * them hence not supported. Reject it here up front: the split
+	 * routine cannot back out cleanly once the folio ref is frozen.
+	 */
+	if (!is_anon && is_swapcache && folio->mapping)
+		return -EINVAL;
+
 	return 0;
 }
 
@@ -3983,14 +3995,8 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 			}
 		}
 
-		if (folio_test_swapcache(folio)) {
-			if (mapping) {
-				VM_WARN_ON_ONCE_FOLIO(mapping, folio);
-				return -EINVAL;
-			}
-
+		if (folio_test_swapcache(folio))
 			ci = swap_cluster_get_and_lock(folio);
-		}
 
 		/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
 		if (do_lru)

-- 
2.55.0




^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 02/13] mm/huge_memory: fix rejection of swap cache folios with a mapping
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

A folio in the swap cache cannot be split if it has a mapping (shmem).
The split code only checks for this in __folio_freeze_and_split_unmapped,
after the folio ref has been frozen and the NR_SHMEM_THPS/NR_FILE_THPS
counters have been decremented, and returns -EINVAL without unfreezing
the folio or restoring the counters. That error path is fragile: if it
is ever taken, the folio is left frozen and stuck, the counters are
skewed, and the VM_WARN_ON_ONCE_FOLIO would fire for a state that is
actually legitimate.

Check for this case up front in folio_check_splittable and return
-EINVAL before any state is modified. Under DEBUG_VM, the existing
"Tried to split an unsplittable folio" warning in __folio_split
reports the rejection.

Fixes: 00527733d0dc ("mm/huge_memory: add two new (not yet used) functions for folio_split()")
Fixes: 714b056c8321 ("mm/huge_memory: convert VM_BUG* to VM_WARN* in __folio_split")
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 26 ++++++++++++++++----------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ced400f72d43..2fa72158e063 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3878,6 +3878,9 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 int folio_check_splittable(struct folio *folio, unsigned int new_order,
 			   enum split_type split_type)
 {
+	bool is_anon = folio_test_anon(folio);
+	bool is_swapcache = folio_test_swapcache(folio);
+
 	VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
 	/*
 	 * Folios that just got truncated cannot get split. Signal to the
@@ -3886,11 +3889,11 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	 * TODO: this will also currently refuse folios without a mapping in the
 	 * swapcache (shmem or to-be-anon folios).
 	 */
-	if (!folio->mapping && !folio_test_anon(folio))
+	if (!folio->mapping && !is_anon)
 		return -EBUSY;
 
 	/* order-1 is not supported for anonymous THP. */
-	if (folio_test_anon(folio) && new_order == 1)
+	if (is_anon && new_order == 1)
 		return -EINVAL;
 
 	/*
@@ -3901,7 +3904,7 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	 * swapcache folio split. Only uniform split to order-0 can be used
 	 * here.
 	 */
-	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && folio_test_swapcache(folio)) {
+	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && is_swapcache) {
 		return -EINVAL;
 	}
 
@@ -3911,6 +3914,15 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	if (folio_test_writeback(folio))
 		return -EBUSY;
 
+	/*
+	 * A non-anon swapcache folio that still has a mapping should only
+	 * be a shmem folio under IO, there is little benefit in splitting
+	 * them hence not supported. Reject it here up front: the split
+	 * routine cannot back out cleanly once the folio ref is frozen.
+	 */
+	if (!is_anon && is_swapcache && folio->mapping)
+		return -EINVAL;
+
 	return 0;
 }
 
@@ -3983,14 +3995,8 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 			}
 		}
 
-		if (folio_test_swapcache(folio)) {
-			if (mapping) {
-				VM_WARN_ON_ONCE_FOLIO(mapping, folio);
-				return -EINVAL;
-			}
-
+		if (folio_test_swapcache(folio))
 			ci = swap_cluster_get_and_lock(folio);
-		}
 
 		/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
 		if (do_lru)

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 03/13] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

Invert the folio_ref_freeze() success check in
__folio_freeze_and_split_unmapped() to return early on failure, which
removes one level of indentation from the entire success path.

This is a pure refactoring with no functional change.  It prepares the
function to be split into separate helpers for anonymous and
file-backed folios in a later patch.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 181 +++++++++++++++++++++++++++----------------------------
 1 file changed, 90 insertions(+), 91 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 2fa72158e063..cf8f90b94e42 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3941,9 +3941,11 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 					     pgoff_t end, int *nr_shmem_dropped)
 {
 	struct folio *end_folio = folio_next(folio);
+	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio, *next;
 	int old_order = folio_order(folio);
 	struct list_lru_one *lru;
+	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
@@ -3964,122 +3966,119 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 		lru = list_lru_lock(&deferred_split_lru,
 				    folio_nid(folio), &memcg);
 	}
-	if (folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
-		struct swap_cluster_info *ci = NULL;
-		struct lruvec *lruvec;
 
+	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
 		if (dequeue_deferred) {
-			__list_lru_del(&deferred_split_lru, lru,
-				       &folio->_deferred_list, folio_nid(folio));
-			if (folio_test_partially_mapped(folio)) {
-				folio_clear_partially_mapped(folio);
-				mod_mthp_stat(old_order,
-					MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
-			}
 			list_lru_unlock(lru);
 			rcu_read_unlock();
 		}
+		return -EAGAIN;
+	}
 
-		if (mapping) {
-			int nr = folio_nr_pages(folio);
-
-			if (folio_test_pmd_mappable(folio) &&
-			    new_order < HPAGE_PMD_ORDER) {
-				if (folio_test_swapbacked(folio)) {
-					lruvec_stat_mod_folio(folio,
-							NR_SHMEM_THPS, -nr);
-				} else {
-					lruvec_stat_mod_folio(folio,
-							NR_FILE_THPS, -nr);
-				}
-			}
+	if (dequeue_deferred) {
+		__list_lru_del(&deferred_split_lru, lru,
+			       &folio->_deferred_list, folio_nid(folio));
+		if (folio_test_partially_mapped(folio)) {
+			folio_clear_partially_mapped(folio);
+			mod_mthp_stat(old_order,
+				      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
 		}
+		list_lru_unlock(lru);
+		rcu_read_unlock();
+	}
 
-		if (folio_test_swapcache(folio))
-			ci = swap_cluster_get_and_lock(folio);
-
-		/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
-		if (do_lru)
-			lruvec = folio_lruvec_lock(folio);
-
-		ret = __split_unmapped_folio(folio, new_order, split_at, xas,
-					     mapping, split_type);
+	if (mapping) {
+		int nr = folio_nr_pages(folio);
 
-		/*
-		 * Unfreeze after-split folios and put them back to the right
-		 * list. @folio should be kept frozon until page cache
-		 * entries are updated with all the other after-split folios
-		 * to prevent others seeing stale page cache entries.
-		 * As a result, new_folio starts from the next folio of
-		 * @folio.
-		 */
-		for (new_folio = folio_next(folio); new_folio != end_folio;
-		     new_folio = next) {
-			unsigned long nr_pages = folio_nr_pages(new_folio);
+		if (folio_test_pmd_mappable(folio) &&
+		    new_order < HPAGE_PMD_ORDER) {
+			if (folio_test_swapbacked(folio)) {
+				lruvec_stat_mod_folio(folio,
+						      NR_SHMEM_THPS, -nr);
+			} else {
+				lruvec_stat_mod_folio(folio,
+						      NR_FILE_THPS, -nr);
+			}
+		}
+	}
 
-			next = folio_next(new_folio);
+	if (folio_test_swapcache(folio))
+		ci = swap_cluster_get_and_lock(folio);
 
-			zone_device_private_split_cb(folio, new_folio);
+	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	if (do_lru)
+		lruvec = folio_lruvec_lock(folio);
 
-			folio_ref_unfreeze(new_folio,
-					   folio_cache_ref_count(new_folio) + 1);
+	ret = __split_unmapped_folio(folio, new_order, split_at, xas,
+				     mapping, split_type);
 
-			if (do_lru)
-				lru_add_split_folio(folio, new_folio, lruvec, list);
+	/*
+	 * Unfreeze after-split folios and put them back to the right
+	 * list. @folio should be kept frozon until page cache
+	 * entries are updated with all the other after-split folios
+	 * to prevent others seeing stale page cache entries.
+	 * As a result, new_folio starts from the next folio of
+	 * @folio.
+	 */
+	for (new_folio = folio_next(folio); new_folio != end_folio;
+	     new_folio = next) {
+		unsigned long nr_pages = folio_nr_pages(new_folio);
 
-			/*
-			 * Anonymous folio with swap cache.
-			 * NOTE: shmem in swap cache is not supported yet.
-			 */
-			if (ci) {
-				__swap_cache_replace_folio(ci, folio, new_folio);
-				continue;
-			}
+		next = folio_next(new_folio);
 
-			/* Anonymous folio without swap cache */
-			if (!mapping)
-				continue;
+		zone_device_private_split_cb(folio, new_folio);
 
-			/* Add the new folio to the page cache. */
-			if (new_folio->index < end) {
-				__xa_store(&mapping->i_pages, new_folio->index,
-					   new_folio, 0);
-				continue;
-			}
+		folio_ref_unfreeze(new_folio,
+				   folio_cache_ref_count(new_folio) + 1);
 
-			VM_WARN_ON_ONCE(!nr_shmem_dropped);
-			/* Drop folio beyond EOF: ->index >= end */
-			if (shmem_mapping(mapping) && nr_shmem_dropped)
-				*nr_shmem_dropped += nr_pages;
-			else if (folio_test_clear_dirty(new_folio))
-				folio_account_cleaned(
-					new_folio, inode_to_wb(mapping->host));
-			__filemap_remove_folio(new_folio, NULL);
-			folio_put_refs(new_folio, nr_pages);
-		}
+		if (do_lru)
+			lru_add_split_folio(folio, new_folio, lruvec, list);
 
-		zone_device_private_split_cb(folio, NULL);
 		/*
-		 * Unfreeze @folio only after all page cache entries, which
-		 * used to point to it, have been updated with new folios.
-		 * Otherwise, a parallel folio_try_get() can grab @folio
-		 * and its caller can see stale page cache entries.
+		 * Anonymous folio with swap cache.
+		 * NOTE: shmem in swap cache is not supported yet.
 		 */
-		folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+		if (ci) {
+			__swap_cache_replace_folio(ci, folio, new_folio);
+			continue;
+		}
 
-		if (do_lru)
-			lruvec_unlock(lruvec);
+		/* Anonymous folio without swap cache */
+		if (!mapping)
+			continue;
 
-		if (ci)
-			swap_cluster_unlock(ci);
-	} else {
-		if (dequeue_deferred) {
-			list_lru_unlock(lru);
-			rcu_read_unlock();
+		/* Add the new folio to the page cache. */
+		if (new_folio->index < end) {
+			__xa_store(&mapping->i_pages, new_folio->index,
+				   new_folio, 0);
+			continue;
 		}
-		return -EAGAIN;
+
+		VM_WARN_ON_ONCE(!nr_shmem_dropped);
+		/* Drop folio beyond EOF: ->index >= end */
+		if (shmem_mapping(mapping) && nr_shmem_dropped)
+			*nr_shmem_dropped += nr_pages;
+		else if (folio_test_clear_dirty(new_folio))
+			folio_account_cleaned(new_folio,
+					      inode_to_wb(mapping->host));
+		__filemap_remove_folio(new_folio, NULL);
+		folio_put_refs(new_folio, nr_pages);
 	}
 
+	zone_device_private_split_cb(folio, NULL);
+	/*
+	 * Unfreeze @folio only after all page cache entries, which
+	 * used to point to it, have been updated with new folios.
+	 * Otherwise, a parallel folio_try_get() can grab @folio
+	 * and its caller can see stale page cache entries.
+	 */
+	folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+
+	if (do_lru)
+		lruvec_unlock(lruvec);
+	if (ci)
+		swap_cluster_unlock(ci);
+
 	return ret;
 }
 

-- 
2.55.0




^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 03/13] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

Invert the folio_ref_freeze() success check in
__folio_freeze_and_split_unmapped() to return early on failure, which
removes one level of indentation from the entire success path.

This is a pure refactoring with no functional change.  It prepares the
function to be split into separate helpers for anonymous and
file-backed folios in a later patch.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 181 +++++++++++++++++++++++++++----------------------------
 1 file changed, 90 insertions(+), 91 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 2fa72158e063..cf8f90b94e42 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3941,9 +3941,11 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 					     pgoff_t end, int *nr_shmem_dropped)
 {
 	struct folio *end_folio = folio_next(folio);
+	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio, *next;
 	int old_order = folio_order(folio);
 	struct list_lru_one *lru;
+	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
@@ -3964,122 +3966,119 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 		lru = list_lru_lock(&deferred_split_lru,
 				    folio_nid(folio), &memcg);
 	}
-	if (folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
-		struct swap_cluster_info *ci = NULL;
-		struct lruvec *lruvec;
 
+	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
 		if (dequeue_deferred) {
-			__list_lru_del(&deferred_split_lru, lru,
-				       &folio->_deferred_list, folio_nid(folio));
-			if (folio_test_partially_mapped(folio)) {
-				folio_clear_partially_mapped(folio);
-				mod_mthp_stat(old_order,
-					MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
-			}
 			list_lru_unlock(lru);
 			rcu_read_unlock();
 		}
+		return -EAGAIN;
+	}
 
-		if (mapping) {
-			int nr = folio_nr_pages(folio);
-
-			if (folio_test_pmd_mappable(folio) &&
-			    new_order < HPAGE_PMD_ORDER) {
-				if (folio_test_swapbacked(folio)) {
-					lruvec_stat_mod_folio(folio,
-							NR_SHMEM_THPS, -nr);
-				} else {
-					lruvec_stat_mod_folio(folio,
-							NR_FILE_THPS, -nr);
-				}
-			}
+	if (dequeue_deferred) {
+		__list_lru_del(&deferred_split_lru, lru,
+			       &folio->_deferred_list, folio_nid(folio));
+		if (folio_test_partially_mapped(folio)) {
+			folio_clear_partially_mapped(folio);
+			mod_mthp_stat(old_order,
+				      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
 		}
+		list_lru_unlock(lru);
+		rcu_read_unlock();
+	}
 
-		if (folio_test_swapcache(folio))
-			ci = swap_cluster_get_and_lock(folio);
-
-		/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
-		if (do_lru)
-			lruvec = folio_lruvec_lock(folio);
-
-		ret = __split_unmapped_folio(folio, new_order, split_at, xas,
-					     mapping, split_type);
+	if (mapping) {
+		int nr = folio_nr_pages(folio);
 
-		/*
-		 * Unfreeze after-split folios and put them back to the right
-		 * list. @folio should be kept frozon until page cache
-		 * entries are updated with all the other after-split folios
-		 * to prevent others seeing stale page cache entries.
-		 * As a result, new_folio starts from the next folio of
-		 * @folio.
-		 */
-		for (new_folio = folio_next(folio); new_folio != end_folio;
-		     new_folio = next) {
-			unsigned long nr_pages = folio_nr_pages(new_folio);
+		if (folio_test_pmd_mappable(folio) &&
+		    new_order < HPAGE_PMD_ORDER) {
+			if (folio_test_swapbacked(folio)) {
+				lruvec_stat_mod_folio(folio,
+						      NR_SHMEM_THPS, -nr);
+			} else {
+				lruvec_stat_mod_folio(folio,
+						      NR_FILE_THPS, -nr);
+			}
+		}
+	}
 
-			next = folio_next(new_folio);
+	if (folio_test_swapcache(folio))
+		ci = swap_cluster_get_and_lock(folio);
 
-			zone_device_private_split_cb(folio, new_folio);
+	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	if (do_lru)
+		lruvec = folio_lruvec_lock(folio);
 
-			folio_ref_unfreeze(new_folio,
-					   folio_cache_ref_count(new_folio) + 1);
+	ret = __split_unmapped_folio(folio, new_order, split_at, xas,
+				     mapping, split_type);
 
-			if (do_lru)
-				lru_add_split_folio(folio, new_folio, lruvec, list);
+	/*
+	 * Unfreeze after-split folios and put them back to the right
+	 * list. @folio should be kept frozon until page cache
+	 * entries are updated with all the other after-split folios
+	 * to prevent others seeing stale page cache entries.
+	 * As a result, new_folio starts from the next folio of
+	 * @folio.
+	 */
+	for (new_folio = folio_next(folio); new_folio != end_folio;
+	     new_folio = next) {
+		unsigned long nr_pages = folio_nr_pages(new_folio);
 
-			/*
-			 * Anonymous folio with swap cache.
-			 * NOTE: shmem in swap cache is not supported yet.
-			 */
-			if (ci) {
-				__swap_cache_replace_folio(ci, folio, new_folio);
-				continue;
-			}
+		next = folio_next(new_folio);
 
-			/* Anonymous folio without swap cache */
-			if (!mapping)
-				continue;
+		zone_device_private_split_cb(folio, new_folio);
 
-			/* Add the new folio to the page cache. */
-			if (new_folio->index < end) {
-				__xa_store(&mapping->i_pages, new_folio->index,
-					   new_folio, 0);
-				continue;
-			}
+		folio_ref_unfreeze(new_folio,
+				   folio_cache_ref_count(new_folio) + 1);
 
-			VM_WARN_ON_ONCE(!nr_shmem_dropped);
-			/* Drop folio beyond EOF: ->index >= end */
-			if (shmem_mapping(mapping) && nr_shmem_dropped)
-				*nr_shmem_dropped += nr_pages;
-			else if (folio_test_clear_dirty(new_folio))
-				folio_account_cleaned(
-					new_folio, inode_to_wb(mapping->host));
-			__filemap_remove_folio(new_folio, NULL);
-			folio_put_refs(new_folio, nr_pages);
-		}
+		if (do_lru)
+			lru_add_split_folio(folio, new_folio, lruvec, list);
 
-		zone_device_private_split_cb(folio, NULL);
 		/*
-		 * Unfreeze @folio only after all page cache entries, which
-		 * used to point to it, have been updated with new folios.
-		 * Otherwise, a parallel folio_try_get() can grab @folio
-		 * and its caller can see stale page cache entries.
+		 * Anonymous folio with swap cache.
+		 * NOTE: shmem in swap cache is not supported yet.
 		 */
-		folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+		if (ci) {
+			__swap_cache_replace_folio(ci, folio, new_folio);
+			continue;
+		}
 
-		if (do_lru)
-			lruvec_unlock(lruvec);
+		/* Anonymous folio without swap cache */
+		if (!mapping)
+			continue;
 
-		if (ci)
-			swap_cluster_unlock(ci);
-	} else {
-		if (dequeue_deferred) {
-			list_lru_unlock(lru);
-			rcu_read_unlock();
+		/* Add the new folio to the page cache. */
+		if (new_folio->index < end) {
+			__xa_store(&mapping->i_pages, new_folio->index,
+				   new_folio, 0);
+			continue;
 		}
-		return -EAGAIN;
+
+		VM_WARN_ON_ONCE(!nr_shmem_dropped);
+		/* Drop folio beyond EOF: ->index >= end */
+		if (shmem_mapping(mapping) && nr_shmem_dropped)
+			*nr_shmem_dropped += nr_pages;
+		else if (folio_test_clear_dirty(new_folio))
+			folio_account_cleaned(new_folio,
+					      inode_to_wb(mapping->host));
+		__filemap_remove_folio(new_folio, NULL);
+		folio_put_refs(new_folio, nr_pages);
 	}
 
+	zone_device_private_split_cb(folio, NULL);
+	/*
+	 * Unfreeze @folio only after all page cache entries, which
+	 * used to point to it, have been updated with new folios.
+	 * Otherwise, a parallel folio_try_get() can grab @folio
+	 * and its caller can see stale page cache entries.
+	 */
+	folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+
+	if (do_lru)
+		lruvec_unlock(lruvec);
+	if (ci)
+		swap_cluster_unlock(ci);
+
 	return ret;
 }
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 04/13] mm/huge_memory: split the routine for splitting anon and file folio
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

No functional change intended. Before adding more logic, split
__folio_freeze_and_split_unmapped() into an anon and a file variant so
each path can evolve independently. The two paths shared little beyond
the folio freeze call, the LRU locking, and the unfreeze skeleton, but
differed in all other per-folio bookkeeping and routines.

While splitting, some cleanups become easy to apply, and helped dropping
a few now redundant checks.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 121 ++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 76 insertions(+), 45 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index cf8f90b94e42..56a356c30f30 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3934,22 +3934,19 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
 	return folio_nr_pages(folio);
 }
 
-static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int new_order,
-					     struct page *split_at, struct xa_state *xas,
-					     struct address_space *mapping, bool do_lru,
-					     struct list_head *list, enum split_type split_type,
-					     pgoff_t end, int *nr_shmem_dropped)
+static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int new_order,
+					      struct page *split_at, bool do_lru,
+					      struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
 	struct swap_cluster_info *ci = NULL;
-	struct folio *new_folio, *next;
+	struct folio *new_folio;
 	int old_order = folio_order(folio);
 	struct list_lru_one *lru;
 	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
-	VM_WARN_ON_ONCE(!mapping && end);
 	/*
 	 * If this folio can be on the deferred split queue, lock out
 	 * the shrinker before freezing the ref. If the shrinker sees
@@ -3957,7 +3954,7 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 	 * lock and must clean up the LRU state - the same dequeue we
 	 * will do below as part of the split.
 	 */
-	dequeue_deferred = folio_test_anon(folio) && old_order > 1;
+	dequeue_deferred = old_order > 1;
 	if (dequeue_deferred) {
 		struct mem_cgroup *memcg;
 
@@ -3987,24 +3984,73 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 		rcu_read_unlock();
 	}
 
-	if (mapping) {
+	if (folio_test_swapcache(folio))
+		ci = swap_cluster_get_and_lock(folio);
+
+	if (do_lru)
+		lruvec = folio_lruvec_lock(folio);
+
+	ret = __split_unmapped_folio(folio, new_order, split_at, NULL,
+				     NULL, split_type);
+
+	/*
+	 * Unfreeze the after-split folios and put them back to the right
+	 * place, keeping the head @folio frozen until the end. While the
+	 * folio is in the swap cache, the sub entries must be updated with
+	 * their after-split folios before the head is unfrozen, so a
+	 * concurrent swap_cache_get_folio() cannot return the head folio
+	 * for a sub entry. Keeping the head frozen throughout also stops a
+	 * parallel folio_try_get() from observing a partially split folio.
+	 */
+	for (new_folio = folio_next(folio); new_folio != end_folio;
+	     new_folio = folio_next(new_folio)) {
+		zone_device_private_split_cb(folio, new_folio);
+		folio_ref_unfreeze(new_folio,
+				   folio_cache_ref_count(new_folio) + 1);
+		if (do_lru)
+			lru_add_split_folio(folio, new_folio, lruvec, list);
+		if (ci)
+			__swap_cache_replace_folio(ci, folio, new_folio);
+	}
+
+	zone_device_private_split_cb(folio, NULL);
+	folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+
+	if (do_lru)
+		lruvec_unlock(lruvec);
+	if (ci)
+		swap_cluster_unlock(ci);
+
+	return ret;
+}
+
+static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int new_order,
+					      struct page *split_at, struct xa_state *xas,
+					      struct address_space *mapping, bool do_lru,
+					      struct list_head *list, enum split_type split_type,
+					      pgoff_t end, int *nr_shmem_dropped)
+{
+	struct folio *end_folio = folio_next(folio);
+	struct folio *new_folio, *next;
+	struct lruvec *lruvec;
+	int ret;
+
+	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1))
+		return -EAGAIN;
+
+	if (folio_test_pmd_mappable(folio) &&
+	    new_order < HPAGE_PMD_ORDER) {
 		int nr = folio_nr_pages(folio);
 
-		if (folio_test_pmd_mappable(folio) &&
-		    new_order < HPAGE_PMD_ORDER) {
-			if (folio_test_swapbacked(folio)) {
-				lruvec_stat_mod_folio(folio,
-						      NR_SHMEM_THPS, -nr);
-			} else {
-				lruvec_stat_mod_folio(folio,
-						      NR_FILE_THPS, -nr);
-			}
+		if (folio_test_swapbacked(folio)) {
+			lruvec_stat_mod_folio(folio,
+					      NR_SHMEM_THPS, -nr);
+		} else {
+			lruvec_stat_mod_folio(folio,
+					      NR_FILE_THPS, -nr);
 		}
 	}
 
-	if (folio_test_swapcache(folio))
-		ci = swap_cluster_get_and_lock(folio);
-
 	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
 	if (do_lru)
 		lruvec = folio_lruvec_lock(folio);
@@ -4014,7 +4060,7 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 
 	/*
 	 * Unfreeze after-split folios and put them back to the right
-	 * list. @folio should be kept frozon until page cache
+	 * list. @folio should be kept frozen until page cache
 	 * entries are updated with all the other after-split folios
 	 * to prevent others seeing stale page cache entries.
 	 * As a result, new_folio starts from the next folio of
@@ -4026,27 +4072,12 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 
 		next = folio_next(new_folio);
 
-		zone_device_private_split_cb(folio, new_folio);
-
 		folio_ref_unfreeze(new_folio,
 				   folio_cache_ref_count(new_folio) + 1);
 
 		if (do_lru)
 			lru_add_split_folio(folio, new_folio, lruvec, list);
 
-		/*
-		 * Anonymous folio with swap cache.
-		 * NOTE: shmem in swap cache is not supported yet.
-		 */
-		if (ci) {
-			__swap_cache_replace_folio(ci, folio, new_folio);
-			continue;
-		}
-
-		/* Anonymous folio without swap cache */
-		if (!mapping)
-			continue;
-
 		/* Add the new folio to the page cache. */
 		if (new_folio->index < end) {
 			__xa_store(&mapping->i_pages, new_folio->index,
@@ -4065,7 +4096,6 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 		folio_put_refs(new_folio, nr_pages);
 	}
 
-	zone_device_private_split_cb(folio, NULL);
 	/*
 	 * Unfreeze @folio only after all page cache entries, which
 	 * used to point to it, have been updated with new folios.
@@ -4076,8 +4106,6 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 
 	if (do_lru)
 		lruvec_unlock(lruvec);
-	if (ci)
-		swap_cluster_unlock(ci);
 
 	return ret;
 }
@@ -4231,10 +4259,14 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 			ret = -EAGAIN;
 			goto fail;
 		}
+		ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
+							 true, list, split_type, end,
+							 &nr_shmem_dropped);
+	} else {
+		ret = __folio_freeze_split_unmapped_anon(folio, new_order, split_at, true,
+							 list, split_type);
 	}
 
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, split_at, &xas, mapping,
-						true, list, split_type, end, &nr_shmem_dropped);
 fail:
 	if (mapping)
 		xas_unlock(&xas);
@@ -4334,9 +4366,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 		return -EAGAIN;
 
 	local_irq_disable();
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, &folio->page, NULL,
-						NULL, false, NULL, SPLIT_TYPE_UNIFORM,
-						0, NULL);
+	ret = __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
+						 false, NULL, SPLIT_TYPE_UNIFORM);
 	local_irq_enable();
 	return ret;
 }

-- 
2.55.0




^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 04/13] mm/huge_memory: split the routine for splitting anon and file folio
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

No functional change intended. Before adding more logic, split
__folio_freeze_and_split_unmapped() into an anon and a file variant so
each path can evolve independently. The two paths shared little beyond
the folio freeze call, the LRU locking, and the unfreeze skeleton, but
differed in all other per-folio bookkeeping and routines.

While splitting, some cleanups become easy to apply, and helped dropping
a few now redundant checks.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 121 ++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 76 insertions(+), 45 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index cf8f90b94e42..56a356c30f30 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3934,22 +3934,19 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
 	return folio_nr_pages(folio);
 }
 
-static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int new_order,
-					     struct page *split_at, struct xa_state *xas,
-					     struct address_space *mapping, bool do_lru,
-					     struct list_head *list, enum split_type split_type,
-					     pgoff_t end, int *nr_shmem_dropped)
+static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int new_order,
+					      struct page *split_at, bool do_lru,
+					      struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
 	struct swap_cluster_info *ci = NULL;
-	struct folio *new_folio, *next;
+	struct folio *new_folio;
 	int old_order = folio_order(folio);
 	struct list_lru_one *lru;
 	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
-	VM_WARN_ON_ONCE(!mapping && end);
 	/*
 	 * If this folio can be on the deferred split queue, lock out
 	 * the shrinker before freezing the ref. If the shrinker sees
@@ -3957,7 +3954,7 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 	 * lock and must clean up the LRU state - the same dequeue we
 	 * will do below as part of the split.
 	 */
-	dequeue_deferred = folio_test_anon(folio) && old_order > 1;
+	dequeue_deferred = old_order > 1;
 	if (dequeue_deferred) {
 		struct mem_cgroup *memcg;
 
@@ -3987,24 +3984,73 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 		rcu_read_unlock();
 	}
 
-	if (mapping) {
+	if (folio_test_swapcache(folio))
+		ci = swap_cluster_get_and_lock(folio);
+
+	if (do_lru)
+		lruvec = folio_lruvec_lock(folio);
+
+	ret = __split_unmapped_folio(folio, new_order, split_at, NULL,
+				     NULL, split_type);
+
+	/*
+	 * Unfreeze the after-split folios and put them back to the right
+	 * place, keeping the head @folio frozen until the end. While the
+	 * folio is in the swap cache, the sub entries must be updated with
+	 * their after-split folios before the head is unfrozen, so a
+	 * concurrent swap_cache_get_folio() cannot return the head folio
+	 * for a sub entry. Keeping the head frozen throughout also stops a
+	 * parallel folio_try_get() from observing a partially split folio.
+	 */
+	for (new_folio = folio_next(folio); new_folio != end_folio;
+	     new_folio = folio_next(new_folio)) {
+		zone_device_private_split_cb(folio, new_folio);
+		folio_ref_unfreeze(new_folio,
+				   folio_cache_ref_count(new_folio) + 1);
+		if (do_lru)
+			lru_add_split_folio(folio, new_folio, lruvec, list);
+		if (ci)
+			__swap_cache_replace_folio(ci, folio, new_folio);
+	}
+
+	zone_device_private_split_cb(folio, NULL);
+	folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+
+	if (do_lru)
+		lruvec_unlock(lruvec);
+	if (ci)
+		swap_cluster_unlock(ci);
+
+	return ret;
+}
+
+static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int new_order,
+					      struct page *split_at, struct xa_state *xas,
+					      struct address_space *mapping, bool do_lru,
+					      struct list_head *list, enum split_type split_type,
+					      pgoff_t end, int *nr_shmem_dropped)
+{
+	struct folio *end_folio = folio_next(folio);
+	struct folio *new_folio, *next;
+	struct lruvec *lruvec;
+	int ret;
+
+	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1))
+		return -EAGAIN;
+
+	if (folio_test_pmd_mappable(folio) &&
+	    new_order < HPAGE_PMD_ORDER) {
 		int nr = folio_nr_pages(folio);
 
-		if (folio_test_pmd_mappable(folio) &&
-		    new_order < HPAGE_PMD_ORDER) {
-			if (folio_test_swapbacked(folio)) {
-				lruvec_stat_mod_folio(folio,
-						      NR_SHMEM_THPS, -nr);
-			} else {
-				lruvec_stat_mod_folio(folio,
-						      NR_FILE_THPS, -nr);
-			}
+		if (folio_test_swapbacked(folio)) {
+			lruvec_stat_mod_folio(folio,
+					      NR_SHMEM_THPS, -nr);
+		} else {
+			lruvec_stat_mod_folio(folio,
+					      NR_FILE_THPS, -nr);
 		}
 	}
 
-	if (folio_test_swapcache(folio))
-		ci = swap_cluster_get_and_lock(folio);
-
 	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
 	if (do_lru)
 		lruvec = folio_lruvec_lock(folio);
@@ -4014,7 +4060,7 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 
 	/*
 	 * Unfreeze after-split folios and put them back to the right
-	 * list. @folio should be kept frozon until page cache
+	 * list. @folio should be kept frozen until page cache
 	 * entries are updated with all the other after-split folios
 	 * to prevent others seeing stale page cache entries.
 	 * As a result, new_folio starts from the next folio of
@@ -4026,27 +4072,12 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 
 		next = folio_next(new_folio);
 
-		zone_device_private_split_cb(folio, new_folio);
-
 		folio_ref_unfreeze(new_folio,
 				   folio_cache_ref_count(new_folio) + 1);
 
 		if (do_lru)
 			lru_add_split_folio(folio, new_folio, lruvec, list);
 
-		/*
-		 * Anonymous folio with swap cache.
-		 * NOTE: shmem in swap cache is not supported yet.
-		 */
-		if (ci) {
-			__swap_cache_replace_folio(ci, folio, new_folio);
-			continue;
-		}
-
-		/* Anonymous folio without swap cache */
-		if (!mapping)
-			continue;
-
 		/* Add the new folio to the page cache. */
 		if (new_folio->index < end) {
 			__xa_store(&mapping->i_pages, new_folio->index,
@@ -4065,7 +4096,6 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 		folio_put_refs(new_folio, nr_pages);
 	}
 
-	zone_device_private_split_cb(folio, NULL);
 	/*
 	 * Unfreeze @folio only after all page cache entries, which
 	 * used to point to it, have been updated with new folios.
@@ -4076,8 +4106,6 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 
 	if (do_lru)
 		lruvec_unlock(lruvec);
-	if (ci)
-		swap_cluster_unlock(ci);
 
 	return ret;
 }
@@ -4231,10 +4259,14 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 			ret = -EAGAIN;
 			goto fail;
 		}
+		ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
+							 true, list, split_type, end,
+							 &nr_shmem_dropped);
+	} else {
+		ret = __folio_freeze_split_unmapped_anon(folio, new_order, split_at, true,
+							 list, split_type);
 	}
 
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, split_at, &xas, mapping,
-						true, list, split_type, end, &nr_shmem_dropped);
 fail:
 	if (mapping)
 		xas_unlock(&xas);
@@ -4334,9 +4366,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 		return -EAGAIN;
 
 	local_irq_disable();
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, &folio->page, NULL,
-						NULL, false, NULL, SPLIT_TYPE_UNIFORM,
-						0, NULL);
+	ret = __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
+						 false, NULL, SPLIT_TYPE_UNIFORM);
 	local_irq_enable();
 	return ret;
 }

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 05/13] mm/huge_memory: consolidate irq and locking for folio split
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

Let each split helper handle its own locking instead of relying on
the caller, so both paths follow the same convention and __folio_split()
can drop its local irq handling and fail label, preparing for further
cleanup.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 52 ++++++++++++++++++++++++----------------------------
 1 file changed, 24 insertions(+), 28 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 56a356c30f30..ca9430a6f8d1 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3947,6 +3947,8 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 	bool dequeue_deferred;
 	int ret = 0;
 
+	local_irq_disable();
+
 	/*
 	 * If this folio can be on the deferred split queue, lock out
 	 * the shrinker before freezing the ref. If the shrinker sees
@@ -3969,6 +3971,7 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 			list_lru_unlock(lru);
 			rcu_read_unlock();
 		}
+		local_irq_enable();
 		return -EAGAIN;
 	}
 
@@ -4020,6 +4023,7 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 		lruvec_unlock(lruvec);
 	if (ci)
 		swap_cluster_unlock(ci);
+	local_irq_enable();
 
 	return ret;
 }
@@ -4035,8 +4039,21 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 	struct lruvec *lruvec;
 	int ret;
 
-	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1))
-		return -EAGAIN;
+	xas_lock_irq(xas);
+
+	/*
+	 * Check if the folio is present in page cache.
+	 * We assume all tail are present too, if folio is there.
+	 */
+	if (xas_load(xas) != folio) {
+		ret = -EAGAIN;
+		goto fail;
+	}
+
+	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
+		ret = -EAGAIN;
+		goto fail;
+	}
 
 	if (folio_test_pmd_mappable(folio) &&
 	    new_order < HPAGE_PMD_ORDER) {
@@ -4107,6 +4124,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 	if (do_lru)
 		lruvec_unlock(lruvec);
 
+fail:
+	xas_unlock_irq(xas);
 	return ret;
 }
 
@@ -4246,19 +4265,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	unmap_folio(folio);
 
-	/* block interrupt reentry in xa_lock and spinlock */
-	local_irq_disable();
-	if (mapping) {
-		/*
-		 * Check if the folio is present in page cache.
-		 * We assume all tail are present too, if folio is there.
-		 */
-		xas_lock(&xas);
-		xas_reset(&xas);
-		if (xas_load(&xas) != folio) {
-			ret = -EAGAIN;
-			goto fail;
-		}
+	if (!is_anon) {
 		ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
 							 true, list, split_type, end,
 							 &nr_shmem_dropped);
@@ -4267,12 +4274,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 							 list, split_type);
 	}
 
-fail:
-	if (mapping)
-		xas_unlock(&xas);
-
-	local_irq_enable();
-
 	if (nr_shmem_dropped)
 		shmem_uncharge(mapping->host, nr_shmem_dropped);
 
@@ -4355,8 +4356,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
  */
 int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 {
-	int ret = 0;
-
 	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
@@ -4365,11 +4364,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
 		return -EAGAIN;
 
-	local_irq_disable();
-	ret = __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
-						 false, NULL, SPLIT_TYPE_UNIFORM);
-	local_irq_enable();
-	return ret;
+	return __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
+						  false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*

-- 
2.55.0




^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 05/13] mm/huge_memory: consolidate irq and locking for folio split
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

Let each split helper handle its own locking instead of relying on
the caller, so both paths follow the same convention and __folio_split()
can drop its local irq handling and fail label, preparing for further
cleanup.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 52 ++++++++++++++++++++++++----------------------------
 1 file changed, 24 insertions(+), 28 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 56a356c30f30..ca9430a6f8d1 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3947,6 +3947,8 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 	bool dequeue_deferred;
 	int ret = 0;
 
+	local_irq_disable();
+
 	/*
 	 * If this folio can be on the deferred split queue, lock out
 	 * the shrinker before freezing the ref. If the shrinker sees
@@ -3969,6 +3971,7 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 			list_lru_unlock(lru);
 			rcu_read_unlock();
 		}
+		local_irq_enable();
 		return -EAGAIN;
 	}
 
@@ -4020,6 +4023,7 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 		lruvec_unlock(lruvec);
 	if (ci)
 		swap_cluster_unlock(ci);
+	local_irq_enable();
 
 	return ret;
 }
@@ -4035,8 +4039,21 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 	struct lruvec *lruvec;
 	int ret;
 
-	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1))
-		return -EAGAIN;
+	xas_lock_irq(xas);
+
+	/*
+	 * Check if the folio is present in page cache.
+	 * We assume all tail are present too, if folio is there.
+	 */
+	if (xas_load(xas) != folio) {
+		ret = -EAGAIN;
+		goto fail;
+	}
+
+	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
+		ret = -EAGAIN;
+		goto fail;
+	}
 
 	if (folio_test_pmd_mappable(folio) &&
 	    new_order < HPAGE_PMD_ORDER) {
@@ -4107,6 +4124,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 	if (do_lru)
 		lruvec_unlock(lruvec);
 
+fail:
+	xas_unlock_irq(xas);
 	return ret;
 }
 
@@ -4246,19 +4265,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	unmap_folio(folio);
 
-	/* block interrupt reentry in xa_lock and spinlock */
-	local_irq_disable();
-	if (mapping) {
-		/*
-		 * Check if the folio is present in page cache.
-		 * We assume all tail are present too, if folio is there.
-		 */
-		xas_lock(&xas);
-		xas_reset(&xas);
-		if (xas_load(&xas) != folio) {
-			ret = -EAGAIN;
-			goto fail;
-		}
+	if (!is_anon) {
 		ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
 							 true, list, split_type, end,
 							 &nr_shmem_dropped);
@@ -4267,12 +4274,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 							 list, split_type);
 	}
 
-fail:
-	if (mapping)
-		xas_unlock(&xas);
-
-	local_irq_enable();
-
 	if (nr_shmem_dropped)
 		shmem_uncharge(mapping->host, nr_shmem_dropped);
 
@@ -4355,8 +4356,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
  */
 int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 {
-	int ret = 0;
-
 	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
@@ -4365,11 +4364,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
 		return -EAGAIN;
 
-	local_irq_disable();
-	ret = __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
-						 false, NULL, SPLIT_TYPE_UNIFORM);
-	local_irq_enable();
-	return ret;
+	return __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
+						  false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 06/13] mm/huge_memory: move EOF trimming into the file split helper
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

Instead of receiving @end and @nr_shmem_dropped from the caller, the
file split helper now computes the EOF boundary and trims pages beyond
it itself, as this is only needed for file split.  This drops the
redundant parameter passing and sanity check.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 42 +++++++++++++++++++-----------------------
 1 file changed, 19 insertions(+), 23 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ca9430a6f8d1..72f5d0d24127 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4031,14 +4031,26 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int new_order,
 					      struct page *split_at, struct xa_state *xas,
 					      struct address_space *mapping, bool do_lru,
-					      struct list_head *list, enum split_type split_type,
-					      pgoff_t end, int *nr_shmem_dropped)
+					      struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
 	struct folio *new_folio, *next;
+	int nr_shmem_dropped = 0;
 	struct lruvec *lruvec;
+	pgoff_t end = 0;
 	int ret;
 
+	/*
+	 *__split_unmapped_folio() may need to trim off pages beyond
+	 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
+	 * seqlock, which cannot be nested inside the page tree lock.
+	 * So note end now: i_size itself may be changed at any moment,
+	 * but folio lock is good enough to serialize the trimming.
+	 */
+	end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
+	if (shmem_mapping(mapping))
+		end = shmem_fallocend(mapping->host, end);
+
 	xas_lock_irq(xas);
 
 	/*
@@ -4102,10 +4114,9 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 			continue;
 		}
 
-		VM_WARN_ON_ONCE(!nr_shmem_dropped);
 		/* Drop folio beyond EOF: ->index >= end */
-		if (shmem_mapping(mapping) && nr_shmem_dropped)
-			*nr_shmem_dropped += nr_pages;
+		if (shmem_mapping(mapping))
+			nr_shmem_dropped += nr_pages;
 		else if (folio_test_clear_dirty(new_folio))
 			folio_account_cleaned(new_folio,
 					      inode_to_wb(mapping->host));
@@ -4126,6 +4137,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 
 fail:
 	xas_unlock_irq(xas);
+	if (nr_shmem_dropped)
+		shmem_uncharge(mapping->host, nr_shmem_dropped);
 	return ret;
 }
 
@@ -4162,9 +4175,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
-	int nr_shmem_dropped = 0;
 	enum ttu_flags ttu_flags = 0;
-	pgoff_t end = 0;
 	int ret;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
@@ -4241,17 +4252,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 		anon_vma = NULL;
 		i_mmap_lock_read(mapping);
-
-		/*
-		 *__split_unmapped_folio() may need to trim off pages beyond
-		 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
-		 * seqlock, which cannot be nested inside the page tree lock.
-		 * So note end now: i_size itself may be changed at any moment,
-		 * but folio lock is good enough to serialize the trimming.
-		 */
-		end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
-		if (shmem_mapping(mapping))
-			end = shmem_fallocend(mapping->host, end);
 	}
 
 	/*
@@ -4267,16 +4267,12 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	if (!is_anon) {
 		ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
-							 true, list, split_type, end,
-							 &nr_shmem_dropped);
+							 true, list, split_type);
 	} else {
 		ret = __folio_freeze_split_unmapped_anon(folio, new_order, split_at, true,
 							 list, split_type);
 	}
 
-	if (nr_shmem_dropped)
-		shmem_uncharge(mapping->host, nr_shmem_dropped);
-
 	if (!ret && is_anon && !folio_is_device_private(folio))
 		ttu_flags = TTU_USE_SHARED_ZEROPAGE;
 

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 06/13] mm/huge_memory: move EOF trimming into the file split helper
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

Instead of receiving @end and @nr_shmem_dropped from the caller, the
file split helper now computes the EOF boundary and trims pages beyond
it itself, as this is only needed for file split.  This drops the
redundant parameter passing and sanity check.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 42 +++++++++++++++++++-----------------------
 1 file changed, 19 insertions(+), 23 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ca9430a6f8d1..72f5d0d24127 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4031,14 +4031,26 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int new_order,
 					      struct page *split_at, struct xa_state *xas,
 					      struct address_space *mapping, bool do_lru,
-					      struct list_head *list, enum split_type split_type,
-					      pgoff_t end, int *nr_shmem_dropped)
+					      struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
 	struct folio *new_folio, *next;
+	int nr_shmem_dropped = 0;
 	struct lruvec *lruvec;
+	pgoff_t end = 0;
 	int ret;
 
+	/*
+	 *__split_unmapped_folio() may need to trim off pages beyond
+	 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
+	 * seqlock, which cannot be nested inside the page tree lock.
+	 * So note end now: i_size itself may be changed at any moment,
+	 * but folio lock is good enough to serialize the trimming.
+	 */
+	end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
+	if (shmem_mapping(mapping))
+		end = shmem_fallocend(mapping->host, end);
+
 	xas_lock_irq(xas);
 
 	/*
@@ -4102,10 +4114,9 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 			continue;
 		}
 
-		VM_WARN_ON_ONCE(!nr_shmem_dropped);
 		/* Drop folio beyond EOF: ->index >= end */
-		if (shmem_mapping(mapping) && nr_shmem_dropped)
-			*nr_shmem_dropped += nr_pages;
+		if (shmem_mapping(mapping))
+			nr_shmem_dropped += nr_pages;
 		else if (folio_test_clear_dirty(new_folio))
 			folio_account_cleaned(new_folio,
 					      inode_to_wb(mapping->host));
@@ -4126,6 +4137,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 
 fail:
 	xas_unlock_irq(xas);
+	if (nr_shmem_dropped)
+		shmem_uncharge(mapping->host, nr_shmem_dropped);
 	return ret;
 }
 
@@ -4162,9 +4175,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
-	int nr_shmem_dropped = 0;
 	enum ttu_flags ttu_flags = 0;
-	pgoff_t end = 0;
 	int ret;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
@@ -4241,17 +4252,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 		anon_vma = NULL;
 		i_mmap_lock_read(mapping);
-
-		/*
-		 *__split_unmapped_folio() may need to trim off pages beyond
-		 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
-		 * seqlock, which cannot be nested inside the page tree lock.
-		 * So note end now: i_size itself may be changed at any moment,
-		 * but folio lock is good enough to serialize the trimming.
-		 */
-		end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
-		if (shmem_mapping(mapping))
-			end = shmem_fallocend(mapping->host, end);
 	}
 
 	/*
@@ -4267,16 +4267,12 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	if (!is_anon) {
 		ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
-							 true, list, split_type, end,
-							 &nr_shmem_dropped);
+							 true, list, split_type);
 	} else {
 		ret = __folio_freeze_split_unmapped_anon(folio, new_order, split_at, true,
 							 list, split_type);
 	}
 
-	if (nr_shmem_dropped)
-		shmem_uncharge(mapping->host, nr_shmem_dropped);
-
 	if (!ret && is_anon && !folio_is_device_private(folio))
 		ttu_flags = TTU_USE_SHARED_ZEROPAGE;
 

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 07/13] mm/huge_memory: move unmap and remap into the split helpers
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

To prepare for further cleanup, move the unmap/remap handling from
__folio_split() into the split helpers.  Only anon folios need to
be remapped, so remap_page() is now only called for anon splits and
the anon check in remap_page() is redundant and can be removed.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 51 ++++++++++++++++++++++++++-------------------------
 1 file changed, 26 insertions(+), 25 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 72f5d0d24127..c0115841d1a0 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3589,9 +3589,6 @@ static void remap_page(struct folio *folio, unsigned long nr, int flags)
 {
 	int i = 0;
 
-	/* If unmap_folio() uses try_to_migrate() on file, remove this check */
-	if (!folio_test_anon(folio))
-		return;
 	for (;;) {
 		remove_migration_ptes(folio, folio, TTU_RMAP_LOCKED | flags);
 		i += folio_nr_pages(folio);
@@ -3934,19 +3931,23 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
 	return folio_nr_pages(folio);
 }
 
-static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int new_order,
-					      struct page *split_at, bool do_lru,
-					      struct list_head *list, enum split_type split_type)
+static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new_order,
+					   struct page *split_at, bool do_lru, bool unmap,
+					   struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
 	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio;
 	int old_order = folio_order(folio);
+	enum ttu_flags ttu_flags = 0;
 	struct list_lru_one *lru;
 	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
+	if (unmap)
+		unmap_folio(folio);
+
 	local_irq_disable();
 
 	/*
@@ -3971,8 +3972,8 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 			list_lru_unlock(lru);
 			rcu_read_unlock();
 		}
-		local_irq_enable();
-		return -EAGAIN;
+		ret = -EAGAIN;
+		goto out_no_split;
 	}
 
 	if (dequeue_deferred) {
@@ -4023,15 +4024,21 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 		lruvec_unlock(lruvec);
 	if (ci)
 		swap_cluster_unlock(ci);
+out_no_split:
 	local_irq_enable();
+	if (unmap) {
+		if (!ret && !folio_is_device_private(folio))
+			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
+		remap_page(folio, 1 << old_order, ttu_flags);
+	}
 
 	return ret;
 }
 
-static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int new_order,
-					      struct page *split_at, struct xa_state *xas,
-					      struct address_space *mapping, bool do_lru,
-					      struct list_head *list, enum split_type split_type)
+static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new_order,
+					   struct page *split_at, struct xa_state *xas,
+					   struct address_space *mapping, bool do_lru,
+					   struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
 	struct folio *new_folio, *next;
@@ -4051,6 +4058,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 	if (shmem_mapping(mapping))
 		end = shmem_fallocend(mapping->host, end);
 
+	unmap_folio(folio);
+
 	xas_lock_irq(xas);
 
 	/*
@@ -4175,7 +4184,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
-	enum ttu_flags ttu_flags = 0;
 	int ret;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
@@ -4263,21 +4271,14 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		goto out_unlock;
 	}
 
-	unmap_folio(folio);
-
 	if (!is_anon) {
-		ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
+		ret = __folio_freeze_split_unmap_file(folio, new_order, split_at, &xas, mapping,
 							 true, list, split_type);
 	} else {
-		ret = __folio_freeze_split_unmapped_anon(folio, new_order, split_at, true,
-							 list, split_type);
+		ret = __folio_freeze_split_unmap_anon(folio, new_order, split_at, true,
+						      true, list, split_type);
 	}
 
-	if (!ret && is_anon && !folio_is_device_private(folio))
-		ttu_flags = TTU_USE_SHARED_ZEROPAGE;
-
-	remap_page(folio, 1 << old_order, ttu_flags);
-
 	/*
 	 * Drop the mapping while the inode is still pinned. @folio stays
 	 * locked and present in the page cache until the loop below, so
@@ -4360,8 +4361,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
 		return -EAGAIN;
 
-	return __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
-						  false, NULL, SPLIT_TYPE_UNIFORM);
+	return __folio_freeze_split_unmap_anon(folio, new_order, &folio->page, false,
+					       false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 07/13] mm/huge_memory: move unmap and remap into the split helpers
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

To prepare for further cleanup, move the unmap/remap handling from
__folio_split() into the split helpers.  Only anon folios need to
be remapped, so remap_page() is now only called for anon splits and
the anon check in remap_page() is redundant and can be removed.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 51 ++++++++++++++++++++++++++-------------------------
 1 file changed, 26 insertions(+), 25 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 72f5d0d24127..c0115841d1a0 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3589,9 +3589,6 @@ static void remap_page(struct folio *folio, unsigned long nr, int flags)
 {
 	int i = 0;
 
-	/* If unmap_folio() uses try_to_migrate() on file, remove this check */
-	if (!folio_test_anon(folio))
-		return;
 	for (;;) {
 		remove_migration_ptes(folio, folio, TTU_RMAP_LOCKED | flags);
 		i += folio_nr_pages(folio);
@@ -3934,19 +3931,23 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
 	return folio_nr_pages(folio);
 }
 
-static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int new_order,
-					      struct page *split_at, bool do_lru,
-					      struct list_head *list, enum split_type split_type)
+static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new_order,
+					   struct page *split_at, bool do_lru, bool unmap,
+					   struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
 	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio;
 	int old_order = folio_order(folio);
+	enum ttu_flags ttu_flags = 0;
 	struct list_lru_one *lru;
 	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
+	if (unmap)
+		unmap_folio(folio);
+
 	local_irq_disable();
 
 	/*
@@ -3971,8 +3972,8 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 			list_lru_unlock(lru);
 			rcu_read_unlock();
 		}
-		local_irq_enable();
-		return -EAGAIN;
+		ret = -EAGAIN;
+		goto out_no_split;
 	}
 
 	if (dequeue_deferred) {
@@ -4023,15 +4024,21 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 		lruvec_unlock(lruvec);
 	if (ci)
 		swap_cluster_unlock(ci);
+out_no_split:
 	local_irq_enable();
+	if (unmap) {
+		if (!ret && !folio_is_device_private(folio))
+			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
+		remap_page(folio, 1 << old_order, ttu_flags);
+	}
 
 	return ret;
 }
 
-static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int new_order,
-					      struct page *split_at, struct xa_state *xas,
-					      struct address_space *mapping, bool do_lru,
-					      struct list_head *list, enum split_type split_type)
+static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new_order,
+					   struct page *split_at, struct xa_state *xas,
+					   struct address_space *mapping, bool do_lru,
+					   struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
 	struct folio *new_folio, *next;
@@ -4051,6 +4058,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 	if (shmem_mapping(mapping))
 		end = shmem_fallocend(mapping->host, end);
 
+	unmap_folio(folio);
+
 	xas_lock_irq(xas);
 
 	/*
@@ -4175,7 +4184,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
-	enum ttu_flags ttu_flags = 0;
 	int ret;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
@@ -4263,21 +4271,14 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		goto out_unlock;
 	}
 
-	unmap_folio(folio);
-
 	if (!is_anon) {
-		ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
+		ret = __folio_freeze_split_unmap_file(folio, new_order, split_at, &xas, mapping,
 							 true, list, split_type);
 	} else {
-		ret = __folio_freeze_split_unmapped_anon(folio, new_order, split_at, true,
-							 list, split_type);
+		ret = __folio_freeze_split_unmap_anon(folio, new_order, split_at, true,
+						      true, list, split_type);
 	}
 
-	if (!ret && is_anon && !folio_is_device_private(folio))
-		ttu_flags = TTU_USE_SHARED_ZEROPAGE;
-
-	remap_page(folio, 1 << old_order, ttu_flags);
-
 	/*
 	 * Drop the mapping while the inode is still pinned. @folio stays
 	 * locked and present in the page cache until the loop below, so
@@ -4360,8 +4361,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
 		return -EAGAIN;
 
-	return __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
-						  false, NULL, SPLIT_TYPE_UNIFORM);
+	return __folio_freeze_split_unmap_anon(folio, new_order, &folio->page, false,
+					       false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 08/13] mm/huge_memory: move anon_vma and filemap management into split helpers
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

Only anon split needs vma info, and only file split needs the filemap
handling. Move the related code into separate helpers so they are
genuinely more self-contained.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 177 +++++++++++++++++++++++++------------------------------
 1 file changed, 79 insertions(+), 98 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index c0115841d1a0..f0ea6e5f53f7 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3939,12 +3939,33 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio;
 	int old_order = folio_order(folio);
+	struct anon_vma *anon_vma = NULL;
 	enum ttu_flags ttu_flags = 0;
 	struct list_lru_one *lru;
 	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
+	/*
+	 * Unmap/remap needs the anon_vma. The caller does not necessarily
+	 * hold an mmap_lock that would prevent the anon_vma from
+	 * disappearing, so we first take a reference and lock it. This is
+	 * similar to folio_lock_anon_vma_read() except the write lock is
+	 * taken to serialize against parallel split or collapse.
+	 */
+	if (unmap) {
+		anon_vma = folio_get_anon_vma(folio);
+		if (!anon_vma)
+			return -EBUSY;
+		anon_vma_lock_write(anon_vma);
+	}
+
+	/* Racy check if we can split the page, before the optional unmap. */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
 	if (unmap)
 		unmap_folio(folio);
 
@@ -4031,21 +4052,58 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
 		remap_page(folio, 1 << old_order, ttu_flags);
 	}
+out_unlock:
+	if (anon_vma) {
+		anon_vma_unlock_write(anon_vma);
+		put_anon_vma(anon_vma);
+	}
 
 	return ret;
 }
 
 static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new_order,
-					   struct page *split_at, struct xa_state *xas,
-					   struct address_space *mapping, bool do_lru,
+					   struct page *split_at, bool do_lru,
 					   struct list_head *list, enum split_type split_type)
 {
+	struct address_space *mapping = folio->mapping;
+	XA_STATE(xas, &mapping->i_pages, folio->index);
 	struct folio *end_folio = folio_next(folio);
 	struct folio *new_folio, *next;
 	int nr_shmem_dropped = 0;
+	unsigned int min_order;
 	struct lruvec *lruvec;
 	pgoff_t end = 0;
-	int ret;
+	gfp_t gfp;
+	int ret = 0;
+
+	min_order = mapping_min_folio_order(mapping);
+	if (new_order < min_order)
+		return -EINVAL;
+
+	gfp = current_gfp_context(mapping_gfp_mask(mapping) & GFP_RECLAIM_MASK);
+	if (!filemap_release_folio(folio, gfp))
+		return -EBUSY;
+
+	mapping_set_update(&xas, mapping);
+
+	if (split_type == SPLIT_TYPE_UNIFORM) {
+		int old_order = folio_order(folio);
+
+		xas_set_order(&xas, folio->index, new_order);
+		xas_split_alloc(&xas, folio, old_order, gfp);
+		if (xas_error(&xas)) {
+			ret = xas_error(&xas);
+			goto fail_free;
+		}
+	}
+
+	i_mmap_lock_read(mapping);
+
+	/* Racy check if we can split the page, before unmap_folio() */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto fail_mmap_unlock;
+	}
 
 	/*
 	 *__split_unmapped_folio() may need to trim off pages beyond
@@ -4060,13 +4118,13 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 
 	unmap_folio(folio);
 
-	xas_lock_irq(xas);
+	xas_lock_irq(&xas);
 
 	/*
 	 * Check if the folio is present in page cache.
 	 * We assume all tail are present too, if folio is there.
 	 */
-	if (xas_load(xas) != folio) {
+	if (xas_load(&xas) != folio) {
 		ret = -EAGAIN;
 		goto fail;
 	}
@@ -4093,7 +4151,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	if (do_lru)
 		lruvec = folio_lruvec_lock(folio);
 
-	ret = __split_unmapped_folio(folio, new_order, split_at, xas,
+	ret = __split_unmapped_folio(folio, new_order, split_at, &xas,
 				     mapping, split_type);
 
 	/*
@@ -4145,9 +4203,19 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 		lruvec_unlock(lruvec);
 
 fail:
-	xas_unlock_irq(xas);
+	xas_unlock_irq(&xas);
+fail_mmap_unlock:
 	if (nr_shmem_dropped)
 		shmem_uncharge(mapping->host, nr_shmem_dropped);
+	/*
+	 * Drop the mapping while the inode is still pinned. @folio stays
+	 * locked and present in the page cache, so eviction cannot free
+	 * the inode yet, nothing past this point may touch the inode or
+	 * the mapping.
+	 */
+	i_mmap_unlock_read(mapping);
+fail_free:
+	xas_destroy(&xas);
 	return ret;
 }
 
@@ -4176,12 +4244,9 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		struct page *split_at, struct page *lock_at,
 		struct list_head *list, enum split_type split_type)
 {
-	XA_STATE(xas, &folio->mapping->i_pages, folio->index);
 	struct folio *end_folio = folio_next(folio);
 	bool is_anon = folio_test_anon(folio);
 	struct mem_cgroup *memcg, *old_memcg;
-	struct address_space *mapping = NULL;
-	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
 	int ret;
@@ -4212,84 +4277,12 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 	memcg = get_mem_cgroup_from_folio(folio);
 	old_memcg = set_active_memcg(memcg);
 
-	if (is_anon) {
-		/*
-		 * The caller does not necessarily hold an mmap_lock that would
-		 * prevent the anon_vma disappearing so we first we take a
-		 * reference to it and then lock the anon_vma for write. This
-		 * is similar to folio_lock_anon_vma_read except the write lock
-		 * is taken to serialise against parallel split or collapse
-		 * operations.
-		 */
-		anon_vma = folio_get_anon_vma(folio);
-		if (!anon_vma) {
-			ret = -EBUSY;
-			goto out;
-		}
-		anon_vma_lock_write(anon_vma);
-		mapping = NULL;
-	} else {
-		unsigned int min_order;
-		gfp_t gfp;
-
-		mapping = folio->mapping;
-		min_order = mapping_min_folio_order(mapping);
-		if (new_order < min_order) {
-			ret = -EINVAL;
-			goto out;
-		}
-
-		gfp = current_gfp_context(mapping_gfp_mask(mapping) &
-							GFP_RECLAIM_MASK);
-
-		if (!filemap_release_folio(folio, gfp)) {
-			ret = -EBUSY;
-			goto out;
-		}
-
-		mapping_set_update(&xas, mapping);
-
-		if (split_type == SPLIT_TYPE_UNIFORM) {
-			xas_set_order(&xas, folio->index, new_order);
-			xas_split_alloc(&xas, folio, old_order, gfp);
-			if (xas_error(&xas)) {
-				ret = xas_error(&xas);
-				goto out;
-			}
-		}
-
-		anon_vma = NULL;
-		i_mmap_lock_read(mapping);
-	}
-
-	/*
-	 * Racy check if we can split the page, before unmap_folio() will
-	 * split PMDs
-	 */
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
-		ret = -EAGAIN;
-		goto out_unlock;
-	}
-
-	if (!is_anon) {
-		ret = __folio_freeze_split_unmap_file(folio, new_order, split_at, &xas, mapping,
-							 true, list, split_type);
-	} else {
+	if (is_anon)
 		ret = __folio_freeze_split_unmap_anon(folio, new_order, split_at, true,
 						      true, list, split_type);
-	}
-
-	/*
-	 * Drop the mapping while the inode is still pinned. @folio stays
-	 * locked and present in the page cache until the loop below, so
-	 * eviction cannot free the inode yet; @lock_at is not enough, it may
-	 * be a tail beyond EOF that the split already dropped from the page
-	 * cache. Nothing past this point may touch the inode or the mapping.
-	 */
-	if (mapping) {
-		i_mmap_unlock_read(mapping);
-		mapping = NULL;
-	}
+	else
+		ret = __folio_freeze_split_unmap_file(folio, new_order, split_at,
+						      true, list, split_type);
 
 	/*
 	 * Unlock all after-split folios except the one containing
@@ -4310,19 +4303,10 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		free_folio_and_swap_cache(new_folio);
 	}
 
-out_unlock:
-	if (anon_vma) {
-		anon_vma_unlock_write(anon_vma);
-		put_anon_vma(anon_vma);
-	}
-	if (mapping)
-		i_mmap_unlock_read(mapping);
-out:
 	/* restore to caller's old_memcg */
 	set_active_memcg(old_memcg);
 	mem_cgroup_put(memcg);
 out_no_memcg:
-	xas_destroy(&xas);
 	if (is_pmd_order(old_order))
 		count_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);
 	count_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);
@@ -4358,9 +4342,6 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);
 
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
-		return -EAGAIN;
-
 	return __folio_freeze_split_unmap_anon(folio, new_order, &folio->page, false,
 					       false, NULL, SPLIT_TYPE_UNIFORM);
 }

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 08/13] mm/huge_memory: move anon_vma and filemap management into split helpers
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

Only anon split needs vma info, and only file split needs the filemap
handling. Move the related code into separate helpers so they are
genuinely more self-contained.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 177 +++++++++++++++++++++++++------------------------------
 1 file changed, 79 insertions(+), 98 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index c0115841d1a0..f0ea6e5f53f7 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3939,12 +3939,33 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio;
 	int old_order = folio_order(folio);
+	struct anon_vma *anon_vma = NULL;
 	enum ttu_flags ttu_flags = 0;
 	struct list_lru_one *lru;
 	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
+	/*
+	 * Unmap/remap needs the anon_vma. The caller does not necessarily
+	 * hold an mmap_lock that would prevent the anon_vma from
+	 * disappearing, so we first take a reference and lock it. This is
+	 * similar to folio_lock_anon_vma_read() except the write lock is
+	 * taken to serialize against parallel split or collapse.
+	 */
+	if (unmap) {
+		anon_vma = folio_get_anon_vma(folio);
+		if (!anon_vma)
+			return -EBUSY;
+		anon_vma_lock_write(anon_vma);
+	}
+
+	/* Racy check if we can split the page, before the optional unmap. */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
 	if (unmap)
 		unmap_folio(folio);
 
@@ -4031,21 +4052,58 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
 		remap_page(folio, 1 << old_order, ttu_flags);
 	}
+out_unlock:
+	if (anon_vma) {
+		anon_vma_unlock_write(anon_vma);
+		put_anon_vma(anon_vma);
+	}
 
 	return ret;
 }
 
 static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new_order,
-					   struct page *split_at, struct xa_state *xas,
-					   struct address_space *mapping, bool do_lru,
+					   struct page *split_at, bool do_lru,
 					   struct list_head *list, enum split_type split_type)
 {
+	struct address_space *mapping = folio->mapping;
+	XA_STATE(xas, &mapping->i_pages, folio->index);
 	struct folio *end_folio = folio_next(folio);
 	struct folio *new_folio, *next;
 	int nr_shmem_dropped = 0;
+	unsigned int min_order;
 	struct lruvec *lruvec;
 	pgoff_t end = 0;
-	int ret;
+	gfp_t gfp;
+	int ret = 0;
+
+	min_order = mapping_min_folio_order(mapping);
+	if (new_order < min_order)
+		return -EINVAL;
+
+	gfp = current_gfp_context(mapping_gfp_mask(mapping) & GFP_RECLAIM_MASK);
+	if (!filemap_release_folio(folio, gfp))
+		return -EBUSY;
+
+	mapping_set_update(&xas, mapping);
+
+	if (split_type == SPLIT_TYPE_UNIFORM) {
+		int old_order = folio_order(folio);
+
+		xas_set_order(&xas, folio->index, new_order);
+		xas_split_alloc(&xas, folio, old_order, gfp);
+		if (xas_error(&xas)) {
+			ret = xas_error(&xas);
+			goto fail_free;
+		}
+	}
+
+	i_mmap_lock_read(mapping);
+
+	/* Racy check if we can split the page, before unmap_folio() */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto fail_mmap_unlock;
+	}
 
 	/*
 	 *__split_unmapped_folio() may need to trim off pages beyond
@@ -4060,13 +4118,13 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 
 	unmap_folio(folio);
 
-	xas_lock_irq(xas);
+	xas_lock_irq(&xas);
 
 	/*
 	 * Check if the folio is present in page cache.
 	 * We assume all tail are present too, if folio is there.
 	 */
-	if (xas_load(xas) != folio) {
+	if (xas_load(&xas) != folio) {
 		ret = -EAGAIN;
 		goto fail;
 	}
@@ -4093,7 +4151,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	if (do_lru)
 		lruvec = folio_lruvec_lock(folio);
 
-	ret = __split_unmapped_folio(folio, new_order, split_at, xas,
+	ret = __split_unmapped_folio(folio, new_order, split_at, &xas,
 				     mapping, split_type);
 
 	/*
@@ -4145,9 +4203,19 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 		lruvec_unlock(lruvec);
 
 fail:
-	xas_unlock_irq(xas);
+	xas_unlock_irq(&xas);
+fail_mmap_unlock:
 	if (nr_shmem_dropped)
 		shmem_uncharge(mapping->host, nr_shmem_dropped);
+	/*
+	 * Drop the mapping while the inode is still pinned. @folio stays
+	 * locked and present in the page cache, so eviction cannot free
+	 * the inode yet, nothing past this point may touch the inode or
+	 * the mapping.
+	 */
+	i_mmap_unlock_read(mapping);
+fail_free:
+	xas_destroy(&xas);
 	return ret;
 }
 
@@ -4176,12 +4244,9 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		struct page *split_at, struct page *lock_at,
 		struct list_head *list, enum split_type split_type)
 {
-	XA_STATE(xas, &folio->mapping->i_pages, folio->index);
 	struct folio *end_folio = folio_next(folio);
 	bool is_anon = folio_test_anon(folio);
 	struct mem_cgroup *memcg, *old_memcg;
-	struct address_space *mapping = NULL;
-	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
 	int ret;
@@ -4212,84 +4277,12 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 	memcg = get_mem_cgroup_from_folio(folio);
 	old_memcg = set_active_memcg(memcg);
 
-	if (is_anon) {
-		/*
-		 * The caller does not necessarily hold an mmap_lock that would
-		 * prevent the anon_vma disappearing so we first we take a
-		 * reference to it and then lock the anon_vma for write. This
-		 * is similar to folio_lock_anon_vma_read except the write lock
-		 * is taken to serialise against parallel split or collapse
-		 * operations.
-		 */
-		anon_vma = folio_get_anon_vma(folio);
-		if (!anon_vma) {
-			ret = -EBUSY;
-			goto out;
-		}
-		anon_vma_lock_write(anon_vma);
-		mapping = NULL;
-	} else {
-		unsigned int min_order;
-		gfp_t gfp;
-
-		mapping = folio->mapping;
-		min_order = mapping_min_folio_order(mapping);
-		if (new_order < min_order) {
-			ret = -EINVAL;
-			goto out;
-		}
-
-		gfp = current_gfp_context(mapping_gfp_mask(mapping) &
-							GFP_RECLAIM_MASK);
-
-		if (!filemap_release_folio(folio, gfp)) {
-			ret = -EBUSY;
-			goto out;
-		}
-
-		mapping_set_update(&xas, mapping);
-
-		if (split_type == SPLIT_TYPE_UNIFORM) {
-			xas_set_order(&xas, folio->index, new_order);
-			xas_split_alloc(&xas, folio, old_order, gfp);
-			if (xas_error(&xas)) {
-				ret = xas_error(&xas);
-				goto out;
-			}
-		}
-
-		anon_vma = NULL;
-		i_mmap_lock_read(mapping);
-	}
-
-	/*
-	 * Racy check if we can split the page, before unmap_folio() will
-	 * split PMDs
-	 */
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
-		ret = -EAGAIN;
-		goto out_unlock;
-	}
-
-	if (!is_anon) {
-		ret = __folio_freeze_split_unmap_file(folio, new_order, split_at, &xas, mapping,
-							 true, list, split_type);
-	} else {
+	if (is_anon)
 		ret = __folio_freeze_split_unmap_anon(folio, new_order, split_at, true,
 						      true, list, split_type);
-	}
-
-	/*
-	 * Drop the mapping while the inode is still pinned. @folio stays
-	 * locked and present in the page cache until the loop below, so
-	 * eviction cannot free the inode yet; @lock_at is not enough, it may
-	 * be a tail beyond EOF that the split already dropped from the page
-	 * cache. Nothing past this point may touch the inode or the mapping.
-	 */
-	if (mapping) {
-		i_mmap_unlock_read(mapping);
-		mapping = NULL;
-	}
+	else
+		ret = __folio_freeze_split_unmap_file(folio, new_order, split_at,
+						      true, list, split_type);
 
 	/*
 	 * Unlock all after-split folios except the one containing
@@ -4310,19 +4303,10 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		free_folio_and_swap_cache(new_folio);
 	}
 
-out_unlock:
-	if (anon_vma) {
-		anon_vma_unlock_write(anon_vma);
-		put_anon_vma(anon_vma);
-	}
-	if (mapping)
-		i_mmap_unlock_read(mapping);
-out:
 	/* restore to caller's old_memcg */
 	set_active_memcg(old_memcg);
 	mem_cgroup_put(memcg);
 out_no_memcg:
-	xas_destroy(&xas);
 	if (is_pmd_order(old_order))
 		count_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);
 	count_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);
@@ -4358,9 +4342,6 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);
 
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
-		return -EAGAIN;
-
 	return __folio_freeze_split_unmap_anon(folio, new_order, &folio->page, false,
 					       false, NULL, SPLIT_TYPE_UNIFORM);
 }

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 09/13] mm/huge_memory: move memcg switch into the file split helper
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

The xarray node allocations in __folio_freeze_split_unmap_file() need
to be charged to the folio's memcg, so move the memcg switch from
__folio_split() into the helper.

The anon split helper and the after-split folio freeing perform no
chargeable allocations, so no memcg handling is left in __folio_split().
Rename its out_no_memcg label to out.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 36 +++++++++++++++++++-----------------
 1 file changed, 19 insertions(+), 17 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index f0ea6e5f53f7..ab2bb29748d3 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4068,6 +4068,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	struct address_space *mapping = folio->mapping;
 	XA_STATE(xas, &mapping->i_pages, folio->index);
 	struct folio *end_folio = folio_next(folio);
+	struct mem_cgroup *memcg, *old_memcg;
 	struct folio *new_folio, *next;
 	int nr_shmem_dropped = 0;
 	unsigned int min_order;
@@ -4080,9 +4081,18 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	if (new_order < min_order)
 		return -EINVAL;
 
+	/*
+	 * Switch to folio's memcg as xarray node allocation can happen and
+	 * needs to charge to it.
+	 */
+	memcg = get_mem_cgroup_from_folio(folio);
+	old_memcg = set_active_memcg(memcg);
+
 	gfp = current_gfp_context(mapping_gfp_mask(mapping) & GFP_RECLAIM_MASK);
-	if (!filemap_release_folio(folio, gfp))
-		return -EBUSY;
+	if (!filemap_release_folio(folio, gfp)) {
+		ret = -EBUSY;
+		goto fail_free;
+	}
 
 	mapping_set_update(&xas, mapping);
 
@@ -4215,6 +4225,9 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	 */
 	i_mmap_unlock_read(mapping);
 fail_free:
+	/* Restore the previously active memcg */
+	set_active_memcg(old_memcg);
+	mem_cgroup_put(memcg);
 	xas_destroy(&xas);
 	return ret;
 }
@@ -4246,7 +4259,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 {
 	struct folio *end_folio = folio_next(folio);
 	bool is_anon = folio_test_anon(folio);
-	struct mem_cgroup *memcg, *old_memcg;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
 	int ret;
@@ -4256,27 +4268,20 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	if (folio != page_folio(split_at) || folio != page_folio(lock_at)) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	if (new_order >= old_order) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	ret = folio_check_splittable(folio, new_order, split_type);
 	if (ret) {
 		VM_WARN_ONCE(ret == -EINVAL, "Tried to split an unsplittable folio");
-		goto out_no_memcg;
+		goto out;
 	}
 
-	/*
-	 * switch to folio's memcg as xarray node allocation can happen and
-	 * needs to charge to it.
-	 */
-	memcg = get_mem_cgroup_from_folio(folio);
-	old_memcg = set_active_memcg(memcg);
-
 	if (is_anon)
 		ret = __folio_freeze_split_unmap_anon(folio, new_order, split_at, true,
 						      true, list, split_type);
@@ -4303,10 +4308,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		free_folio_and_swap_cache(new_folio);
 	}
 
-	/* restore to caller's old_memcg */
-	set_active_memcg(old_memcg);
-	mem_cgroup_put(memcg);
-out_no_memcg:
+out:
 	if (is_pmd_order(old_order))
 		count_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);
 	count_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 09/13] mm/huge_memory: move memcg switch into the file split helper
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

The xarray node allocations in __folio_freeze_split_unmap_file() need
to be charged to the folio's memcg, so move the memcg switch from
__folio_split() into the helper.

The anon split helper and the after-split folio freeing perform no
chargeable allocations, so no memcg handling is left in __folio_split().
Rename its out_no_memcg label to out.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 36 +++++++++++++++++++-----------------
 1 file changed, 19 insertions(+), 17 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index f0ea6e5f53f7..ab2bb29748d3 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4068,6 +4068,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	struct address_space *mapping = folio->mapping;
 	XA_STATE(xas, &mapping->i_pages, folio->index);
 	struct folio *end_folio = folio_next(folio);
+	struct mem_cgroup *memcg, *old_memcg;
 	struct folio *new_folio, *next;
 	int nr_shmem_dropped = 0;
 	unsigned int min_order;
@@ -4080,9 +4081,18 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	if (new_order < min_order)
 		return -EINVAL;
 
+	/*
+	 * Switch to folio's memcg as xarray node allocation can happen and
+	 * needs to charge to it.
+	 */
+	memcg = get_mem_cgroup_from_folio(folio);
+	old_memcg = set_active_memcg(memcg);
+
 	gfp = current_gfp_context(mapping_gfp_mask(mapping) & GFP_RECLAIM_MASK);
-	if (!filemap_release_folio(folio, gfp))
-		return -EBUSY;
+	if (!filemap_release_folio(folio, gfp)) {
+		ret = -EBUSY;
+		goto fail_free;
+	}
 
 	mapping_set_update(&xas, mapping);
 
@@ -4215,6 +4225,9 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	 */
 	i_mmap_unlock_read(mapping);
 fail_free:
+	/* Restore the previously active memcg */
+	set_active_memcg(old_memcg);
+	mem_cgroup_put(memcg);
 	xas_destroy(&xas);
 	return ret;
 }
@@ -4246,7 +4259,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 {
 	struct folio *end_folio = folio_next(folio);
 	bool is_anon = folio_test_anon(folio);
-	struct mem_cgroup *memcg, *old_memcg;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
 	int ret;
@@ -4256,27 +4268,20 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	if (folio != page_folio(split_at) || folio != page_folio(lock_at)) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	if (new_order >= old_order) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	ret = folio_check_splittable(folio, new_order, split_type);
 	if (ret) {
 		VM_WARN_ONCE(ret == -EINVAL, "Tried to split an unsplittable folio");
-		goto out_no_memcg;
+		goto out;
 	}
 
-	/*
-	 * switch to folio's memcg as xarray node allocation can happen and
-	 * needs to charge to it.
-	 */
-	memcg = get_mem_cgroup_from_folio(folio);
-	old_memcg = set_active_memcg(memcg);
-
 	if (is_anon)
 		ret = __folio_freeze_split_unmap_anon(folio, new_order, split_at, true,
 						      true, list, split_type);
@@ -4303,10 +4308,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		free_folio_and_swap_cache(new_folio);
 	}
 
-	/* restore to caller's old_memcg */
-	set_active_memcg(old_memcg);
-	mem_cgroup_put(memcg);
-out_no_memcg:
+out:
 	if (is_pmd_order(old_order))
 		count_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);
 	count_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 10/13] mm/huge_memory: allow splitting mappingless swap cache folios
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

Lift the restriction that kept swap cache folios without a mapping
from being split.  All the underlying infrastructure is sound against
that with a few more tweaks, no reason to block it anymore.

Also rename the split helper, which now handles mappingless swap
cache folios that are yet to be anon, or may actually belong to
shmem.  In either case there is not much difference in how they would
be split.

A non-anon swap cache folio that still has a mapping (e.g. a shmem
swap cache folio) remains rejected up front: it would need both its
page cache and swap cache entries updated on split, which the split
helpers do not do, and there would be little benefit in doing so.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 40 +++++++++++++++++++++++-----------------
 1 file changed, 23 insertions(+), 17 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ab2bb29748d3..b80d0db63225 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3881,12 +3881,10 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
 	/*
 	 * Folios that just got truncated cannot get split. Signal to the
-	 * caller that there was a race.
-	 *
-	 * TODO: this will also currently refuse folios without a mapping in the
-	 * swapcache (shmem or to-be-anon folios).
+	 * caller that there was a race. A mappingless swap cache folio
+	 * has no page cache entries to update, so it is fine to split.
 	 */
-	if (!folio->mapping && !is_anon)
+	if (!folio->mapping && !is_swapcache)
 		return -EBUSY;
 
 	/* order-1 is not supported for anonymous THP. */
@@ -3931,11 +3929,12 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
 	return folio_nr_pages(folio);
 }
 
-static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new_order,
-					   struct page *split_at, bool do_lru, bool unmap,
-					   struct list_head *list, enum split_type split_type)
+static int __folio_freeze_split_unmap(struct folio *folio, unsigned int new_order,
+				      struct page *split_at, bool do_lru, bool anon_unmap,
+				      struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
+	bool is_anon = folio_test_anon(folio);
 	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio;
 	int old_order = folio_order(folio);
@@ -3953,7 +3952,7 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 	 * similar to folio_lock_anon_vma_read() except the write lock is
 	 * taken to serialize against parallel split or collapse.
 	 */
-	if (unmap) {
+	if (anon_unmap) {
 		anon_vma = folio_get_anon_vma(folio);
 		if (!anon_vma)
 			return -EBUSY;
@@ -3966,7 +3965,7 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 		goto out_unlock;
 	}
 
-	if (unmap)
+	if (anon_unmap)
 		unmap_folio(folio);
 
 	local_irq_disable();
@@ -3977,8 +3976,11 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 	 * a 0-ref folio, it assumes it beat folio_put() to the list
 	 * lock and must clean up the LRU state - the same dequeue we
 	 * will do below as part of the split.
+	 *
+	 * Only anon folios are ever queued on the deferred split list,
+	 * so non-anon folios (mappingless swapcache) never need dequeuing.
 	 */
-	dequeue_deferred = old_order > 1;
+	dequeue_deferred = old_order > 1 && is_anon;
 	if (dequeue_deferred) {
 		struct mem_cgroup *memcg;
 
@@ -4047,13 +4049,13 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 		swap_cluster_unlock(ci);
 out_no_split:
 	local_irq_enable();
-	if (unmap) {
+	if (anon_unmap) {
 		if (!ret && !folio_is_device_private(folio))
 			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
 		remap_page(folio, 1 << old_order, ttu_flags);
 	}
 out_unlock:
-	if (anon_vma) {
+	if (anon_unmap) {
 		anon_vma_unlock_write(anon_vma);
 		put_anon_vma(anon_vma);
 	}
@@ -4257,6 +4259,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		struct page *split_at, struct page *lock_at,
 		struct list_head *list, enum split_type split_type)
 {
+	bool is_swapcache = folio_test_swapcache(folio);
 	struct folio *end_folio = folio_next(folio);
 	bool is_anon = folio_test_anon(folio);
 	int old_order = folio_order(folio);
@@ -4283,8 +4286,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 	}
 
 	if (is_anon)
-		ret = __folio_freeze_split_unmap_anon(folio, new_order, split_at, true,
-						      true, list, split_type);
+		ret = __folio_freeze_split_unmap(folio, new_order, split_at, true,
+						 true, list, split_type);
+	else if (is_swapcache)
+		ret = __folio_freeze_split_unmap(folio, new_order, split_at, true,
+						 false, list, split_type);
 	else
 		ret = __folio_freeze_split_unmap_file(folio, new_order, split_at,
 						      true, list, split_type);
@@ -4344,8 +4350,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);
 
-	return __folio_freeze_split_unmap_anon(folio, new_order, &folio->page, false,
-					       false, NULL, SPLIT_TYPE_UNIFORM);
+	return __folio_freeze_split_unmap(folio, new_order, &folio->page, false,
+					  false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 10/13] mm/huge_memory: allow splitting mappingless swap cache folios
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

Lift the restriction that kept swap cache folios without a mapping
from being split.  All the underlying infrastructure is sound against
that with a few more tweaks, no reason to block it anymore.

Also rename the split helper, which now handles mappingless swap
cache folios that are yet to be anon, or may actually belong to
shmem.  In either case there is not much difference in how they would
be split.

A non-anon swap cache folio that still has a mapping (e.g. a shmem
swap cache folio) remains rejected up front: it would need both its
page cache and swap cache entries updated on split, which the split
helpers do not do, and there would be little benefit in doing so.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 40 +++++++++++++++++++++++-----------------
 1 file changed, 23 insertions(+), 17 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ab2bb29748d3..b80d0db63225 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3881,12 +3881,10 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
 	/*
 	 * Folios that just got truncated cannot get split. Signal to the
-	 * caller that there was a race.
-	 *
-	 * TODO: this will also currently refuse folios without a mapping in the
-	 * swapcache (shmem or to-be-anon folios).
+	 * caller that there was a race. A mappingless swap cache folio
+	 * has no page cache entries to update, so it is fine to split.
 	 */
-	if (!folio->mapping && !is_anon)
+	if (!folio->mapping && !is_swapcache)
 		return -EBUSY;
 
 	/* order-1 is not supported for anonymous THP. */
@@ -3931,11 +3929,12 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
 	return folio_nr_pages(folio);
 }
 
-static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new_order,
-					   struct page *split_at, bool do_lru, bool unmap,
-					   struct list_head *list, enum split_type split_type)
+static int __folio_freeze_split_unmap(struct folio *folio, unsigned int new_order,
+				      struct page *split_at, bool do_lru, bool anon_unmap,
+				      struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
+	bool is_anon = folio_test_anon(folio);
 	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio;
 	int old_order = folio_order(folio);
@@ -3953,7 +3952,7 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 	 * similar to folio_lock_anon_vma_read() except the write lock is
 	 * taken to serialize against parallel split or collapse.
 	 */
-	if (unmap) {
+	if (anon_unmap) {
 		anon_vma = folio_get_anon_vma(folio);
 		if (!anon_vma)
 			return -EBUSY;
@@ -3966,7 +3965,7 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 		goto out_unlock;
 	}
 
-	if (unmap)
+	if (anon_unmap)
 		unmap_folio(folio);
 
 	local_irq_disable();
@@ -3977,8 +3976,11 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 	 * a 0-ref folio, it assumes it beat folio_put() to the list
 	 * lock and must clean up the LRU state - the same dequeue we
 	 * will do below as part of the split.
+	 *
+	 * Only anon folios are ever queued on the deferred split list,
+	 * so non-anon folios (mappingless swapcache) never need dequeuing.
 	 */
-	dequeue_deferred = old_order > 1;
+	dequeue_deferred = old_order > 1 && is_anon;
 	if (dequeue_deferred) {
 		struct mem_cgroup *memcg;
 
@@ -4047,13 +4049,13 @@ static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new
 		swap_cluster_unlock(ci);
 out_no_split:
 	local_irq_enable();
-	if (unmap) {
+	if (anon_unmap) {
 		if (!ret && !folio_is_device_private(folio))
 			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
 		remap_page(folio, 1 << old_order, ttu_flags);
 	}
 out_unlock:
-	if (anon_vma) {
+	if (anon_unmap) {
 		anon_vma_unlock_write(anon_vma);
 		put_anon_vma(anon_vma);
 	}
@@ -4257,6 +4259,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		struct page *split_at, struct page *lock_at,
 		struct list_head *list, enum split_type split_type)
 {
+	bool is_swapcache = folio_test_swapcache(folio);
 	struct folio *end_folio = folio_next(folio);
 	bool is_anon = folio_test_anon(folio);
 	int old_order = folio_order(folio);
@@ -4283,8 +4286,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 	}
 
 	if (is_anon)
-		ret = __folio_freeze_split_unmap_anon(folio, new_order, split_at, true,
-						      true, list, split_type);
+		ret = __folio_freeze_split_unmap(folio, new_order, split_at, true,
+						 true, list, split_type);
+	else if (is_swapcache)
+		ret = __folio_freeze_split_unmap(folio, new_order, split_at, true,
+						 false, list, split_type);
 	else
 		ret = __folio_freeze_split_unmap_file(folio, new_order, split_at,
 						      true, list, split_type);
@@ -4344,8 +4350,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);
 
-	return __folio_freeze_split_unmap_anon(folio, new_order, &folio->page, false,
-					       false, NULL, SPLIT_TYPE_UNIFORM);
+	return __folio_freeze_split_unmap(folio, new_order, &folio->page, false,
+					  false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 11/13] mm/huge_memory: clean up after-split folio freeing in __folio_split
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

Replace free_folio_and_swap_cache() with an explicit folio_free_swap()
and folio_put() in the after-split loop.  free_folio_and_swap_cache()
unlocks the folio, then free_swap_cache() must trylock it again and
re-check folio_mapped() before freeing the swap cache entries; if the
trylock loses a race, the entries are left behind even though the folio
reference is dropped.  The sub folios are still locked and unmapped
here, so just directly call folio_free_swap() directly under the lock,
unlock and drop the reference.  This makes the swap cache freeing
deterministic and the reference drop explicit.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index b80d0db63225..39c91c8e5bc8 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4304,14 +4304,16 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		if (new_folio == page_folio(lock_at))
 			continue;
 
-		folio_unlock(new_folio);
 		/*
 		 * Subpages whose mapping has been zapped may be freed
 		 * earlier, but freeing them requires taking the
 		 * lru_lock, so we defer put_page() on tail pages until
 		 * after the split completes.
 		 */
-		free_folio_and_swap_cache(new_folio);
+		if (is_swapcache)
+			folio_free_swap(new_folio);
+		folio_unlock(new_folio);
+		folio_put(new_folio);
 	}
 
 out:

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 11/13] mm/huge_memory: clean up after-split folio freeing in __folio_split
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

Replace free_folio_and_swap_cache() with an explicit folio_free_swap()
and folio_put() in the after-split loop.  free_folio_and_swap_cache()
unlocks the folio, then free_swap_cache() must trylock it again and
re-check folio_mapped() before freeing the swap cache entries; if the
trylock loses a race, the entries are left behind even though the folio
reference is dropped.  The sub folios are still locked and unmapped
here, so just directly call folio_free_swap() directly under the lock,
unlock and drop the reference.  This makes the swap cache freeing
deterministic and the reference drop explicit.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index b80d0db63225..39c91c8e5bc8 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4304,14 +4304,16 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		if (new_folio == page_folio(lock_at))
 			continue;
 
-		folio_unlock(new_folio);
 		/*
 		 * Subpages whose mapping has been zapped may be freed
 		 * earlier, but freeing them requires taking the
 		 * lru_lock, so we defer put_page() on tail pages until
 		 * after the split completes.
 		 */
-		free_folio_and_swap_cache(new_folio);
+		if (is_swapcache)
+			folio_free_swap(new_folio);
+		folio_unlock(new_folio);
+		folio_put(new_folio);
 	}
 
 out:

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 12/13] mm/huge_memory: lift order-0 restriction for swapcache split
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

The restriction that swapcache folios can only be uniformly split to
order 0 dates back to when the swap cache was managed via address_space
mapping (swap_address_space).  The old split loop only created order-0
sub-folios with a fixed stride, so non-uniform split and non-zero order
were rightfully blocked.

After the swap cache switched to swap table under a cluster lock,
__swap_cache_replace_folio already gained the ability to replace any
number of entries for any sub-folio size in one cluster, and the old
swap_address_space locking and limit was removed. The restriction
became obsolete but persisted through multiple refactorings.

Drop it now: swapcache folios can be split to any supported order with
either uniform or non-uniform split, except order-1 which is not
supported for anon folios. Mappingless swap cache folios could be either
anon or shmem, so for now we just simply forbid order-1 for all swapcache.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 32 +++++++++++++-------------------
 1 file changed, 13 insertions(+), 19 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 39c91c8e5bc8..dba53fbb93a8 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3797,6 +3797,7 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 		struct address_space *mapping, enum split_type split_type)
 {
 	const bool is_anon = folio_test_anon(folio);
+	const bool is_swapcache = folio_test_swapcache(folio);
 	int old_order = folio_order(folio);
 	int start_order = split_type == SPLIT_TYPE_UNIFORM ? new_order : old_order - 1;
 	struct folio *old_folio = folio;
@@ -3811,8 +3812,8 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 	     split_order--) {
 		int nr_new_folios = 1UL << (old_order - split_order);
 
-		/* order-1 anonymous folio is not supported */
-		if (is_anon && split_order == 1)
+		/* order-1 anonymous or swapcache folio is not supported */
+		if ((is_anon || is_swapcache) && split_order == 1)
 			continue;
 
 		if (mapping) {
@@ -3887,21 +3888,14 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	if (!folio->mapping && !is_swapcache)
 		return -EBUSY;
 
-	/* order-1 is not supported for anonymous THP. */
-	if (is_anon && new_order == 1)
-		return -EINVAL;
-
 	/*
-	 * swapcache folio could only be split to order 0
-	 *
-	 * non-uniform split creates after-split folios with orders from
-	 * folio_order(folio) - 1 to new_order, making it not suitable for any
-	 * swapcache folio split. Only uniform split to order-0 can be used
-	 * here.
+	 * Order-1 is unsupported: anon folios need subpage 2 for the
+	 * deferred split list, hybrid shmem & swap cache folios are not
+	 * splittable, and a splittable mappingless swap cache folio could
+	 * be either anon or shmem, which we cannot tell apart.
 	 */
-	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && is_swapcache) {
+	if ((is_anon || is_swapcache) && new_order == 1)
 		return -EINVAL;
-	}
 
 	if (is_huge_zero_folio(folio))
 		return -EINVAL;
@@ -4372,11 +4366,11 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  *    GUP pins, will result in the folio not getting split; instead, the caller
  *    will receive an -EAGAIN.
  *
- * 4) @new_order > 1, usually. Splitting to order-1 anonymous folios is not
- *    supported for non-file-backed folios, because folio->_deferred_list, which
- *    is used by partially mapped folios, is stored in subpage 2, but an order-1
- *    folio only has subpages 0 and 1. File-backed order-1 folios are supported,
- *    since they do not use _deferred_list.
+ * 4) @new_order > 1, usually. Order-1 is not supported for anon or swapcache
+ *    folios: anon folios need subpage 2 for _deferred_list, which order-1
+ *    folios lack, and a swapcache folio may become anon once faulted in.
+ *    File-backed order-1 folios are supported, since they do not use
+ *    _deferred_list.
  *
  * After splitting, the caller's folio reference will be transferred to @page,
  * resulting in a raised refcount of @page after this call. The other pages may

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 12/13] mm/huge_memory: lift order-0 restriction for swapcache split
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

The restriction that swapcache folios can only be uniformly split to
order 0 dates back to when the swap cache was managed via address_space
mapping (swap_address_space).  The old split loop only created order-0
sub-folios with a fixed stride, so non-uniform split and non-zero order
were rightfully blocked.

After the swap cache switched to swap table under a cluster lock,
__swap_cache_replace_folio already gained the ability to replace any
number of entries for any sub-folio size in one cluster, and the old
swap_address_space locking and limit was removed. The restriction
became obsolete but persisted through multiple refactorings.

Drop it now: swapcache folios can be split to any supported order with
either uniform or non-uniform split, except order-1 which is not
supported for anon folios. Mappingless swap cache folios could be either
anon or shmem, so for now we just simply forbid order-1 for all swapcache.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 32 +++++++++++++-------------------
 1 file changed, 13 insertions(+), 19 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 39c91c8e5bc8..dba53fbb93a8 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3797,6 +3797,7 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 		struct address_space *mapping, enum split_type split_type)
 {
 	const bool is_anon = folio_test_anon(folio);
+	const bool is_swapcache = folio_test_swapcache(folio);
 	int old_order = folio_order(folio);
 	int start_order = split_type == SPLIT_TYPE_UNIFORM ? new_order : old_order - 1;
 	struct folio *old_folio = folio;
@@ -3811,8 +3812,8 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 	     split_order--) {
 		int nr_new_folios = 1UL << (old_order - split_order);
 
-		/* order-1 anonymous folio is not supported */
-		if (is_anon && split_order == 1)
+		/* order-1 anonymous or swapcache folio is not supported */
+		if ((is_anon || is_swapcache) && split_order == 1)
 			continue;
 
 		if (mapping) {
@@ -3887,21 +3888,14 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	if (!folio->mapping && !is_swapcache)
 		return -EBUSY;
 
-	/* order-1 is not supported for anonymous THP. */
-	if (is_anon && new_order == 1)
-		return -EINVAL;
-
 	/*
-	 * swapcache folio could only be split to order 0
-	 *
-	 * non-uniform split creates after-split folios with orders from
-	 * folio_order(folio) - 1 to new_order, making it not suitable for any
-	 * swapcache folio split. Only uniform split to order-0 can be used
-	 * here.
+	 * Order-1 is unsupported: anon folios need subpage 2 for the
+	 * deferred split list, hybrid shmem & swap cache folios are not
+	 * splittable, and a splittable mappingless swap cache folio could
+	 * be either anon or shmem, which we cannot tell apart.
 	 */
-	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && is_swapcache) {
+	if ((is_anon || is_swapcache) && new_order == 1)
 		return -EINVAL;
-	}
 
 	if (is_huge_zero_folio(folio))
 		return -EINVAL;
@@ -4372,11 +4366,11 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  *    GUP pins, will result in the folio not getting split; instead, the caller
  *    will receive an -EAGAIN.
  *
- * 4) @new_order > 1, usually. Splitting to order-1 anonymous folios is not
- *    supported for non-file-backed folios, because folio->_deferred_list, which
- *    is used by partially mapped folios, is stored in subpage 2, but an order-1
- *    folio only has subpages 0 and 1. File-backed order-1 folios are supported,
- *    since they do not use _deferred_list.
+ * 4) @new_order > 1, usually. Order-1 is not supported for anon or swapcache
+ *    folios: anon folios need subpage 2 for _deferred_list, which order-1
+ *    folios lack, and a swapcache folio may become anon once faulted in.
+ *    File-backed order-1 folios are supported, since they do not use
+ *    _deferred_list.
  *
  * After splitting, the caller's folio reference will be transferred to @page,
  * resulting in a raised refcount of @page after this call. The other pages may

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 13/13] mm/huge_memory: count only swap cache refs in anon folio split
  2026-08-07 21:17 ` Kairui Song
@ 2026-08-07 21:17   ` Kairui Song
  -1 siblings, 0 replies; 45+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

From: Kairui Song <kasong@tencent.com>

Only __folio_freeze_split_unmap() sees anon folios and swap cache folios
now. The file split helper only handles page cache folios, which hold
exactly folio_nr_pages() references.

Rename folio_cache_ref_count() to folio_swapcache_ref_count() and drop
the anon check so the helper counts what its name says. The file split
helper now uses folio_nr_pages() directly.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 35 +++++++++++++++--------------------
 1 file changed, 15 insertions(+), 20 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index dba53fbb93a8..0d70ee3017c4 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3915,10 +3915,10 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	return 0;
 }
 
-/* Number of folio references from the pagecache or the swapcache. */
-static unsigned int folio_cache_ref_count(const struct folio *folio)
+/* Number of folio references from the swapcache. */
+static unsigned int folio_swapcache_ref_count(const struct folio *folio)
 {
-	if (folio_test_anon(folio) && !folio_test_swapcache(folio))
+	if (!folio_test_swapcache(folio))
 		return 0;
 	return folio_nr_pages(folio);
 }
@@ -3984,7 +3984,7 @@ static int __folio_freeze_split_unmap(struct folio *folio, unsigned int new_orde
 				    folio_nid(folio), &memcg);
 	}
 
-	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
+	if (!folio_ref_freeze(folio, folio_swapcache_ref_count(folio) + 1)) {
 		if (dequeue_deferred) {
 			list_lru_unlock(lru);
 			rcu_read_unlock();
@@ -4027,7 +4027,7 @@ static int __folio_freeze_split_unmap(struct folio *folio, unsigned int new_orde
 	     new_folio = folio_next(new_folio)) {
 		zone_device_private_split_cb(folio, new_folio);
 		folio_ref_unfreeze(new_folio,
-				   folio_cache_ref_count(new_folio) + 1);
+				   folio_swapcache_ref_count(new_folio) + 1);
 		if (do_lru)
 			lru_add_split_folio(folio, new_folio, lruvec, list);
 		if (ci)
@@ -4035,7 +4035,7 @@ static int __folio_freeze_split_unmap(struct folio *folio, unsigned int new_orde
 	}
 
 	zone_device_private_split_cb(folio, NULL);
-	folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+	folio_ref_unfreeze(folio, folio_swapcache_ref_count(folio) + 1);
 
 	if (do_lru)
 		lruvec_unlock(lruvec);
@@ -4064,6 +4064,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	struct address_space *mapping = folio->mapping;
 	XA_STATE(xas, &mapping->i_pages, folio->index);
 	struct folio *end_folio = folio_next(folio);
+	long old_nr_pages = folio_nr_pages(folio);
 	struct mem_cgroup *memcg, *old_memcg;
 	struct folio *new_folio, *next;
 	int nr_shmem_dropped = 0;
@@ -4135,22 +4136,16 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 		goto fail;
 	}
 
-	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
+	if (!folio_ref_freeze(folio, old_nr_pages + 1)) {
 		ret = -EAGAIN;
 		goto fail;
 	}
 
-	if (folio_test_pmd_mappable(folio) &&
-	    new_order < HPAGE_PMD_ORDER) {
-		int nr = folio_nr_pages(folio);
-
-		if (folio_test_swapbacked(folio)) {
-			lruvec_stat_mod_folio(folio,
-					      NR_SHMEM_THPS, -nr);
-		} else {
-			lruvec_stat_mod_folio(folio,
-					      NR_FILE_THPS, -nr);
-		}
+	if (folio_test_pmd_mappable(folio) && new_order < HPAGE_PMD_ORDER) {
+		if (folio_test_swapbacked(folio))
+			lruvec_stat_mod_folio(folio, NR_SHMEM_THPS, -old_nr_pages);
+		else
+			lruvec_stat_mod_folio(folio, NR_FILE_THPS, -old_nr_pages);
 	}
 
 	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
@@ -4175,7 +4170,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 		next = folio_next(new_folio);
 
 		folio_ref_unfreeze(new_folio,
-				   folio_cache_ref_count(new_folio) + 1);
+				   folio_nr_pages(new_folio) + 1);
 
 		if (do_lru)
 			lru_add_split_folio(folio, new_folio, lruvec, list);
@@ -4203,7 +4198,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	 * Otherwise, a parallel folio_try_get() can grab @folio
 	 * and its caller can see stale page cache entries.
 	 */
-	folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+	folio_ref_unfreeze(folio, folio_nr_pages(folio) + 1);
 
 	if (do_lru)
 		lruvec_unlock(lruvec);

-- 
2.55.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH RFC 13/13] mm/huge_memory: count only swap cache refs in anon folio split
@ 2026-08-07 21:17   ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-07 21:17 UTC (permalink / raw)
  To: linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts,
	Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park, Kairui Song

Only __folio_freeze_split_unmap() sees anon folios and swap cache folios
now. The file split helper only handles page cache folios, which hold
exactly folio_nr_pages() references.

Rename folio_cache_ref_count() to folio_swapcache_ref_count() and drop
the anon check so the helper counts what its name says. The file split
helper now uses folio_nr_pages() directly.

Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 35 +++++++++++++++--------------------
 1 file changed, 15 insertions(+), 20 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index dba53fbb93a8..0d70ee3017c4 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3915,10 +3915,10 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	return 0;
 }
 
-/* Number of folio references from the pagecache or the swapcache. */
-static unsigned int folio_cache_ref_count(const struct folio *folio)
+/* Number of folio references from the swapcache. */
+static unsigned int folio_swapcache_ref_count(const struct folio *folio)
 {
-	if (folio_test_anon(folio) && !folio_test_swapcache(folio))
+	if (!folio_test_swapcache(folio))
 		return 0;
 	return folio_nr_pages(folio);
 }
@@ -3984,7 +3984,7 @@ static int __folio_freeze_split_unmap(struct folio *folio, unsigned int new_orde
 				    folio_nid(folio), &memcg);
 	}
 
-	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
+	if (!folio_ref_freeze(folio, folio_swapcache_ref_count(folio) + 1)) {
 		if (dequeue_deferred) {
 			list_lru_unlock(lru);
 			rcu_read_unlock();
@@ -4027,7 +4027,7 @@ static int __folio_freeze_split_unmap(struct folio *folio, unsigned int new_orde
 	     new_folio = folio_next(new_folio)) {
 		zone_device_private_split_cb(folio, new_folio);
 		folio_ref_unfreeze(new_folio,
-				   folio_cache_ref_count(new_folio) + 1);
+				   folio_swapcache_ref_count(new_folio) + 1);
 		if (do_lru)
 			lru_add_split_folio(folio, new_folio, lruvec, list);
 		if (ci)
@@ -4035,7 +4035,7 @@ static int __folio_freeze_split_unmap(struct folio *folio, unsigned int new_orde
 	}
 
 	zone_device_private_split_cb(folio, NULL);
-	folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+	folio_ref_unfreeze(folio, folio_swapcache_ref_count(folio) + 1);
 
 	if (do_lru)
 		lruvec_unlock(lruvec);
@@ -4064,6 +4064,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	struct address_space *mapping = folio->mapping;
 	XA_STATE(xas, &mapping->i_pages, folio->index);
 	struct folio *end_folio = folio_next(folio);
+	long old_nr_pages = folio_nr_pages(folio);
 	struct mem_cgroup *memcg, *old_memcg;
 	struct folio *new_folio, *next;
 	int nr_shmem_dropped = 0;
@@ -4135,22 +4136,16 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 		goto fail;
 	}
 
-	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
+	if (!folio_ref_freeze(folio, old_nr_pages + 1)) {
 		ret = -EAGAIN;
 		goto fail;
 	}
 
-	if (folio_test_pmd_mappable(folio) &&
-	    new_order < HPAGE_PMD_ORDER) {
-		int nr = folio_nr_pages(folio);
-
-		if (folio_test_swapbacked(folio)) {
-			lruvec_stat_mod_folio(folio,
-					      NR_SHMEM_THPS, -nr);
-		} else {
-			lruvec_stat_mod_folio(folio,
-					      NR_FILE_THPS, -nr);
-		}
+	if (folio_test_pmd_mappable(folio) && new_order < HPAGE_PMD_ORDER) {
+		if (folio_test_swapbacked(folio))
+			lruvec_stat_mod_folio(folio, NR_SHMEM_THPS, -old_nr_pages);
+		else
+			lruvec_stat_mod_folio(folio, NR_FILE_THPS, -old_nr_pages);
 	}
 
 	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
@@ -4175,7 +4170,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 		next = folio_next(new_folio);
 
 		folio_ref_unfreeze(new_folio,
-				   folio_cache_ref_count(new_folio) + 1);
+				   folio_nr_pages(new_folio) + 1);
 
 		if (do_lru)
 			lru_add_split_folio(folio, new_folio, lruvec, list);
@@ -4203,7 +4198,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
 	 * Otherwise, a parallel folio_try_get() can grab @folio
 	 * and its caller can see stale page cache entries.
 	 */
-	folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+	folio_ref_unfreeze(folio, folio_nr_pages(folio) + 1);
 
 	if (do_lru)
 		lruvec_unlock(lruvec);

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 01/13] mm/swap: fix off-by-one in swap cache replace sanity check
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-08 17:07   ` Zi Yan
  -1 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-08 17:07 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> The DEBUG_VM sanity check in __swap_cache_replace_folio() iterates
> the old folio's range with "while (ci_off++ < ci_end)", so the loop
> body runs on the already-incremented offset: the first entry is
> skipped and one entry past the range is read.  For a folio split
> that entry belongs to the first after-split folio and was just
> repointed by the replacement loop above, so the check would warn
> spuriously whenever sub-folio orders differ from the head folio's,
> as non-uniform swapcache splits now do.
>
> Use the same do-while pattern as the replacement loop.
>
> Fixes: 8578e0c00dcf ("mm, swap: use the swap table for the swap cache and switch API")
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/swap_state.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)

LGTM.

Acked-by: Zi Yan <ziy@nvidia.com>

-- 
Best Regards,
Yan, Zi



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 02/13] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-08 18:01   ` Zi Yan
  2026-08-08 18:14     ` Kairui Song
  -1 siblings, 1 reply; 45+ messages in thread
From: Zi Yan @ 2026-08-08 18:01 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> A folio in the swap cache cannot be split if it has a mapping (shmem).
> The split code only checks for this in __folio_freeze_and_split_unmapped,
> after the folio ref has been frozen and the NR_SHMEM_THPS/NR_FILE_THPS
> counters have been decremented, and returns -EINVAL without unfreezing
> the folio or restoring the counters. That error path is fragile: if it
> is ever taken, the folio is left frozen and stuck, the counters are
> skewed, and the VM_WARN_ON_ONCE_FOLIO would fire for a state that is
> actually legitimate.
>
> Check for this case up front in folio_check_splittable and return
> -EINVAL before any state is modified. Under DEBUG_VM, the existing
> "Tried to split an unsplittable folio" warning in __folio_split
> reports the rejection.

Should we return -EBUSY instead? -EINVAL means the caller should not
split a swapcache shmem with a mapping and the caller needs to avoid
that. The Fixes tag tells me a caller can split a swapcache shmem with a
mapping, so with -EINVAL, we will want to add checks at callers to avoid
it from happening.

>
> Fixes: 00527733d0dc ("mm/huge_memory: add two new (not yet used) functions for folio_split()")
> Fixes: 714b056c8321 ("mm/huge_memory: convert VM_BUG* to VM_WARN* in __folio_split")
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 26 ++++++++++++++++----------
>  1 file changed, 16 insertions(+), 10 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index ced400f72d43..2fa72158e063 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -3878,6 +3878,9 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
>  int folio_check_splittable(struct folio *folio, unsigned int new_order,
>  			   enum split_type split_type)
>  {
> +	bool is_anon = folio_test_anon(folio);
> +	bool is_swapcache = folio_test_swapcache(folio);
> +
>  	VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
>  	/*
>  	 * Folios that just got truncated cannot get split. Signal to the
> @@ -3886,11 +3889,11 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
>  	 * TODO: this will also currently refuse folios without a mapping in the
>  	 * swapcache (shmem or to-be-anon folios).
>  	 */
> -	if (!folio->mapping && !folio_test_anon(folio))
> +	if (!folio->mapping && !is_anon)
>  		return -EBUSY;
>  
>  	/* order-1 is not supported for anonymous THP. */
> -	if (folio_test_anon(folio) && new_order == 1)
> +	if (is_anon && new_order == 1)
>  		return -EINVAL;
>  
>  	/*
> @@ -3901,7 +3904,7 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
>  	 * swapcache folio split. Only uniform split to order-0 can be used
>  	 * here.
>  	 */
> -	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && folio_test_swapcache(folio)) {
> +	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && is_swapcache) {
>  		return -EINVAL;
>  	}
>  
> @@ -3911,6 +3914,15 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
>  	if (folio_test_writeback(folio))
>  		return -EBUSY;
>  
> +	/*
> +	 * A non-anon swapcache folio that still has a mapping should only
> +	 * be a shmem folio under IO, there is little benefit in splitting
> +	 * them hence not supported. Reject it here up front: the split
> +	 * routine cannot back out cleanly once the folio ref is frozen.
> +	 */
> +	if (!is_anon && is_swapcache && folio->mapping)
> +		return -EINVAL;
> +
>  	return 0;
>  }
>  
> @@ -3983,14 +3995,8 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
>  			}
>  		}
>  
> -		if (folio_test_swapcache(folio)) {
> -			if (mapping) {
> -				VM_WARN_ON_ONCE_FOLIO(mapping, folio);
> -				return -EINVAL;
> -			}
> -
> +		if (folio_test_swapcache(folio))
>  			ci = swap_cluster_get_and_lock(folio);
> -		}
>  
>  		/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
>  		if (do_lru)




-- 
Best Regards,
Yan, Zi



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 03/13] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-08 18:04   ` Zi Yan
  -1 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-08 18:04 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> Invert the folio_ref_freeze() success check in
> __folio_freeze_and_split_unmapped() to return early on failure, which
> removes one level of indentation from the entire success path.
>
> This is a pure refactoring with no functional change.  It prepares the
> function to be split into separate helpers for anonymous and
> file-backed folios in a later patch.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 181 +++++++++++++++++++++++++++----------------------------
>  1 file changed, 90 insertions(+), 91 deletions(-)
>

LGTM. Thanks for the cleanup.

Reviewed-by: Zi Yan <ziy@nvidia.com>

-- 
Best Regards,
Yan, Zi


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 02/13] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-08 18:01   ` Zi Yan
@ 2026-08-08 18:14     ` Kairui Song
  2026-08-08 18:53       ` Zi Yan
  0 siblings, 1 reply; 45+ messages in thread
From: Kairui Song @ 2026-08-08 18:14 UTC (permalink / raw)
  To: Zi Yan
  Cc: linux-mm, linux-kernel, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Baolin Wang, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Chris Li,
	Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Youngjun Park

On Sun, Aug 9, 2026 at 2:02 AM Zi Yan <ziy@nvidia.com> wrote:
>
> On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> > From: Kairui Song <kasong@tencent.com>
> >
> > A folio in the swap cache cannot be split if it has a mapping (shmem).
> > The split code only checks for this in __folio_freeze_and_split_unmapped,
> > after the folio ref has been frozen and the NR_SHMEM_THPS/NR_FILE_THPS
> > counters have been decremented, and returns -EINVAL without unfreezing
> > the folio or restoring the counters. That error path is fragile: if it
> > is ever taken, the folio is left frozen and stuck, the counters are
> > skewed, and the VM_WARN_ON_ONCE_FOLIO would fire for a state that is
> > actually legitimate.
> >
> > Check for this case up front in folio_check_splittable and return
> > -EINVAL before any state is modified. Under DEBUG_VM, the existing
> > "Tried to split an unsplittable folio" warning in __folio_split
> > reports the rejection.
>
> Should we return -EBUSY instead? -EINVAL means the caller should not
> split a swapcache shmem with a mapping and the caller needs to avoid
> that. The Fixes tag tells me a caller can split a swapcache shmem with a
> mapping, so with -EINVAL, we will want to add checks at callers to avoid
> it from happening.
>

I can drop the Fixes tags. I meant that there is already some
defensive code that trying to catch it and return -EINVAL, however,
that defensive code itself is flawed. If this situation occurs due to
a bug or future misuse, the flawed code will causes the folio to get
stuck in a frozen state. The defensive code should at least not make
things worse.

Fortunately I think no one needs to split a hybrid shmem swap cache
folio, returning -EINVAL here may help catch any potential future
misuse.


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 04/13] mm/huge_memory: split the routine for splitting anon and file folio
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-08 18:52   ` Zi Yan
  2026-08-08 20:19     ` Kairui Song
  -1 siblings, 1 reply; 45+ messages in thread
From: Zi Yan @ 2026-08-08 18:52 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> No functional change intended. Before adding more logic, split
> __folio_freeze_and_split_unmapped() into an anon and a file variant so
> each path can evolve independently. The two paths shared little beyond
> the folio freeze call, the LRU locking, and the unfreeze skeleton, but
> differed in all other per-folio bookkeeping and routines.

While at it, can you rename __split_unmapped_folio() to
__split_frozen_folio() to reflect the actual folio state? It is causing
confusion and people tried to use __split_unmapped_folio() on non frozen
folios.

>
> While splitting, some cleanups become easy to apply, and helped dropping
> a few now redundant checks.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 121 ++++++++++++++++++++++++++++++++++---------------------
>  1 file changed, 76 insertions(+), 45 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index cf8f90b94e42..56a356c30f30 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -3934,22 +3934,19 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
>  	return folio_nr_pages(folio);
>  }
>  
> -static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int new_order,
> -					     struct page *split_at, struct xa_state *xas,
> -					     struct address_space *mapping, bool do_lru,
> -					     struct list_head *list, enum split_type split_type,
> -					     pgoff_t end, int *nr_shmem_dropped)
> +static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int new_order,
> +					      struct page *split_at, bool do_lru,
> +					      struct list_head *list, enum split_type split_type)
>  {
>  	struct folio *end_folio = folio_next(folio);
>  	struct swap_cluster_info *ci = NULL;
> -	struct folio *new_folio, *next;
> +	struct folio *new_folio;
>  	int old_order = folio_order(folio);
>  	struct list_lru_one *lru;
>  	struct lruvec *lruvec;
>  	bool dequeue_deferred;
>  	int ret = 0;
>  
> -	VM_WARN_ON_ONCE(!mapping && end);

We no longer need mapping here, the caller already makes sure mapping is
NULL. Great!

>  	/*
>  	 * If this folio can be on the deferred split queue, lock out
>  	 * the shrinker before freezing the ref. If the shrinker sees
> @@ -3957,7 +3954,7 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
>  	 * lock and must clean up the LRU state - the same dequeue we
>  	 * will do below as part of the split.
>  	 */
> -	dequeue_deferred = folio_test_anon(folio) && old_order > 1;
> +	dequeue_deferred = old_order > 1;
>  	if (dequeue_deferred) {
>  		struct mem_cgroup *memcg;
>  
> @@ -3987,24 +3984,73 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
>  		rcu_read_unlock();
>  	}
>  
> -	if (mapping) {
> +	if (folio_test_swapcache(folio))
> +		ci = swap_cluster_get_and_lock(folio);
> +
> +	if (do_lru)
> +		lruvec = folio_lruvec_lock(folio);
> +
> +	ret = __split_unmapped_folio(folio, new_order, split_at, NULL,
> +				     NULL, split_type);
> +
> +	/*
> +	 * Unfreeze the after-split folios and put them back to the right
> +	 * place, keeping the head @folio frozen until the end. While the
> +	 * folio is in the swap cache, the sub entries must be updated with
> +	 * their after-split folios before the head is unfrozen, so a
> +	 * concurrent swap_cache_get_folio() cannot return the head folio
> +	 * for a sub entry. Keeping the head frozen throughout also stops a
> +	 * parallel folio_try_get() from observing a partially split folio.
> +	 */
> +	for (new_folio = folio_next(folio); new_folio != end_folio;
> +	     new_folio = folio_next(new_folio)) {

Please keep the existing for loop pattern by using next =
folio_next(new_folio) in the loop buddy.

Hugh pointed out an issue when I did the above for loop pattern[1].
Basically, folio_next() reads folio_nr_pages() and relies on a stable
new_folio input. In my old code, the input of folio_next() can be freed
and causing oops. In your code, that does not apply, but it can bite
people in the future the loop body changes and new_folio's lifetime ends
before the for loop finishes.

Maybe add a comment to explain why next = folio_next(new_folio) should
be used.

[1] https://lore.kernel.org/all/2fae27fe-6e2e-3587-4b68-072118d80cf8@google.com/

> +		zone_device_private_split_cb(folio, new_folio);
> +		folio_ref_unfreeze(new_folio,
> +				   folio_cache_ref_count(new_folio) + 1);
> +		if (do_lru)
> +			lru_add_split_folio(folio, new_folio, lruvec, list);
> +		if (ci)
> +			__swap_cache_replace_folio(ci, folio, new_folio);
> +	}
> +
> +	zone_device_private_split_cb(folio, NULL);
> +	folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
> +
> +	if (do_lru)
> +		lruvec_unlock(lruvec);
> +	if (ci)
> +		swap_cluster_unlock(ci);
> +
> +	return ret;
> +}
> +
> +static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int new_order,
> +					      struct page *split_at, struct xa_state *xas,
> +					      struct address_space *mapping, bool do_lru,
> +					      struct list_head *list, enum split_type split_type,
> +					      pgoff_t end, int *nr_shmem_dropped)
> +{
> +	struct folio *end_folio = folio_next(folio);
> +	struct folio *new_folio, *next;
> +	struct lruvec *lruvec;
> +	int ret;
> +
> +	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1))
> +		return -EAGAIN;
> +
> +	if (folio_test_pmd_mappable(folio) &&
> +	    new_order < HPAGE_PMD_ORDER) {
>  		int nr = folio_nr_pages(folio);
>  
> -		if (folio_test_pmd_mappable(folio) &&
> -		    new_order < HPAGE_PMD_ORDER) {
> -			if (folio_test_swapbacked(folio)) {
> -				lruvec_stat_mod_folio(folio,
> -						      NR_SHMEM_THPS, -nr);
> -			} else {
> -				lruvec_stat_mod_folio(folio,
> -						      NR_FILE_THPS, -nr);
> -			}
> +		if (folio_test_swapbacked(folio)) {
> +			lruvec_stat_mod_folio(folio,
> +					      NR_SHMEM_THPS, -nr);
> +		} else {
> +			lruvec_stat_mod_folio(folio,
> +					      NR_FILE_THPS, -nr);
>  		}
>  	}
>  
> -	if (folio_test_swapcache(folio))
> -		ci = swap_cluster_get_and_lock(folio);
> -
>  	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
>  	if (do_lru)
>  		lruvec = folio_lruvec_lock(folio);
> @@ -4014,7 +4060,7 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
>  
>  	/*
>  	 * Unfreeze after-split folios and put them back to the right
> -	 * list. @folio should be kept frozon until page cache
> +	 * list. @folio should be kept frozen until page cache
>  	 * entries are updated with all the other after-split folios
>  	 * to prevent others seeing stale page cache entries.
>  	 * As a result, new_folio starts from the next folio of
> @@ -4026,27 +4072,12 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
>  
>  		next = folio_next(new_folio);
>  
> -		zone_device_private_split_cb(folio, new_folio);
> -
>  		folio_ref_unfreeze(new_folio,
>  				   folio_cache_ref_count(new_folio) + 1);
>  
>  		if (do_lru)
>  			lru_add_split_folio(folio, new_folio, lruvec, list);
>  
> -		/*
> -		 * Anonymous folio with swap cache.
> -		 * NOTE: shmem in swap cache is not supported yet.
> -		 */
> -		if (ci) {
> -			__swap_cache_replace_folio(ci, folio, new_folio);
> -			continue;
> -		}
> -
> -		/* Anonymous folio without swap cache */
> -		if (!mapping)
> -			continue;
> -
>  		/* Add the new folio to the page cache. */
>  		if (new_folio->index < end) {
>  			__xa_store(&mapping->i_pages, new_folio->index,
> @@ -4065,7 +4096,6 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
>  		folio_put_refs(new_folio, nr_pages);
>  	}
>  
> -	zone_device_private_split_cb(folio, NULL);
>  	/*
>  	 * Unfreeze @folio only after all page cache entries, which
>  	 * used to point to it, have been updated with new folios.
> @@ -4076,8 +4106,6 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
>  
>  	if (do_lru)
>  		lruvec_unlock(lruvec);
> -	if (ci)
> -		swap_cluster_unlock(ci);
>  
>  	return ret;
>  }
> @@ -4231,10 +4259,14 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>  			ret = -EAGAIN;
>  			goto fail;
>  		}
> +		ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
> +							 true, list, split_type, end,
> +							 &nr_shmem_dropped);
> +	} else {
> +		ret = __folio_freeze_split_unmapped_anon(folio, new_order, split_at, true,
> +							 list, split_type);
>  	}
>  
> -	ret = __folio_freeze_and_split_unmapped(folio, new_order, split_at, &xas, mapping,
> -						true, list, split_type, end, &nr_shmem_dropped);
>  fail:
>  	if (mapping)
>  		xas_unlock(&xas);
> @@ -4334,9 +4366,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
>  		return -EAGAIN;
>  
>  	local_irq_disable();
> -	ret = __folio_freeze_and_split_unmapped(folio, new_order, &folio->page, NULL,
> -						NULL, false, NULL, SPLIT_TYPE_UNIFORM,
> -						0, NULL);
> +	ret = __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
> +						 false, NULL, SPLIT_TYPE_UNIFORM);
>  	local_irq_enable();
>  	return ret;
>  }

There are some code duplications but overall looks good to me. The lru
lock, unfreeze loop, and the last unfreeze are replicated across two
functions. I cannot think of an easy alternative. A tiny improvement
might be instead of replicating unfreeze comments, changing one to point
to the other one and asking the code should be in sync.

-- 
Best Regards,
Yan, Zi


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 02/13] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-08 18:14     ` Kairui Song
@ 2026-08-08 18:53       ` Zi Yan
  0 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-08 18:53 UTC (permalink / raw)
  To: Kairui Song
  Cc: linux-mm, linux-kernel, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Baolin Wang, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Chris Li,
	Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Youngjun Park

On Sat Aug 8, 2026 at 2:14 PM EDT, Kairui Song wrote:
> On Sun, Aug 9, 2026 at 2:02 AM Zi Yan <ziy@nvidia.com> wrote:
>>
>> On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
>> > From: Kairui Song <kasong@tencent.com>
>> >
>> > A folio in the swap cache cannot be split if it has a mapping (shmem).
>> > The split code only checks for this in __folio_freeze_and_split_unmapped,
>> > after the folio ref has been frozen and the NR_SHMEM_THPS/NR_FILE_THPS
>> > counters have been decremented, and returns -EINVAL without unfreezing
>> > the folio or restoring the counters. That error path is fragile: if it
>> > is ever taken, the folio is left frozen and stuck, the counters are
>> > skewed, and the VM_WARN_ON_ONCE_FOLIO would fire for a state that is
>> > actually legitimate.
>> >
>> > Check for this case up front in folio_check_splittable and return
>> > -EINVAL before any state is modified. Under DEBUG_VM, the existing
>> > "Tried to split an unsplittable folio" warning in __folio_split
>> > reports the rejection.
>>
>> Should we return -EBUSY instead? -EINVAL means the caller should not
>> split a swapcache shmem with a mapping and the caller needs to avoid
>> that. The Fixes tag tells me a caller can split a swapcache shmem with a
>> mapping, so with -EINVAL, we will want to add checks at callers to avoid
>> it from happening.
>>
>
> I can drop the Fixes tags. I meant that there is already some

If it can happen, we want to fix it.

> defensive code that trying to catch it and return -EINVAL, however,
> that defensive code itself is flawed. If this situation occurs due to
> a bug or future misuse, the flawed code will causes the folio to get
> stuck in a frozen state. The defensive code should at least not make
> things worse.
>
> Fortunately I think no one needs to split a hybrid shmem swap cache
> folio, returning -EINVAL here may help catch any potential future
> misuse.

Sounds reasonable to me.

-- 
Best Regards,
Yan, Zi



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 04/13] mm/huge_memory: split the routine for splitting anon and file folio
  2026-08-08 18:52   ` Zi Yan
@ 2026-08-08 20:19     ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-08 20:19 UTC (permalink / raw)
  To: Zi Yan
  Cc: linux-mm, linux-kernel, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Baolin Wang, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Chris Li,
	Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Youngjun Park

On Sun, Aug 9, 2026 at 2:52 AM Zi Yan <ziy@nvidia.com> wrote:
>
> On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> > From: Kairui Song <kasong@tencent.com>
> >
> > No functional change intended. Before adding more logic, split
> > __folio_freeze_and_split_unmapped() into an anon and a file variant so
> > each path can evolve independently. The two paths shared little beyond
> > the folio freeze call, the LRU locking, and the unfreeze skeleton, but
> > differed in all other per-folio bookkeeping and routines.
>
> While at it, can you rename __split_unmapped_folio() to
> __split_frozen_folio() to reflect the actual folio state? It is causing
> confusion and people tried to use __split_unmapped_folio() on non frozen
> folios.

Will do.

> > @@ -3987,24 +3984,73 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
> >               rcu_read_unlock();
> >       }
> >
> > -     if (mapping) {
> > +     if (folio_test_swapcache(folio))
> > +             ci = swap_cluster_get_and_lock(folio);
> > +
> > +     if (do_lru)
> > +             lruvec = folio_lruvec_lock(folio);
> > +
> > +     ret = __split_unmapped_folio(folio, new_order, split_at, NULL,
> > +                                  NULL, split_type);
> > +
> > +     /*
> > +      * Unfreeze the after-split folios and put them back to the right
> > +      * place, keeping the head @folio frozen until the end. While the
> > +      * folio is in the swap cache, the sub entries must be updated with
> > +      * their after-split folios before the head is unfrozen, so a
> > +      * concurrent swap_cache_get_folio() cannot return the head folio
> > +      * for a sub entry. Keeping the head frozen throughout also stops a
> > +      * parallel folio_try_get() from observing a partially split folio.
> > +      */
> > +     for (new_folio = folio_next(folio); new_folio != end_folio;
> > +          new_folio = folio_next(new_folio)) {
>
> Please keep the existing for loop pattern by using next =
> folio_next(new_folio) in the loop buddy.
>
> Hugh pointed out an issue when I did the above for loop pattern[1].
> Basically, folio_next() reads folio_nr_pages() and relies on a stable
> new_folio input. In my old code, the input of folio_next() can be freed
> and causing oops. In your code, that does not apply, but it can bite
> people in the future the loop body changes and new_folio's lifetime ends
> before the for loop finishes.
>
> Maybe add a comment to explain why next = folio_next(new_folio) should
> be used.

Sure, I'll try if a macro can be used to deduplicate it.

>
> [1] https://lore.kernel.org/all/2fae27fe-6e2e-3587-4b68-072118d80cf8@google.com/
>

...

> > @@ -4231,10 +4259,14 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
> >                       ret = -EAGAIN;
> >                       goto fail;
> >               }
> > +             ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
> > +                                                      true, list, split_type, end,
> > +                                                      &nr_shmem_dropped);
> > +     } else {
> > +             ret = __folio_freeze_split_unmapped_anon(folio, new_order, split_at, true,
> > +                                                      list, split_type);
> >       }
> >
> > -     ret = __folio_freeze_and_split_unmapped(folio, new_order, split_at, &xas, mapping,
> > -                                             true, list, split_type, end, &nr_shmem_dropped);
> >  fail:
> >       if (mapping)
> >               xas_unlock(&xas);
> > @@ -4334,9 +4366,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
> >               return -EAGAIN;
> >
> >       local_irq_disable();
> > -     ret = __folio_freeze_and_split_unmapped(folio, new_order, &folio->page, NULL,
> > -                                             NULL, false, NULL, SPLIT_TYPE_UNIFORM,
> > -                                             0, NULL);
> > +     ret = __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
> > +                                              false, NULL, SPLIT_TYPE_UNIFORM);
> >       local_irq_enable();
> >       return ret;
> >  }
>
> There are some code duplications but overall looks good to me. The lru
> lock, unfreeze loop, and the last unfreeze are replicated across two
> functions. I cannot think of an easy alternative. A tiny improvement
> might be instead of replicating unfreeze comments, changing one to point
> to the other one and asking the code should be in sync.

Good suggestion, thanks! I think code duplications could be further
reduced by a few macros or helpers. In following patches, removing
many if branches and streamlining the workflow actually improved it as
a whole, so I think it's worth it.


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 05/13] mm/huge_memory: consolidate irq and locking for folio split
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-09  1:54   ` Zi Yan
  2026-08-10  3:35     ` Kairui Song
  -1 siblings, 1 reply; 45+ messages in thread
From: Zi Yan @ 2026-08-09  1:54 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> Let each split helper handle its own locking instead of relying on
> the caller, so both paths follow the same convention and __folio_split()
> can drop its local irq handling and fail label, preparing for further
> cleanup.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 52 ++++++++++++++++++++++++----------------------------
>  1 file changed, 24 insertions(+), 28 deletions(-)
>

<snip>

anon part is not changed.

> @@ -4035,8 +4039,21 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
>  	struct lruvec *lruvec;
>  	int ret;
>  
> -	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1))
> -		return -EAGAIN;
> +	xas_lock_irq(xas);

local_irq_disable() + xas_lock() is replaced by xas_lock_irq(). Are they
equivalent? Codex told me that the latter no longer prevents preemption
on RT kernel and can lengthen folio split process if it is preempted. It
is worth spell out in the commit message after you verify it.

> +
> +	/*
> +	 * Check if the folio is present in page cache.
> +	 * We assume all tail are present too, if folio is there.
> +	 */

xas_reset() is gone here. It seems to be a no-op, since xas is not
walked yet. But it is better to mention it in the commit message.

> +	if (xas_load(xas) != folio) {
> +		ret = -EAGAIN;
> +		goto fail;
> +	}
> +
> +	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
> +		ret = -EAGAIN;
> +		goto fail;
> +	}
>  
>  	if (folio_test_pmd_mappable(folio) &&
>  	    new_order < HPAGE_PMD_ORDER) {
> @@ -4107,6 +4124,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
>  	if (do_lru)
>  		lruvec_unlock(lruvec);
>  
> +fail:
> +	xas_unlock_irq(xas);
>  	return ret;
>  }
>  
> @@ -4246,19 +4265,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>  
>  	unmap_folio(folio);
>  
> -	/* block interrupt reentry in xa_lock and spinlock */
> -	local_irq_disable();
> -	if (mapping) {
> -		/*
> -		 * Check if the folio is present in page cache.
> -		 * We assume all tail are present too, if folio is there.
> -		 */
> -		xas_lock(&xas);
> -		xas_reset(&xas);
> -		if (xas_load(&xas) != folio) {
> -			ret = -EAGAIN;
> -			goto fail;
> -		}
> +	if (!is_anon) {
>  		ret = __folio_freeze_split_unmapped_file(folio, new_order, split_at, &xas, mapping,
>  							 true, list, split_type, end,
>  							 &nr_shmem_dropped);
> @@ -4267,12 +4274,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>  							 list, split_type);
>  	}
>  
> -fail:
> -	if (mapping)
> -		xas_unlock(&xas);
> -
> -	local_irq_enable();
> -
>  	if (nr_shmem_dropped)
>  		shmem_uncharge(mapping->host, nr_shmem_dropped);
>  
> @@ -4355,8 +4356,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>   */
>  int folio_split_unmapped(struct folio *folio, unsigned int new_order)
>  {
> -	int ret = 0;
> -
>  	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
>  	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
>  	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
> @@ -4365,11 +4364,8 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
>  	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
>  		return -EAGAIN;
>  
> -	local_irq_disable();
> -	ret = __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
> -						 false, NULL, SPLIT_TYPE_UNIFORM);
> -	local_irq_enable();
> -	return ret;
> +	return __folio_freeze_split_unmapped_anon(folio, new_order, &folio->page,
> +						  false, NULL, SPLIT_TYPE_UNIFORM);
>  }
>  
>  /*

It is nice to see caller no longer needs to care about
local_irq_disable/enable().



-- 
Best Regards,
Yan, Zi



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 06/13] mm/huge_memory: move EOF trimming into the file split helper
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-09  1:59   ` Zi Yan
  -1 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-09  1:59 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> Instead of receiving @end and @nr_shmem_dropped from the caller, the
> file split helper now computes the EOF boundary and trims pages beyond
> it itself, as this is only needed for file split.  This drops the
> redundant parameter passing and sanity check.

And the end calculation is done under i_mmap_lock_read() already, move
it inside the locked region should not change code behavior.

>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 42 +++++++++++++++++++-----------------------
>  1 file changed, 19 insertions(+), 23 deletions(-)
>

Reviewed-by: Zi Yan <ziy@nvidia.com>

-- 
Best Regards,
Yan, Zi


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 07/13] mm/huge_memory: move unmap and remap into the split helpers
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-09  2:13   ` Zi Yan
  -1 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-09  2:13 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> To prepare for further cleanup, move the unmap/remap handling from
> __folio_split() into the split helpers.  Only anon folios need to
> be remapped, so remap_page() is now only called for anon splits and
> the anon check in remap_page() is redundant and can be removed.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 51 ++++++++++++++++++++++++++-------------------------
>  1 file changed, 26 insertions(+), 25 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 72f5d0d24127..c0115841d1a0 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -3589,9 +3589,6 @@ static void remap_page(struct folio *folio, unsigned long nr, int flags)
>  {
>  	int i = 0;
>  
> -	/* If unmap_folio() uses try_to_migrate() on file, remove this check */

This comment is lost. Like it says, if file folios are unmappped using
migration entries, __folio_freeze_split_unmapped_file() will need to
call remap_page(). Can you move this comment to the end of
__folio_freeze_split_unmap_file(), where remap_page() could be called.

> -	if (!folio_test_anon(folio))
> -		return;
>  	for (;;) {
>  		remove_migration_ptes(folio, folio, TTU_RMAP_LOCKED | flags);
>  		i += folio_nr_pages(folio);
> @@ -3934,19 +3931,23 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
>  	return folio_nr_pages(folio);
>  }
>  
> -static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int new_order,
> -					      struct page *split_at, bool do_lru,
> -					      struct list_head *list, enum split_type split_type)
> +static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new_order,

__folio_split_unmap_and_freeze_anon() might be better?

> +					   struct page *split_at, bool do_lru, bool unmap,
> +					   struct list_head *list, enum split_type split_type)
>  {
>  	struct folio *end_folio = folio_next(folio);
>  	struct swap_cluster_info *ci = NULL;
>  	struct folio *new_folio;
>  	int old_order = folio_order(folio);
> +	enum ttu_flags ttu_flags = 0;
>  	struct list_lru_one *lru;
>  	struct lruvec *lruvec;
>  	bool dequeue_deferred;
>  	int ret = 0;
>  
> +	if (unmap)
> +		unmap_folio(folio);

This unmap parameter is to accommodate folio_split_unmapped() and
counter-intuitive for __folio_freeze_split_unmap_anon() this function
name. __folio_split_may_unmap_and_freeze_anon() might match the code
better, but sounds ugly. :(

With the moved comment, feel free to add

Reviewed-by: Zi Yan <ziy@nvidia.com>

The function name is secondary.


-- 
Best Regards,
Yan, Zi



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 08/13] mm/huge_memory: move anon_vma and filemap management into split helpers
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-09  2:22   ` Zi Yan
  -1 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-09  2:22 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> Only anon split needs vma info, and only file split needs the filemap
> handling. Move the related code into separate helpers so they are
> genuinely more self-contained.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 177 +++++++++++++++++++++++++------------------------------
>  1 file changed, 79 insertions(+), 98 deletions(-)
>

<snip>

>  static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new_order,
> -					   struct page *split_at, struct xa_state *xas,
> -					   struct address_space *mapping, bool do_lru,
> +					   struct page *split_at, bool do_lru,
>  					   struct list_head *list, enum split_type split_type)
>  {
> +	struct address_space *mapping = folio->mapping;
> +	XA_STATE(xas, &mapping->i_pages, folio->index);

mapping should be always non-NULL now. mapping->i_pages no longer exists
for anon code path. I like it.

>  	struct folio *end_folio = folio_next(folio);
>  	struct folio *new_folio, *next;
>  	int nr_shmem_dropped = 0;
> +	unsigned int min_order;
>  	struct lruvec *lruvec;
>  	pgoff_t end = 0;
> -	int ret;
> +	gfp_t gfp;
> +	int ret = 0;
> +
> +	min_order = mapping_min_folio_order(mapping);
> +	if (new_order < min_order)
> +		return -EINVAL;
> +
> +	gfp = current_gfp_context(mapping_gfp_mask(mapping) & GFP_RECLAIM_MASK);
> +	if (!filemap_release_folio(folio, gfp))
> +		return -EBUSY;
> +
> +	mapping_set_update(&xas, mapping);
> +
> +	if (split_type == SPLIT_TYPE_UNIFORM) {
> +		int old_order = folio_order(folio);
> +
> +		xas_set_order(&xas, folio->index, new_order);
> +		xas_split_alloc(&xas, folio, old_order, gfp);
> +		if (xas_error(&xas)) {
> +			ret = xas_error(&xas);
> +			goto fail_free;
> +		}
> +	}
> +
> +	i_mmap_lock_read(mapping);
> +
> +	/* Racy check if we can split the page, before unmap_folio() */
> +	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
> +		ret = -EAGAIN;
> +		goto fail_mmap_unlock;
> +	}
>  
>  	/*
>  	 *__split_unmapped_folio() may need to trim off pages beyond
> @@ -4060,13 +4118,13 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
>  
>  	unmap_folio(folio);
>  
> -	xas_lock_irq(xas);
> +	xas_lock_irq(&xas);
>  
>  	/*
>  	 * Check if the folio is present in page cache.
>  	 * We assume all tail are present too, if folio is there.
>  	 */
> -	if (xas_load(xas) != folio) {
> +	if (xas_load(&xas) != folio) {
>  		ret = -EAGAIN;
>  		goto fail;
>  	}
> @@ -4093,7 +4151,7 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
>  	if (do_lru)
>  		lruvec = folio_lruvec_lock(folio);
>  
> -	ret = __split_unmapped_folio(folio, new_order, split_at, xas,
> +	ret = __split_unmapped_folio(folio, new_order, split_at, &xas,
>  				     mapping, split_type);
>  
>  	/*
> @@ -4145,9 +4203,19 @@ static int __folio_freeze_split_unmap_file(struct folio *folio, unsigned int new
>  		lruvec_unlock(lruvec);
>  
>  fail:
> -	xas_unlock_irq(xas);
> +	xas_unlock_irq(&xas);
> +fail_mmap_unlock:
>  	if (nr_shmem_dropped)
>  		shmem_uncharge(mapping->host, nr_shmem_dropped);
> +	/*
> +	 * Drop the mapping while the inode is still pinned. @folio stays
> +	 * locked and present in the page cache, so eviction cannot free
> +	 * the inode yet, nothing past this point may touch the inode or
> +	 * the mapping.
> +	 */
> +	i_mmap_unlock_read(mapping);
> +fail_free:
> +	xas_destroy(&xas);
>  	return ret;
>  }
>  
> @@ -4176,12 +4244,9 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>  		struct page *split_at, struct page *lock_at,
>  		struct list_head *list, enum split_type split_type)
>  {
> -	XA_STATE(xas, &folio->mapping->i_pages, folio->index);
>  	struct folio *end_folio = folio_next(folio);
>  	bool is_anon = folio_test_anon(folio);
>  	struct mem_cgroup *memcg, *old_memcg;
> -	struct address_space *mapping = NULL;
> -	struct anon_vma *anon_vma = NULL;
>  	int old_order = folio_order(folio);
>  	struct folio *new_folio, *next;
>  	int ret;
> @@ -4212,84 +4277,12 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>  	memcg = get_mem_cgroup_from_folio(folio);
>  	old_memcg = set_active_memcg(memcg);
>  
> -	if (is_anon) {
> -		/*
> -		 * The caller does not necessarily hold an mmap_lock that would
> -		 * prevent the anon_vma disappearing so we first we take a
> -		 * reference to it and then lock the anon_vma for write. This
> -		 * is similar to folio_lock_anon_vma_read except the write lock
> -		 * is taken to serialise against parallel split or collapse
> -		 * operations.
> -		 */
> -		anon_vma = folio_get_anon_vma(folio);
> -		if (!anon_vma) {
> -			ret = -EBUSY;
> -			goto out;
> -		}
> -		anon_vma_lock_write(anon_vma);
> -		mapping = NULL;
> -	} else {
> -		unsigned int min_order;
> -		gfp_t gfp;
> -
> -		mapping = folio->mapping;
> -		min_order = mapping_min_folio_order(mapping);
> -		if (new_order < min_order) {
> -			ret = -EINVAL;
> -			goto out;
> -		}
> -
> -		gfp = current_gfp_context(mapping_gfp_mask(mapping) &
> -							GFP_RECLAIM_MASK);
> -
> -		if (!filemap_release_folio(folio, gfp)) {
> -			ret = -EBUSY;
> -			goto out;
> -		}
> -
> -		mapping_set_update(&xas, mapping);
> -
> -		if (split_type == SPLIT_TYPE_UNIFORM) {
> -			xas_set_order(&xas, folio->index, new_order);
> -			xas_split_alloc(&xas, folio, old_order, gfp);
> -			if (xas_error(&xas)) {
> -				ret = xas_error(&xas);
> -				goto out;
> -			}
> -		}
> -
> -		anon_vma = NULL;
> -		i_mmap_lock_read(mapping);
> -	}
> -
> -	/*
> -	 * Racy check if we can split the page, before unmap_folio() will
> -	 * split PMDs
> -	 */
> -	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
> -		ret = -EAGAIN;
> -		goto out_unlock;
> -	}
> -
> -	if (!is_anon) {
> -		ret = __folio_freeze_split_unmap_file(folio, new_order, split_at, &xas, mapping,
> -							 true, list, split_type);
> -	} else {
> +	if (is_anon)
>  		ret = __folio_freeze_split_unmap_anon(folio, new_order, split_at, true,
>  						      true, list, split_type);
> -	}
> -
> -	/*
> -	 * Drop the mapping while the inode is still pinned. @folio stays
> -	 * locked and present in the page cache until the loop below, so
> -	 * eviction cannot free the inode yet; @lock_at is not enough, it may
> -	 * be a tail beyond EOF that the split already dropped from the page
> -	 * cache. Nothing past this point may touch the inode or the mapping.
> -	 */
> -	if (mapping) {
> -		i_mmap_unlock_read(mapping);
> -		mapping = NULL;
> -	}
> +	else
> +		ret = __folio_freeze_split_unmap_file(folio, new_order, split_at,
> +						      true, list, split_type);
>  
>  	/*
>  	 * Unlock all after-split folios except the one containing
> @@ -4310,19 +4303,10 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>  		free_folio_and_swap_cache(new_folio);
>  	}
>  
> -out_unlock:
> -	if (anon_vma) {
> -		anon_vma_unlock_write(anon_vma);
> -		put_anon_vma(anon_vma);
> -	}
> -	if (mapping)
> -		i_mmap_unlock_read(mapping);
> -out:
>  	/* restore to caller's old_memcg */
>  	set_active_memcg(old_memcg);
>  	mem_cgroup_put(memcg);
>  out_no_memcg:
> -	xas_destroy(&xas);
>  	if (is_pmd_order(old_order))
>  		count_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);
>  	count_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);
> @@ -4358,9 +4342,6 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
>  	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
>  	VM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);
>  
> -	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
> -		return -EAGAIN;
> -
>  	return __folio_freeze_split_unmap_anon(folio, new_order, &folio->page, false,
>  					       false, NULL, SPLIT_TYPE_UNIFORM);
>  }

__folio_split() looks much cleaner. Thanks.

Reviewed-by: Zi Yan <ziy@nvidia.com>



-- 
Best Regards,
Yan, Zi


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 09/13] mm/huge_memory: move memcg switch into the file split helper
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-09  2:29   ` Zi Yan
  -1 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-09  2:29 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> The xarray node allocations in __folio_freeze_split_unmap_file() need
> to be charged to the folio's memcg, so move the memcg switch from
> __folio_split() into the helper.
>
> The anon split helper and the after-split folio freeing perform no
> chargeable allocations, so no memcg handling is left in __folio_split().
> Rename its out_no_memcg label to out.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 36 +++++++++++++++++++-----------------
>  1 file changed, 19 insertions(+), 17 deletions(-)
>

Yeah, it should not change anon accounting.

Acked-by: Zi Yan <ziy@nvidia.com>


-- 
Best Regards,
Yan, Zi



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 10/13] mm/huge_memory: allow splitting mappingless swap cache folios
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-09  2:35   ` Zi Yan
  -1 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-09  2:35 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> Lift the restriction that kept swap cache folios without a mapping
> from being split.  All the underlying infrastructure is sound against
> that with a few more tweaks, no reason to block it anymore.
>
> Also rename the split helper, which now handles mappingless swap
> cache folios that are yet to be anon, or may actually belong to
> shmem.  In either case there is not much difference in how they would
> be split.
>
> A non-anon swap cache folio that still has a mapping (e.g. a shmem
> swap cache folio) remains rejected up front: it would need both its
> page cache and swap cache entries updated on split, which the split
> helpers do not do, and there would be little benefit in doing so.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 40 +++++++++++++++++++++++-----------------
>  1 file changed, 23 insertions(+), 17 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index ab2bb29748d3..b80d0db63225 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -3881,12 +3881,10 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
>  	VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
>  	/*
>  	 * Folios that just got truncated cannot get split. Signal to the
> -	 * caller that there was a race.
> -	 *
> -	 * TODO: this will also currently refuse folios without a mapping in the
> -	 * swapcache (shmem or to-be-anon folios).
> +	 * caller that there was a race. A mappingless swap cache folio
> +	 * has no page cache entries to update, so it is fine to split.
>  	 */
> -	if (!folio->mapping && !is_anon)
> +	if (!folio->mapping && !is_swapcache)
>  		return -EBUSY;
>  
>  	/* order-1 is not supported for anonymous THP. */
> @@ -3931,11 +3929,12 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
>  	return folio_nr_pages(folio);
>  }
>  
> -static int __folio_freeze_split_unmap_anon(struct folio *folio, unsigned int new_order,
> -					   struct page *split_at, bool do_lru, bool unmap,
> -					   struct list_head *list, enum split_type split_type)
> +static int __folio_freeze_split_unmap(struct folio *folio, unsigned int new_order,
> +				      struct page *split_at, bool do_lru, bool anon_unmap,
> +				      struct list_head *list, enum split_type split_type)

I have not figured out a good name for it, but it definitely needs a
function kdoc to explain what it does, since it is no longer a simple
anon only function. It is better to add the documentation for this one
and file one when they are introduced and modify anon one's comment
here.



-- 
Best Regards,
Yan, Zi


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 11/13] mm/huge_memory: clean up after-split folio freeing in __folio_split
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-09  2:41   ` Zi Yan
  -1 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-09  2:41 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> Replace free_folio_and_swap_cache() with an explicit folio_free_swap()
> and folio_put() in the after-split loop.  free_folio_and_swap_cache()
> unlocks the folio, then free_swap_cache() must trylock it again and
> re-check folio_mapped() before freeing the swap cache entries; if the
> trylock loses a race, the entries are left behind even though the folio
> reference is dropped.  The sub folios are still locked and unmapped
> here, so just directly call folio_free_swap() directly under the lock,
> unlock and drop the reference.  This makes the swap cache freeing
> deterministic and the reference drop explicit.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 6 ++++--
>  1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index b80d0db63225..39c91c8e5bc8 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -4304,14 +4304,16 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>  		if (new_folio == page_folio(lock_at))
>  			continue;
>  
> -		folio_unlock(new_folio);
>  		/*
>  		 * Subpages whose mapping has been zapped may be freed
>  		 * earlier, but freeing them requires taking the
>  		 * lru_lock, so we defer put_page() on tail pages until
>  		 * after the split completes.

this comment needs some love.

>  		 */
> -		free_folio_and_swap_cache(new_folio);
> +		if (is_swapcache)
> +			folio_free_swap(new_folio);
> +		folio_unlock(new_folio);
> +		folio_put(new_folio);
>  	}
>  
>  out:

folio_split_unmaped()'s comment still refers to
free_folio_and_swap_cache().

Otherwise, LGTM.

Reviewed-by: Zi Yan <ziy@nvidia.com>



-- 
Best Regards,
Yan, Zi



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 12/13] mm/huge_memory: lift order-0 restriction for swapcache split
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-09  2:46   ` Zi Yan
  -1 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-09  2:46 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> The restriction that swapcache folios can only be uniformly split to
> order 0 dates back to when the swap cache was managed via address_space
> mapping (swap_address_space).  The old split loop only created order-0
> sub-folios with a fixed stride, so non-uniform split and non-zero order
> were rightfully blocked.
>
> After the swap cache switched to swap table under a cluster lock,
> __swap_cache_replace_folio already gained the ability to replace any
> number of entries for any sub-folio size in one cluster, and the old
> swap_address_space locking and limit was removed. The restriction
> became obsolete but persisted through multiple refactorings.
>
> Drop it now: swapcache folios can be split to any supported order with
> either uniform or non-uniform split, except order-1 which is not
> supported for anon folios. Mappingless swap cache folios could be either
> anon or shmem, so for now we just simply forbid order-1 for all swapcache.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 32 +++++++++++++-------------------
>  1 file changed, 13 insertions(+), 19 deletions(-)
>

LGTM. BTW, __split_huge_page_to_list_to_order()'s comment also needs an
update about splitting to order-1 folios.

Acked-by: Zi Yan <ziy@nvidia.com>


-- 
Best Regards,
Yan, Zi


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 13/13] mm/huge_memory: count only swap cache refs in anon folio split
  2026-08-07 21:17   ` Kairui Song
  (?)
@ 2026-08-09  2:49   ` Zi Yan
  -1 siblings, 0 replies; 45+ messages in thread
From: Zi Yan @ 2026-08-09  2:49 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Youngjun Park

On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> Only __folio_freeze_split_unmap() sees anon folios and swap cache folios
> now. The file split helper only handles page cache folios, which hold
> exactly folio_nr_pages() references.
>
> Rename folio_cache_ref_count() to folio_swapcache_ref_count() and drop
> the anon check so the helper counts what its name says. The file split
> helper now uses folio_nr_pages() directly.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 35 +++++++++++++++--------------------
>  1 file changed, 15 insertions(+), 20 deletions(-)
>

LGTM. Thanks.

Reviewed-by: Zi Yan <ziy@nvidia.com>


-- 
Best Regards,
Yan, Zi



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH RFC 05/13] mm/huge_memory: consolidate irq and locking for folio split
  2026-08-09  1:54   ` Zi Yan
@ 2026-08-10  3:35     ` Kairui Song
  0 siblings, 0 replies; 45+ messages in thread
From: Kairui Song @ 2026-08-10  3:35 UTC (permalink / raw)
  To: Zi Yan
  Cc: linux-mm, linux-kernel, Andrew Morton, David Hildenbrand,
	Lorenzo Stoakes, Baolin Wang, Liam R. Howlett, Nico Pache,
	Ryan Roberts, Dev Jain, Lance Yang, Usama Arif, Vlastimil Babka,
	Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Chris Li,
	Kemeng Shi, Nhat Pham, Baoquan He, Barry Song, Youngjun Park

On Sun, Aug 9, 2026 at 9:54 AM Zi Yan <ziy@nvidia.com> wrote:
>
> On Fri Aug 7, 2026 at 5:17 PM EDT, Kairui Song via B4 Relay wrote:
> > From: Kairui Song <kasong@tencent.com>
> >
> > Let each split helper handle its own locking instead of relying on
> > the caller, so both paths follow the same convention and __folio_split()
> > can drop its local irq handling and fail label, preparing for further
> > cleanup.
> >
> > Signed-off-by: Kairui Song <kasong@tencent.com>
> > ---
> >  mm/huge_memory.c | 52 ++++++++++++++++++++++++----------------------------
> >  1 file changed, 24 insertions(+), 28 deletions(-)
> >
>
> <snip>
>
> anon part is not changed.

Right, I can mention this in commit message.

>
> > @@ -4035,8 +4039,21 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
> >       struct lruvec *lruvec;
> >       int ret;
> >
> > -     if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1))
> > -             return -EAGAIN;
> > +     xas_lock_irq(xas);
>
> local_irq_disable() + xas_lock() is replaced by xas_lock_irq(). Are they
> equivalent? Codex told me that the latter no longer prevents preemption
> on RT kernel and can lengthen folio split process if it is preempted. It
> is worth spell out in the commit message after you verify it.

It's a typical RT tradeoff, but I think it's actually an improvement?
The old behavior was an actual RT anti-pattern, RT cares more about
latency. I also traced other freeze & filemap update user, most are
using xas_lock_irq, and that seems better. I'll mention this in commit
message.

> > +
> > +     /*
> > +      * Check if the folio is present in page cache.
> > +      * We assume all tail are present too, if folio is there.
> > +      */
>
> xas_reset() is gone here. It seems to be a no-op, since xas is not
> walked yet. But it is better to mention it in the commit message.

Will do.

Thanks for the review!


^ permalink raw reply	[flat|nested] 45+ messages in thread

end of thread, other threads:[~2026-08-10  3:36 UTC | newest]

Thread overview: 45+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-07 21:17 [PATCH RFC 00/13] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
2026-08-07 21:17 ` Kairui Song
2026-08-07 21:17 ` [PATCH RFC 01/13] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-08 17:07   ` Zi Yan
2026-08-07 21:17 ` [PATCH RFC 02/13] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-08 18:01   ` Zi Yan
2026-08-08 18:14     ` Kairui Song
2026-08-08 18:53       ` Zi Yan
2026-08-07 21:17 ` [PATCH RFC 03/13] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-08 18:04   ` Zi Yan
2026-08-07 21:17 ` [PATCH RFC 04/13] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-08 18:52   ` Zi Yan
2026-08-08 20:19     ` Kairui Song
2026-08-07 21:17 ` [PATCH RFC 05/13] mm/huge_memory: consolidate irq and locking for folio split Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-09  1:54   ` Zi Yan
2026-08-10  3:35     ` Kairui Song
2026-08-07 21:17 ` [PATCH RFC 06/13] mm/huge_memory: move EOF trimming into the file split helper Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-09  1:59   ` Zi Yan
2026-08-07 21:17 ` [PATCH RFC 07/13] mm/huge_memory: move unmap and remap into the split helpers Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-09  2:13   ` Zi Yan
2026-08-07 21:17 ` [PATCH RFC 08/13] mm/huge_memory: move anon_vma and filemap management into " Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-09  2:22   ` Zi Yan
2026-08-07 21:17 ` [PATCH RFC 09/13] mm/huge_memory: move memcg switch into the file split helper Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-09  2:29   ` Zi Yan
2026-08-07 21:17 ` [PATCH RFC 10/13] mm/huge_memory: allow splitting mappingless swap cache folios Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-09  2:35   ` Zi Yan
2026-08-07 21:17 ` [PATCH RFC 11/13] mm/huge_memory: clean up after-split folio freeing in __folio_split Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-09  2:41   ` Zi Yan
2026-08-07 21:17 ` [PATCH RFC 12/13] mm/huge_memory: lift order-0 restriction for swapcache split Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-09  2:46   ` Zi Yan
2026-08-07 21:17 ` [PATCH RFC 13/13] mm/huge_memory: count only swap cache refs in anon folio split Kairui Song via B4 Relay
2026-08-07 21:17   ` Kairui Song
2026-08-09  2:49   ` Zi Yan

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.