Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits
@ 2026-08-20 18:55 Kairui Song via B4 Relay
  2026-08-20 18:55 ` [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
                   ` (18 more replies)
  0 siblings, 19 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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;
135 split calls per run, 50 test run):

Before: 67.9 us, stddev: 1.59
After:  66.4 us, stddev: 1.19

The patched kernel is slightly faster. The stack usage is also reduced
by about ~10%, with a very slight growth of huge_memory.o.

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

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

---
Kairui Song (18):
      mm/swap: fix off-by-one in swap cache replace sanity check
      mm/huge_memory: fix rejection of swap cache folios with a mapping
      mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
      mm/huge_memory: split the routine for splitting anon and file folio
      mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio()
      mm/huge_memory: consolidate irq and locking for folio split
      mm/huge_memory: move EOF trimming into the file split helper
      mm/huge_memory: move unmap and remap into the split helpers
      mm/huge_memory: 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: add kerneldoc for the split helpers
      mm/huge_memory: drop the unused do_lru argument of the file split helper
      mm/huge_memory: clean up after-split folio freeing in __folio_split
      mm/huge_memory: lift order-0 restriction for swapcache split
      mm/huge_memory: clarify supported split orders in comment
      mm/huge_memory: count only swap cache refs in anon folio split
      mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio

 mm/huge_memory.c | 633 +++++++++++++++++++++++++++++--------------------------
 mm/swap_state.c  |   3 +-
 2 files changed, 338 insertions(+), 298 deletions(-)
---
base-commit: 4b2ae13f3393ef4b4bce0021e8762790354f369f
change-id: 20260804-swap-thp-cleanup-6ce2be6cf3b8

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




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

* [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-23  8:53   ` Barry Song
                     ` (2 more replies)
  2026-08-20 18:55 ` [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
                   ` (17 subsequent siblings)
  18 siblings, 3 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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")
Acked-by: Zi Yan <ziy@nvidia.com>
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 b76eb3d876fd..59a577f685b5 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -389,8 +389,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] 74+ messages in thread

* [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
  2026-08-20 18:55 ` [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-27  8:46   ` Barry Song
                     ` (3 more replies)
  2026-08-20 18:55 ` [PATCH v3 03/18] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation Kairui Song via B4 Relay
                   ` (16 subsequent siblings)
  18 siblings, 4 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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 does a defensive check 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. It
rejects the split and returns -EINVAL without unfreezing the folio or
restoring the counters. That error path is buggy: if it is ever taken,
it leaves the folio frozen and stuck, skews the counters, and fires
the VM_WARN_ON_ONCE_FOLIO for a state that is actually legitimate.

Check for this case up front in folio_check_splittable and return
-EBUSY before any state is modified, so the split routine always backs
out cleanly.

Also fix a bracket style issue that checkpatch.pl keeps complaining
about.

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")
Reviewed-by: Zi Yan <ziy@nvidia.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 27 ++++++++++++++++-----------
 1 file changed, 16 insertions(+), 11 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ced400f72d43..a6759a14e057 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,9 +3904,8 @@ 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;
-	}
 
 	if (is_huge_zero_folio(folio))
 		return -EINVAL;
@@ -3911,6 +3913,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 can only be a
+	 * shmem folio under SWAP IO, it's removed from either swap cache or
+	 * shmem mapping afterward. There is little benefit in splitting them
+	 * hence reject it here up front before touching anything.
+	 */
+	if (!is_anon && is_swapcache && folio->mapping)
+		return -EBUSY;
+
 	return 0;
 }
 
@@ -3983,14 +3994,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] 74+ messages in thread

* [PATCH v3 03/18] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
  2026-08-20 18:55 ` [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
  2026-08-20 18:55 ` [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-27  9:05   ` Barry Song
                     ` (2 more replies)
  2026-08-20 18:55 ` [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
                   ` (15 subsequent siblings)
  18 siblings, 3 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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.

Reviewed-by: Zi Yan <ziy@nvidia.com>
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 a6759a14e057..7fb603ac500f 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3940,9 +3940,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;
 
@@ -3963,122 +3965,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] 74+ messages in thread

* [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (2 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 03/18] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-26  1:31   ` Zi Yan
                     ` (2 more replies)
  2026-08-20 18:55 ` [PATCH v3 05/18] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio() Kairui Song via B4 Relay
                   ` (14 subsequent siblings)
  18 siblings, 3 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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 drop a
few now-redundant checks. Also introduce a folio iteration helper to
avoid a common pitfall of iterating post-split sub-folios: a sub folio
might get freed mid-iteration as pointed out by Zi [1].

Link: https://lore.kernel.org/linux-mm/DKJSFCLP967N.YBR4DNK1NM2N@nvidia.com/ [1]
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 119 +++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 75 insertions(+), 44 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 7fb603ac500f..c3fd6757c14c 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3933,11 +3933,9 @@ 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;
@@ -3948,7 +3946,6 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 	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
@@ -3956,7 +3953,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;
 
@@ -3986,24 +3983,72 @@ 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 post-split folios and put them back to the right
+	 * place. Keep the head @folio frozen until the end: sub entries
+	 * in swap cache must be updated first, so a concurrent
+	 * swap_cache_get_folio() cannot return the head folio for a sub
+	 * entry (folio_try_get() will fail on the head @folio until unfreeze).
+	 */
+	for (new_folio = folio_next(folio); new_folio != end_folio;
+	     new_folio = next) {
+		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);
+		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);
@@ -4013,7 +4058,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
@@ -4023,29 +4068,15 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 	     new_folio = next) {
 		unsigned long nr_pages = folio_nr_pages(new_folio);
 
+		/* compute next before the folio can be freed below */
 		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,
@@ -4064,7 +4095,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.
@@ -4075,8 +4105,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;
 }
@@ -4230,10 +4258,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);
@@ -4333,9 +4365,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] 74+ messages in thread

* [PATCH v3 05/18] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio()
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (3 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-27 15:06   ` Kiryl Shutsemau
  2026-08-20 18:55 ` [PATCH v3 06/18] mm/huge_memory: consolidate irq and locking for folio split Kairui Song via B4 Relay
                   ` (13 subsequent siblings)
  18 siblings, 1 reply; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	Kairui Song

From: Kairui Song <kasong@tencent.com>

The helper splits a folio whose refcount is frozen: the frozen refcount
is the state it relies on, while unmapping is arranged by the caller
beforehand. The old name caused confusion and people may try to call the
helper on non-frozen folios.

Suggested-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index c3fd6757c14c..427e14d7985a 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3755,8 +3755,8 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
 }
 
 /**
- * __split_unmapped_folio() - splits an unmapped @folio to lower order folios in
- * two ways: uniform split or non-uniform split.
+ * __split_frozen_folio() - splits a frozen @folio to lower order folios
+ * in two ways: uniform split or non-uniform split.
  * @folio: the to-be-split folio
  * @new_order: the smallest order of the after split folios (since buddy
  *             allocator like split generates folios with orders from @folio's
@@ -3795,7 +3795,7 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
  * Return: 0 - successful, <0 - failed (if -ENOMEM is returned, @folio might be
  * split but not to @new_order, the caller needs to check)
  */
-static int __split_unmapped_folio(struct folio *folio, int new_order,
+static int __split_frozen_folio(struct folio *folio, int new_order,
 		struct page *split_at, struct xa_state *xas,
 		struct address_space *mapping, enum split_type split_type)
 {
@@ -3989,8 +3989,8 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
 	if (do_lru)
 		lruvec = folio_lruvec_lock(folio);
 
-	ret = __split_unmapped_folio(folio, new_order, split_at, NULL,
-				     NULL, split_type);
+	ret = __split_frozen_folio(folio, new_order, split_at, NULL,
+				   NULL, split_type);
 
 	/*
 	 * Unfreeze the post-split folios and put them back to the right
@@ -4053,8 +4053,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 	if (do_lru)
 		lruvec = folio_lruvec_lock(folio);
 
-	ret = __split_unmapped_folio(folio, new_order, split_at, xas,
-				     mapping, split_type);
+	ret = __split_frozen_folio(folio, new_order, split_at, xas,
+				   mapping, split_type);
 
 	/*
 	 * Unfreeze after-split folios and put them back to the right
@@ -4118,9 +4118,9 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
  * @list: after-split folios will be put on it if non NULL
  * @split_type: perform uniform split or not (non-uniform split)
  *
- * It calls __split_unmapped_folio() to perform uniform and non-uniform split.
+ * It calls __split_frozen_folio() to perform uniform and non-uniform split.
  * It is in charge of checking whether the split is supported or not and
- * preparing @folio for __split_unmapped_folio().
+ * preparing @folio for __split_frozen_folio().
  *
  * After splitting, the after-split folio containing @lock_at remains locked
  * and others are unlocked:
@@ -4223,7 +4223,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		i_mmap_lock_read(mapping);
 
 		/*
-		 *__split_unmapped_folio() may need to trim off pages beyond
+		 * __split_frozen_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,

-- 
2.55.0




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

* [PATCH v3 06/18] mm/huge_memory: consolidate irq and locking for folio split
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (4 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 05/18] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio() Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-27 15:15   ` Kiryl Shutsemau
  2026-08-27 16:24   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 07/18] mm/huge_memory: move EOF trimming into the file split helper Kairui Song via B4 Relay
                   ` (12 subsequent siblings)
  18 siblings, 2 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	Kairui Song

From: Kairui Song <kasong@tencent.com>

Let each split helper handle its own locking instead of relying on
the caller, so both helpers manage their own irq and locking state.
This lets __folio_split() drop its local irq handling and fail label,
preparing for further cleanup.

The file path now uses xas_lock_irq() instead of local_irq_disable()
with xas_lock(). The two are equivalent on non-RT, and
TRANSPARENT_HUGEPAGE cannot be enabled on RT anyway. This conversion
also buys consistency: every other place in mm/ that freezes a folio
while it is still reachable through the page cache already takes the
lock this way. This was actually the last plain xas_lock() on
mapping->i_pages left in mm. If we are going to support RT, spinning
on frozen folio refs could be a problem, but it already exists in
many places and should be fixed generically.

The anon helper keeps a single local_irq_disable() as before, because
it has to cover several plain spinlocks at once.

The dropped xas_reset() was a no-op as the xa_state is not walked
before the xas_load() under the lock.

Reviewed-by: Zi Yan <ziy@nvidia.com>
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 427e14d7985a..69d3a6889f9e 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3946,6 +3946,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
@@ -3968,6 +3970,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;
 	}
 
@@ -4018,6 +4021,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;
 }
@@ -4033,8 +4037,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) {
@@ -4106,6 +4123,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;
 }
 
@@ -4245,19 +4264,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);
@@ -4266,12 +4273,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);
 
@@ -4354,8 +4355,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);
@@ -4364,11 +4363,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] 74+ messages in thread

* [PATCH v3 07/18] mm/huge_memory: move EOF trimming into the file split helper
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (5 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 06/18] mm/huge_memory: consolidate irq and locking for folio split Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-27 16:25   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 08/18] mm/huge_memory: move unmap and remap into the split helpers Kairui Song via B4 Relay
                   ` (11 subsequent siblings)
  18 siblings, 1 reply; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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.

Reviewed-by: Zi Yan <ziy@nvidia.com>
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 69d3a6889f9e..01c8cf428595 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4029,14 +4029,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_frozen_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);
 
 	/*
@@ -4101,10 +4113,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));
@@ -4125,6 +4136,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;
 }
 
@@ -4161,9 +4174,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);
@@ -4240,17 +4251,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 		anon_vma = NULL;
 		i_mmap_lock_read(mapping);
-
-		/*
-		 * __split_frozen_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);
 	}
 
 	/*
@@ -4266,16 +4266,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] 74+ messages in thread

* [PATCH v3 08/18] mm/huge_memory: move unmap and remap into the split helpers
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (6 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 07/18] mm/huge_memory: move EOF trimming into the file split helper Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-27 16:32   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 09/18] mm/huge_memory: move anon_vma and filemap management into " Kairui Song via B4 Relay
                   ` (10 subsequent siblings)
  18 siblings, 1 reply; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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.

Reviewed-by: Zi Yan <ziy@nvidia.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 58 ++++++++++++++++++++++++++++++--------------------------
 1 file changed, 31 insertions(+), 27 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 01c8cf428595..af9c2edd1fba 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);
@@ -3933,19 +3930,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_split_unmap_and_freeze_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, *next;
 	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();
 
 	/*
@@ -3970,8 +3971,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) {
@@ -4021,15 +4022,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_split_unmap_and_freeze_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;
@@ -4049,6 +4056,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);
 
 	/*
@@ -4133,8 +4142,11 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
 
 	if (do_lru)
 		lruvec_unlock(lruvec);
-
 fail:
+	/*
+	 * If we want to use try_to_migrate() on file in unmap_folio,
+	 * remember to add remap_page() and adapt it.
+	 */
 	xas_unlock_irq(xas);
 	if (nr_shmem_dropped)
 		shmem_uncharge(mapping->host, nr_shmem_dropped);
@@ -4174,7 +4186,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);
@@ -4262,21 +4273,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,
-							 true, list, split_type);
+		ret = __folio_split_unmap_and_freeze_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_split_unmap_and_freeze_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
@@ -4359,8 +4363,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_split_unmap_and_freeze_anon(folio, new_order, &folio->page, false,
+						   false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*

-- 
2.55.0




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

* [PATCH v3 09/18] mm/huge_memory: move anon_vma and filemap management into split helpers
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (7 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 08/18] mm/huge_memory: move unmap and remap into the split helpers Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-27 16:36   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 10/18] mm/huge_memory: move memcg switch into the file split helper Kairui Song via B4 Relay
                   ` (9 subsequent siblings)
  18 siblings, 1 reply; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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.

Reviewed-by: Zi Yan <ziy@nvidia.com>
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 af9c2edd1fba..1ee312cbd438 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3938,12 +3938,33 @@ static int __folio_split_unmap_and_freeze_anon(struct folio *folio, unsigned int
 	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio, *next;
 	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);
 
@@ -4029,21 +4050,58 @@ static int __folio_split_unmap_and_freeze_anon(struct folio *folio, unsigned int
 			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_split_unmap_and_freeze_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_frozen_folio() may need to trim off pages beyond
@@ -4058,13 +4116,13 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 
 	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;
 	}
@@ -4091,7 +4149,7 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 	if (do_lru)
 		lruvec = folio_lruvec_lock(folio);
 
-	ret = __split_frozen_folio(folio, new_order, split_at, xas,
+	ret = __split_frozen_folio(folio, new_order, split_at, &xas,
 				   mapping, split_type);
 
 	/*
@@ -4147,9 +4205,19 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 	 * If we want to use try_to_migrate() on file in unmap_folio,
 	 * remember to add remap_page() and adapt it.
 	 */
-	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;
 }
 
@@ -4178,12 +4246,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;
@@ -4214,84 +4279,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_split_unmap_and_freeze_file(folio, new_order, split_at, &xas, mapping,
-							  true, list, split_type);
-	} else {
+	if (is_anon)
 		ret = __folio_split_unmap_and_freeze_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_split_unmap_and_freeze_file(folio, new_order, split_at,
+							  true, list, split_type);
 
 	/*
 	 * Unlock all after-split folios except the one containing
@@ -4312,19 +4305,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);
@@ -4360,9 +4344,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_split_unmap_and_freeze_anon(folio, new_order, &folio->page, false,
 						   false, NULL, SPLIT_TYPE_UNIFORM);
 }

-- 
2.55.0




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

* [PATCH v3 10/18] mm/huge_memory: move memcg switch into the file split helper
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (8 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 09/18] mm/huge_memory: move anon_vma and filemap management into " Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-27 16:37   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios Kairui Song via B4 Relay
                   ` (8 subsequent siblings)
  18 siblings, 1 reply; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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.

Acked-by: Zi Yan <ziy@nvidia.com>
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 1ee312cbd438..3029cb1f07f3 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4066,6 +4066,7 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 	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;
@@ -4078,9 +4079,18 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 	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);
 
@@ -4217,6 +4227,9 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 	 */
 	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;
 }
@@ -4248,7 +4261,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;
@@ -4258,27 +4270,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_split_unmap_and_freeze_anon(folio, new_order, split_at, true,
 							  true, list, split_type);
@@ -4305,10 +4310,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] 74+ messages in thread

* [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (9 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 10/18] mm/huge_memory: move memcg switch into the file split helper Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-26  1:55   ` Zi Yan
  2026-08-27 16:41   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 12/18] mm/huge_memory: add kerneldoc for the split helpers Kairui Song via B4 Relay
                   ` (7 subsequent siblings)
  18 siblings, 2 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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 | 38 ++++++++++++++++++++++----------------
 1 file changed, 22 insertions(+), 16 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 3029cb1f07f3..0186238e0d1e 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. */
@@ -3930,11 +3928,12 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
 	return folio_nr_pages(folio);
 }
 
-static int __folio_split_unmap_and_freeze_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_split_unmap_and_freeze(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, *next;
 	int old_order = folio_order(folio);
@@ -3952,7 +3951,7 @@ static int __folio_split_unmap_and_freeze_anon(struct folio *folio, unsigned int
 	 * 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;
@@ -3965,7 +3964,7 @@ static int __folio_split_unmap_and_freeze_anon(struct folio *folio, unsigned int
 		goto out_unlock;
 	}
 
-	if (unmap)
+	if (anon_unmap)
 		unmap_folio(folio);
 
 	local_irq_disable();
@@ -3976,8 +3975,11 @@ static int __folio_split_unmap_and_freeze_anon(struct folio *folio, unsigned int
 	 * 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;
 
@@ -4045,7 +4047,7 @@ static int __folio_split_unmap_and_freeze_anon(struct folio *folio, unsigned int
 		swap_cluster_unlock(ci);
 out_no_split:
 	local_irq_enable();
-	if (unmap) {
+	if (anon_vma) {
 		if (!ret && !folio_is_device_private(folio))
 			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
 		remap_page(folio, 1 << old_order, ttu_flags);
@@ -4259,6 +4261,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);
@@ -4285,8 +4288,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 	}
 
 	if (is_anon)
-		ret = __folio_split_unmap_and_freeze_anon(folio, new_order, split_at, true,
-							  true, list, split_type);
+		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
+						     true, list, split_type);
+	else if (is_swapcache)
+		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
+						     false, list, split_type);
 	else
 		ret = __folio_split_unmap_and_freeze_file(folio, new_order, split_at,
 							  true, list, split_type);
@@ -4346,8 +4352,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_split_unmap_and_freeze_anon(folio, new_order, &folio->page, false,
-						   false, NULL, SPLIT_TYPE_UNIFORM);
+	return __folio_split_unmap_and_freeze(folio, new_order, &folio->page, false,
+					      false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*

-- 
2.55.0




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

* [PATCH v3 12/18] mm/huge_memory: add kerneldoc for the split helpers
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (10 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-26  1:57   ` Zi Yan
  2026-08-27 16:45   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 13/18] mm/huge_memory: drop the unused do_lru argument of the file split helper Kairui Song via B4 Relay
                   ` (6 subsequent siblings)
  18 siblings, 2 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	Kairui Song

From: Kairui Song <kasong@tencent.com>

Document __folio_split_unmap_and_freeze() and
__folio_split_unmap_and_freeze_file(), and rename the file split
helper's definition to match its call site.

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

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 0186238e0d1e..72b364a90062 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3928,6 +3928,25 @@ static unsigned int folio_cache_ref_count(const struct folio *folio)
 	return folio_nr_pages(folio);
 }
 
+/**
+ * __folio_split_unmap_and_freeze() - split an anon or swap cache folio
+ * @folio: folio to split, must be locked
+ * @new_order: the order of the after-split folios (uniform split), or the
+ *             smallest order of the after-split folios (non-uniform split)
+ * @split_at: in non-uniform split, the folio containing @split_at is split
+ *            until its order becomes @new_order
+ * @do_lru: if true, add after-split folios to @list if non NULL, otherwise to
+ *          the LRU list
+ * @anon_unmap: if true, unmap @folio before the split and remap it after
+ * @list: after-split folios will be put on it if non NULL
+ * @split_type: perform uniform split or not (non-uniform split)
+ *
+ * Helper for splitting an anon or swap cache folio. It unmaps @folio (unless
+ * @anon_unmap is false), freezes its refcount, and performs the split, updates
+ * the swap cache entries. Split folios are unfrozen and remapped.
+ *
+ * Return: 0 on success, otherwise an error number is returned.
+ */
 static int __folio_split_unmap_and_freeze(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)
@@ -4061,6 +4080,25 @@ static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_
 	return ret;
 }
 
+/**
+ * __folio_split_unmap_and_freeze_file() - split a file-backed folio
+ * @folio: folio to split, must be locked and file-backed
+ * @new_order: the order of the after-split folios (uniform split), or the
+ *             smallest order of the after-split folios (non-uniform split)
+ * @split_at: in non-uniform split, the folio containing @split_at is split
+ *            until its order becomes @new_order
+ * @do_lru: if true, add after-split folios to @list if non NULL, otherwise to
+ *          the LRU list
+ * @list: after-split folios will be put on it if non NULL
+ * @split_type: perform uniform split or not (non-uniform split)
+ *
+ * Helper for splitting a file-backed folio. It unmaps @folio, freezes its
+ * refcount, and perform the split, updates the page cache entries. Split
+ * folios are unfrozen but not remapped, they are faulted back in on demand.
+ *
+ * Return: 0 on success, otherwise an error number is returned. (if -ENOMEM
+ * is returned, @folio might be split but not to @new_order)
+ */
 static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int new_order,
 					       struct page *split_at, bool do_lru,
 					       struct list_head *list, enum split_type split_type)

-- 
2.55.0




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

* [PATCH v3 13/18] mm/huge_memory: drop the unused do_lru argument of the file split helper
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (11 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 12/18] mm/huge_memory: add kerneldoc for the split helpers Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-26  1:57   ` Zi Yan
  2026-08-27 16:45   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 14/18] mm/huge_memory: clean up after-split folio freeing in __folio_split Kairui Song via B4 Relay
                   ` (5 subsequent siblings)
  18 siblings, 2 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	Kairui Song

From: Kairui Song <kasong@tencent.com>

The only caller of __folio_split_unmap_and_freeze_file() always passes
do_lru as true, so the argument and the branches gated on it are dead
code.  Drop it.

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

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 72b364a90062..84c6e4bbaa88 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4087,8 +4087,6 @@ static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_
  *             smallest order of the after-split folios (non-uniform split)
  * @split_at: in non-uniform split, the folio containing @split_at is split
  *            until its order becomes @new_order
- * @do_lru: if true, add after-split folios to @list if non NULL, otherwise to
- *          the LRU list
  * @list: after-split folios will be put on it if non NULL
  * @split_type: perform uniform split or not (non-uniform split)
  *
@@ -4100,8 +4098,8 @@ static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_
  * is returned, @folio might be split but not to @new_order)
  */
 static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int new_order,
-					       struct page *split_at, bool do_lru,
-					       struct list_head *list, enum split_type split_type)
+					       struct page *split_at, struct list_head *list,
+					       enum split_type split_type)
 {
 	struct address_space *mapping = folio->mapping;
 	XA_STATE(xas, &mapping->i_pages, folio->index);
@@ -4196,9 +4194,7 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 	}
 
 	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
-	if (do_lru)
-		lruvec = folio_lruvec_lock(folio);
-
+	lruvec = folio_lruvec_lock(folio);
 	ret = __split_frozen_folio(folio, new_order, split_at, &xas,
 				   mapping, split_type);
 
@@ -4220,8 +4216,7 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 		folio_ref_unfreeze(new_folio,
 				   folio_cache_ref_count(new_folio) + 1);
 
-		if (do_lru)
-			lru_add_split_folio(folio, new_folio, lruvec, list);
+		lru_add_split_folio(folio, new_folio, lruvec, list);
 
 		/* Add the new folio to the page cache. */
 		if (new_folio->index < end) {
@@ -4247,9 +4242,7 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 	 * 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);
+	lruvec_unlock(lruvec);
 fail:
 	/*
 	 * If we want to use try_to_migrate() on file in unmap_folio,
@@ -4333,7 +4326,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 						     false, list, split_type);
 	else
 		ret = __folio_split_unmap_and_freeze_file(folio, new_order, split_at,
-							  true, list, split_type);
+							  list, split_type);
 
 	/*
 	 * Unlock all after-split folios except the one containing

-- 
2.55.0




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

* [PATCH v3 14/18] mm/huge_memory: clean up after-split folio freeing in __folio_split
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (12 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 13/18] mm/huge_memory: drop the unused do_lru argument of the file split helper Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-27 16:48   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 15/18] mm/huge_memory: lift order-0 restriction for swapcache split Kairui Song via B4 Relay
                   ` (4 subsequent siblings)
  18 siblings, 1 reply; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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.

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

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 84c6e4bbaa88..113a33cddace 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4337,14 +4337,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
+		 * lru_lock, so we defer folio_put() on tail pages until
 		 * after the split completes.
 		 */
-		free_folio_and_swap_cache(new_folio);
+		if (is_swapcache && !folio_mapped(new_folio))
+			folio_free_swap(new_folio);
+		folio_unlock(new_folio);
+		folio_put(new_folio);
 	}
 
 out:
@@ -4371,7 +4373,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
  * isolated from LRU (if applicable)
  *
  * Upon return, the folio is not remapped, split folios are not added to LRU,
- * free_folio_and_swap_cache() is not called, and new folios remain locked.
+ * folio_free_swap() is not called, and new folios remain locked.
  *
  * Return: 0 on success, -EAGAIN if the folio cannot be split (e.g., due to
  *         insufficient reference count or extra pins).

-- 
2.55.0




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

* [PATCH v3 15/18] mm/huge_memory: lift order-0 restriction for swapcache split
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (13 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 14/18] mm/huge_memory: clean up after-split folio freeing in __folio_split Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-27 16:51   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 16/18] mm/huge_memory: clarify supported split orders in comment Kairui Song via B4 Relay
                   ` (3 subsequent siblings)
  18 siblings, 1 reply; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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.

Acked-by: Zi Yan <ziy@nvidia.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 31 +++++++++++++------------------
 1 file changed, 13 insertions(+), 18 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 113a33cddace..06f353f937d1 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3797,6 +3797,7 @@ static int __split_frozen_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_frozen_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,19 +3888,13 @@ 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))
@@ -4405,11 +4400,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] 74+ messages in thread

* [PATCH v3 16/18] mm/huge_memory: clarify supported split orders in comment
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (14 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 15/18] mm/huge_memory: lift order-0 restriction for swapcache split Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-26  2:02   ` Zi Yan
  2026-08-27 16:54   ` David Hildenbrand (Arm)
  2026-08-20 18:55 ` [PATCH v3 17/18] mm/huge_memory: count only swap cache refs in anon folio split Kairui Song via B4 Relay
                   ` (2 subsequent siblings)
  18 siblings, 2 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	Kairui Song

From: Kairui Song <kasong@tencent.com>

The doc comment for __split_huge_page_to_list_to_order() needs an
update: only order 1 is rejected for anon and swapcache folios,
matching the new_order == 1 check in folio_check_splittable().

Also realign the continuation line of the function signature while at
it.

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

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 06f353f937d1..0a971ca48151 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4400,11 +4400,10 @@ 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. 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.
+ * 4) @new_order != 1 for anon or swapcache. 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
@@ -4432,7 +4431,7 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  * with the folio. Splitting to order 0 is compatible with all folios.
  */
 int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
-				     unsigned int new_order)
+				       unsigned int new_order)
 {
 	struct folio *folio = page_folio(page);
 

-- 
2.55.0




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

* [PATCH v3 17/18] mm/huge_memory: count only swap cache refs in anon folio split
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (15 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 16/18] mm/huge_memory: clarify supported split orders in comment Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-20 18:55 ` [PATCH v3 18/18] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio Kairui Song via B4 Relay
  2026-08-27 12:19 ` [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Yeoreum Yun
  18 siblings, 0 replies; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	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.

Reviewed-by: Zi Yan <ziy@nvidia.com>
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 0a971ca48151..3e4c0fac7ba6 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);
 }
@@ -4003,7 +4003,7 @@ static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_
 				    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();
@@ -4045,7 +4045,7 @@ static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_
 		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);
+				   folio_swapcache_ref_count(new_folio) + 1);
 		if (do_lru)
 			lru_add_split_folio(folio, new_folio, lruvec, list);
 		if (ci)
@@ -4053,7 +4053,7 @@ static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_
 	}
 
 	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);
@@ -4099,6 +4099,7 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 	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;
@@ -4170,22 +4171,16 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 		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 */
@@ -4209,7 +4204,7 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 		next = folio_next(new_folio);
 
 		folio_ref_unfreeze(new_folio,
-				   folio_cache_ref_count(new_folio) + 1);
+				   folio_nr_pages(new_folio) + 1);
 
 		lru_add_split_folio(folio, new_folio, lruvec, list);
 
@@ -4236,7 +4231,7 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 	 * 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);
 	lruvec_unlock(lruvec);
 fail:
 	/*

-- 
2.55.0




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

* [PATCH v3 18/18] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (16 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 17/18] mm/huge_memory: count only swap cache refs in anon folio split Kairui Song via B4 Relay
@ 2026-08-20 18:55 ` Kairui Song via B4 Relay
  2026-08-26  2:06   ` Zi Yan
  2026-08-27 12:19 ` [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Yeoreum Yun
  18 siblings, 1 reply; 74+ messages in thread
From: Kairui Song via B4 Relay @ 2026-08-20 18:55 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, Shivam Kalra, Kairui Song,
	Kairui Song

From: Kairui Song <kasong@tencent.com>

The mapping parameter only served as a non-NULL check to detect
whether page cache entries need updating.  The xa_state pointer
conveys exactly the same information: the anon split helper passes
NULL and the file split helper passes &xas, which is non-NULL iff
the folio is in the page cache.

Use the xas pointer instead and drop the parameter, along with its
kerneldoc entry.

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

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 3e4c0fac7ba6..cc9f7e0d4194 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3761,7 +3761,6 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
  * @split_at: in buddy allocator like split, the folio containing @split_at
  *            will be split until its order becomes @new_order.
  * @xas: xa_state pointing to folio->mapping->i_pages and locked by caller
- * @mapping: @folio->mapping
  * @split_type: if the split is uniform or not (buddy allocator like split)
  *
  *
@@ -3794,7 +3793,7 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
  */
 static int __split_frozen_folio(struct folio *folio, int new_order,
 		struct page *split_at, struct xa_state *xas,
-		struct address_space *mapping, enum split_type split_type)
+		enum split_type split_type)
 {
 	const bool is_anon = folio_test_anon(folio);
 	const bool is_swapcache = folio_test_swapcache(folio);
@@ -3816,7 +3815,7 @@ static int __split_frozen_folio(struct folio *folio, int new_order,
 		if ((is_anon || is_swapcache) && split_order == 1)
 			continue;
 
-		if (mapping) {
+		if (xas) {
 			/*
 			 * uniform split has xas_split_alloc() called before
 			 * irq is disabled to allocate enough memory, whereas
@@ -4030,8 +4029,7 @@ static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_
 	if (do_lru)
 		lruvec = folio_lruvec_lock(folio);
 
-	ret = __split_frozen_folio(folio, new_order, split_at, NULL,
-				   NULL, split_type);
+	ret = __split_frozen_folio(folio, new_order, split_at, NULL, split_type);
 
 	/*
 	 * Unfreeze the post-split folios and put them back to the right
@@ -4185,8 +4183,7 @@ static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int
 
 	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
 	lruvec = folio_lruvec_lock(folio);
-	ret = __split_frozen_folio(folio, new_order, split_at, &xas,
-				   mapping, split_type);
+	ret = __split_frozen_folio(folio, new_order, split_at, &xas, split_type);
 
 	/*
 	 * Unfreeze after-split folios and put them back to the right

-- 
2.55.0




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

* Re: [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check
  2026-08-20 18:55 ` [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
@ 2026-08-23  8:53   ` Barry Song
  2026-08-27 14:13   ` Kiryl Shutsemau
  2026-08-27 15:57   ` David Hildenbrand (Arm)
  2 siblings, 0 replies; 74+ messages in thread
From: Barry Song @ 2026-08-23  8:53 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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, Youngjun Park, Shivam Kalra,
	Kairui Song

On Fri, Aug 21, 2026 at 2:55 AM Kairui Song via B4 Relay
<devnull+kasong.tencent.com@kernel.org> 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")
> Acked-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>

LGTM, thanks!

Reviewed-by: Barry Song <baohua@kernel.org>


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

* Re: [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio
  2026-08-20 18:55 ` [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
@ 2026-08-26  1:31   ` Zi Yan
  2026-08-27 14:58   ` Kiryl Shutsemau
  2026-08-27 16:19   ` David Hildenbrand (Arm)
  2 siblings, 0 replies; 74+ messages in thread
From: Zi Yan @ 2026-08-26  1:31 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, Shivam Kalra, Kairui Song

On Thu Aug 20, 2026 at 2:55 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 splitting, some cleanups become easy to apply, and helped drop a
> few now-redundant checks. Also introduce a folio iteration helper to
> avoid a common pitfall of iterating post-split sub-folios: a sub folio
> might get freed mid-iteration as pointed out by Zi [1].
>
> Link: https://lore.kernel.org/linux-mm/DKJSFCLP967N.YBR4DNK1NM2N@nvidia.com/ [1]
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 119 +++++++++++++++++++++++++++++++++++--------------------
>  1 file changed, 75 insertions(+), 44 deletions(-)
>

LGTM.

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



-- 
Best Regards,
Yan, Zi



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

* Re: [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios
  2026-08-20 18:55 ` [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios Kairui Song via B4 Relay
@ 2026-08-26  1:55   ` Zi Yan
  2026-08-27 16:41   ` David Hildenbrand (Arm)
  1 sibling, 0 replies; 74+ messages in thread
From: Zi Yan @ 2026-08-26  1:55 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, Shivam Kalra, Kairui Song

On Thu Aug 20, 2026 at 2:55 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 | 38 ++++++++++++++++++++++----------------
>  1 file changed, 22 insertions(+), 16 deletions(-)
>

LGTM.

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

-- 
Best Regards,
Yan, Zi



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

* Re: [PATCH v3 12/18] mm/huge_memory: add kerneldoc for the split helpers
  2026-08-20 18:55 ` [PATCH v3 12/18] mm/huge_memory: add kerneldoc for the split helpers Kairui Song via B4 Relay
@ 2026-08-26  1:57   ` Zi Yan
  2026-08-27 16:45   ` David Hildenbrand (Arm)
  1 sibling, 0 replies; 74+ messages in thread
From: Zi Yan @ 2026-08-26  1:57 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, Shivam Kalra, Kairui Song

On Thu Aug 20, 2026 at 2:55 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> Document __folio_split_unmap_and_freeze() and
> __folio_split_unmap_and_freeze_file(), and rename the file split
> helper's definition to match its call site.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 38 ++++++++++++++++++++++++++++++++++++++
>  1 file changed, 38 insertions(+)
>

LGTM.

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

-- 
Best Regards,
Yan, Zi



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

* Re: [PATCH v3 13/18] mm/huge_memory: drop the unused do_lru argument of the file split helper
  2026-08-20 18:55 ` [PATCH v3 13/18] mm/huge_memory: drop the unused do_lru argument of the file split helper Kairui Song via B4 Relay
@ 2026-08-26  1:57   ` Zi Yan
  2026-08-27 16:45   ` David Hildenbrand (Arm)
  1 sibling, 0 replies; 74+ messages in thread
From: Zi Yan @ 2026-08-26  1:57 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, Shivam Kalra, Kairui Song

On Thu Aug 20, 2026 at 2:55 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> The only caller of __folio_split_unmap_and_freeze_file() always passes
> do_lru as true, so the argument and the branches gated on it are dead
> code.  Drop it.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 19 ++++++-------------
>  1 file changed, 6 insertions(+), 13 deletions(-)
>

Thanks.

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


-- 
Best Regards,
Yan, Zi



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

* Re: [PATCH v3 16/18] mm/huge_memory: clarify supported split orders in comment
  2026-08-20 18:55 ` [PATCH v3 16/18] mm/huge_memory: clarify supported split orders in comment Kairui Song via B4 Relay
@ 2026-08-26  2:02   ` Zi Yan
  2026-08-27 17:48     ` Kairui Song
  2026-08-27 16:54   ` David Hildenbrand (Arm)
  1 sibling, 1 reply; 74+ messages in thread
From: Zi Yan @ 2026-08-26  2:02 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, Shivam Kalra, Kairui Song

On Thu Aug 20, 2026 at 2:55 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> The doc comment for __split_huge_page_to_list_to_order() needs an
> update: only order 1 is rejected for anon and swapcache folios,
> matching the new_order == 1 check in folio_check_splittable().
>
> Also realign the continuation line of the function signature while at
> it.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 11 +++++------
>  1 file changed, 5 insertions(+), 6 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 06f353f937d1..0a971ca48151 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -4400,11 +4400,10 @@ 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. 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.
> + * 4) @new_order != 1 for anon or swapcache. Anon folios need subpage 2 for

We use head page + tail pages instead of subpages, so the sentence could
be

Anon folios stores _deferred_list in second tail page, which order-1
folios lack, ...

> + *    _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
> @@ -4432,7 +4431,7 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
>   * with the folio. Splitting to order 0 is compatible with all folios.
>   */
>  int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
> -				     unsigned int new_order)
> +				       unsigned int new_order)
>  {
>  	struct folio *folio = page_folio(page);
>  

Otherwise, LGTM.

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



-- 
Best Regards,
Yan, Zi



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

* Re: [PATCH v3 18/18] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio
  2026-08-20 18:55 ` [PATCH v3 18/18] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio Kairui Song via B4 Relay
@ 2026-08-26  2:06   ` Zi Yan
  0 siblings, 0 replies; 74+ messages in thread
From: Zi Yan @ 2026-08-26  2:06 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, Shivam Kalra, Kairui Song

On Thu Aug 20, 2026 at 2:55 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> The mapping parameter only served as a non-NULL check to detect
> whether page cache entries need updating.  The xa_state pointer
> conveys exactly the same information: the anon split helper passes
> NULL and the file split helper passes &xas, which is non-NULL iff
> the folio is in the page cache.
>
> Use the xas pointer instead and drop the parameter, along with its
> kerneldoc entry.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 11 ++++-------
>  1 file changed, 4 insertions(+), 7 deletions(-)
>

Makes sense.

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

-- 
Best Regards,
Yan, Zi



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

* Re: [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-20 18:55 ` [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
@ 2026-08-27  8:46   ` Barry Song
  2026-08-27  9:31     ` Kairui Song
  2026-08-27 14:33   ` Kiryl Shutsemau
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 74+ messages in thread
From: Barry Song @ 2026-08-27  8:46 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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, Youngjun Park, Shivam Kalra,
	Kairui Song

On Fri, Aug 21, 2026 at 2:55 AM Kairui Song via B4 Relay
<devnull+kasong.tencent.com@kernel.org> 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 does a defensive check 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. It
> rejects the split and returns -EINVAL without unfreezing the folio or
> restoring the counters. That error path is buggy: if it is ever taken,
> it leaves the folio frozen and stuck, skews the counters, and fires
> the VM_WARN_ON_ONCE_FOLIO for a state that is actually legitimate.
>
> Check for this case up front in folio_check_splittable and return
> -EBUSY before any state is modified, so the split routine always backs
> out cleanly.
>
> Also fix a bracket style issue that checkpatch.pl keeps complaining
> about.
>
> 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")
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>

LGTM,
Reviewed-by: Barry Song <baohua@kernel.org>

[...]
>
> -               if (folio_test_swapcache(folio)) {
> -                       if (mapping) {
> -                               VM_WARN_ON_ONCE_FOLIO(mapping, folio);
> -                               return -EINVAL;

BTW, I’m really curious whether you’ve ever encountered this warning?

Best Regards
Barry


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

* Re: [PATCH v3 03/18] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
  2026-08-20 18:55 ` [PATCH v3 03/18] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation Kairui Song via B4 Relay
@ 2026-08-27  9:05   ` Barry Song
  2026-08-27 14:39   ` Kiryl Shutsemau
  2026-08-27 16:02   ` David Hildenbrand (Arm)
  2 siblings, 0 replies; 74+ messages in thread
From: Barry Song @ 2026-08-27  9:05 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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, Youngjun Park, Shivam Kalra,
	Kairui Song

On Fri, Aug 21, 2026 at 2:55 AM Kairui Song via B4 Relay
<devnull+kasong.tencent.com@kernel.org> 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.
>
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---

Good indentation reduction.

Reviewed-by: Barry Song <baohua@kernel.org>


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

* Re: [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-27  8:46   ` Barry Song
@ 2026-08-27  9:31     ` Kairui Song
  0 siblings, 0 replies; 74+ messages in thread
From: Kairui Song @ 2026-08-27  9:31 UTC (permalink / raw)
  To: Barry Song
  Cc: linux-mm, 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, Youngjun Park, Shivam Kalra

On Thu, Aug 27, 2026 at 4:46 PM Barry Song <baohua@kernel.org> wrote:
>
> On Fri, Aug 21, 2026 at 2:55 AM Kairui Song via B4 Relay
> <devnull+kasong.tencent.com@kernel.org> 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 does a defensive check 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. It
> > rejects the split and returns -EINVAL without unfreezing the folio or
> > restoring the counters. That error path is buggy: if it is ever taken,
> > it leaves the folio frozen and stuck, skews the counters, and fires
> > the VM_WARN_ON_ONCE_FOLIO for a state that is actually legitimate.
> >
> > Check for this case up front in folio_check_splittable and return
> > -EBUSY before any state is modified, so the split routine always backs
> > out cleanly.
> >
> > Also fix a bracket style issue that checkpatch.pl keeps complaining
> > about.
> >
> > 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")
> > Reviewed-by: Zi Yan <ziy@nvidia.com>
> > Signed-off-by: Kairui Song <kasong@tencent.com>
>
> LGTM,
> Reviewed-by: Barry Song <baohua@kernel.org>

Thanks for the review!

> [...]
> >
> > -               if (folio_test_swapcache(folio)) {
> > -                       if (mapping) {
> > -                               VM_WARN_ON_ONCE_FOLIO(mapping, folio);
> > -                               return -EINVAL;
>
> BTW, I’m really curious whether you’ve ever encountered this warning?
>

Nope, found by reading the code. It's possible in theory, the race
window is super short so maybe no one observed it, or observed it but
hard to debug it (a random frozen folio). Anyway we should just fix
it.


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

* Re: [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits
  2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
                   ` (17 preceding siblings ...)
  2026-08-20 18:55 ` [PATCH v3 18/18] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio Kairui Song via B4 Relay
@ 2026-08-27 12:19 ` Yeoreum Yun
  18 siblings, 0 replies; 74+ messages in thread
From: Yeoreum Yun @ 2026-08-27 12:19 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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,
	Shivam Kalra, 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;
> 135 split calls per run, 50 test run):
> 
> Before: 67.9 us, stddev: 1.59
> After:  66.4 us, stddev: 1.19
> 
> The patched kernel is slightly faster. The stack usage is also reduced
> by about ~10%, with a very slight growth of huge_memory.o.
> 
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
> Changes in v3:
> - Get rid of for_each_folio_safe and open code it.
> - Check if the folio is mapped before freeing it swap cache to avoid
>   potential performance lose.
> - Initial test and binary analyze showed everything is very similiar to
>   previously series.
> - Drop the redundant mapping argument of __split_frozen_folio
> - Link to v2: https://patch.msgid.link/20260813-swap-thp-cleanup-v2-0-d2ee48c6aa49@tencent.com
> 
> Changes in v2:
> - Return -EBUSY instead of -EINVAL for swap cache & shmem folio split
>   attempt.
> - Introduce a for_each_folio_safe macro to dedupliate the code and
>   hightlight the reason we need to keep the iterate safe from folio
>   freeing. [ Zi Yan ]
> - Rename __split_unmapped_folio() to __split_frozen_folio [ Zi Yan ]
> - Rename __folio_freeze_split_unmap_anon. [ Zi Yan ]
> - Several comment improments [ Zi Yan ]
> - Drop an unused do_lru argument.
> - Previouse test results are basically unchanged, stack usage reduced,
>   object very slightly larger.
> - Link to v1: https://patch.msgid.link/20260808-swap-thp-cleanup-v1-0-689939a7ccc3@tencent.com
> 
> ---
> Kairui Song (18):
>       mm/swap: fix off-by-one in swap cache replace sanity check
>       mm/huge_memory: fix rejection of swap cache folios with a mapping
>       mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
>       mm/huge_memory: split the routine for splitting anon and file folio
>       mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio()
>       mm/huge_memory: consolidate irq and locking for folio split
>       mm/huge_memory: move EOF trimming into the file split helper
>       mm/huge_memory: move unmap and remap into the split helpers
>       mm/huge_memory: 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: add kerneldoc for the split helpers
>       mm/huge_memory: drop the unused do_lru argument of the file split helper
>       mm/huge_memory: clean up after-split folio freeing in __folio_split
>       mm/huge_memory: lift order-0 restriction for swapcache split
>       mm/huge_memory: clarify supported split orders in comment
>       mm/huge_memory: count only swap cache refs in anon folio split
>       mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio
> 
>  mm/huge_memory.c | 633 +++++++++++++++++++++++++++++--------------------------
>  mm/swap_state.c  |   3 +-
>  2 files changed, 338 insertions(+), 298 deletions(-)
> ---
> base-commit: 4b2ae13f3393ef4b4bce0021e8762790354f369f
> change-id: 20260804-swap-thp-cleanup-6ce2be6cf3b8
> 
> Best regards,
> --  
> Kairui Song <kasong@tencent.com>

Nice cleanup. this series look good to me.

Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>

-- 
Sincerely,
Yeoreum Yun


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

* Re: [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check
  2026-08-20 18:55 ` [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
  2026-08-23  8:53   ` Barry Song
@ 2026-08-27 14:13   ` Kiryl Shutsemau
  2026-08-27 15:57   ` David Hildenbrand (Arm)
  2 siblings, 0 replies; 74+ messages in thread
From: Kiryl Shutsemau @ 2026-08-27 14:13 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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,
	Shivam Kalra, Kairui Song

On Fri, Aug 21, 2026 at 02:55:14AM +0800, 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")
> Acked-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>

Acked-by: Kiryl Shutsemau (Meta) <kas@kernel.org>

> ---
>  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 b76eb3d876fd..59a577f685b5 100644
> --- a/mm/swap_state.c
> +++ b/mm/swap_state.c
> @@ -389,8 +389,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);

I don't particularly like increment mixed with condition, but it seems
to be consistent with other swap code...

>  	}
>  }
>  
> 
> -- 
> 2.55.0
> 
> 

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-20 18:55 ` [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
  2026-08-27  8:46   ` Barry Song
@ 2026-08-27 14:33   ` Kiryl Shutsemau
  2026-08-27 15:58     ` David Hildenbrand (Arm)
  2026-08-27 14:36   ` Kiryl Shutsemau
  2026-08-27 16:00   ` David Hildenbrand (Arm)
  3 siblings, 1 reply; 74+ messages in thread
From: Kiryl Shutsemau @ 2026-08-27 14:33 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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,
	Shivam Kalra, Kairui Song

On Fri, Aug 21, 2026 at 02:55:15AM +0800, 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 does a defensive check 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. It
> rejects the split and returns -EINVAL without unfreezing the folio or
> restoring the counters. That error path is buggy: if it is ever taken,
> it leaves the folio frozen and stuck, skews the counters, and fires
> the VM_WARN_ON_ONCE_FOLIO for a state that is actually legitimate.

If you are going to repost this, could you make "This error path is
buggy" a new paragraph?

> 
> Check for this case up front in folio_check_splittable and return
> -EBUSY before any state is modified, so the split routine always backs
> out cleanly.
> 
> Also fix a bracket style issue that checkpatch.pl keeps complaining
> about.
> 
> 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")

This is inert bug, right? I don't think we want the commit to be
backported. I expect stable machinery to pick it up based on Fixes.

Is there a way to express "not stable material" while keeping Fixes: in
place?

> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>

Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-20 18:55 ` [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
  2026-08-27  8:46   ` Barry Song
  2026-08-27 14:33   ` Kiryl Shutsemau
@ 2026-08-27 14:36   ` Kiryl Shutsemau
  2026-08-27 16:01     ` David Hildenbrand (Arm)
  2026-08-27 16:00   ` David Hildenbrand (Arm)
  3 siblings, 1 reply; 74+ messages in thread
From: Kiryl Shutsemau @ 2026-08-27 14:36 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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,
	Shivam Kalra, Kairui Song

On Fri, Aug 21, 2026 at 02:55:15AM +0800, Kairui Song via B4 Relay wrote:
> @@ -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)

Just noticed, !folio->mapping is always !is_anon as folio_test_anon() is
encoded in folio->mapping.

>  		return -EBUSY;
>  
>  	/* order-1 is not supported for anonymous THP. */

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v3 03/18] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
  2026-08-20 18:55 ` [PATCH v3 03/18] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation Kairui Song via B4 Relay
  2026-08-27  9:05   ` Barry Song
@ 2026-08-27 14:39   ` Kiryl Shutsemau
  2026-08-27 16:02   ` David Hildenbrand (Arm)
  2 siblings, 0 replies; 74+ messages in thread
From: Kiryl Shutsemau @ 2026-08-27 14:39 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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,
	Shivam Kalra, Kairui Song

On Fri, Aug 21, 2026 at 02:55:16AM +0800, 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.
> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>

Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>

Zi already pointed out that it interacts with my change:

https://lore.kernel.org/all/20260826162101.1314941-3-kirill@shutemov.name

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio
  2026-08-20 18:55 ` [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
  2026-08-26  1:31   ` Zi Yan
@ 2026-08-27 14:58   ` Kiryl Shutsemau
  2026-08-27 17:19     ` Kairui Song
  2026-08-27 16:19   ` David Hildenbrand (Arm)
  2 siblings, 1 reply; 74+ messages in thread
From: Kiryl Shutsemau @ 2026-08-27 14:58 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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,
	Shivam Kalra, Kairui Song

On Fri, Aug 21, 2026 at 02:55:17AM +0800, Kairui Song via B4 Relay wrote:
> @@ -4064,7 +4095,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.

This gave me a pause.

I don't know much about the zone device private thing and I had to dig
to check if such folios are always anon. They are. But it is not obvious
to me why this limitation could not be lifted tomorrow. Nothing in "not
directly addressable by the CPU" implies anon-only.

I would appreciate some explanation in the commit message and maybe
VM_WARN_ON_ONCE_FOLIO(folio_is_device_private(folio), folio) in the file
path to flag surprises.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v3 05/18] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio()
  2026-08-20 18:55 ` [PATCH v3 05/18] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio() Kairui Song via B4 Relay
@ 2026-08-27 15:06   ` Kiryl Shutsemau
  2026-08-27 16:22     ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 74+ messages in thread
From: Kiryl Shutsemau @ 2026-08-27 15:06 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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,
	Shivam Kalra, Kairui Song

On Fri, Aug 21, 2026 at 02:55:18AM +0800, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
> 
> The helper splits a folio whose refcount is frozen: the frozen refcount
> is the state it relies on, while unmapping is arranged by the caller
> beforehand. The old name caused confusion and people may try to call the
> helper on non-frozen folios.
> 
> Suggested-by: Zi Yan <ziy@nvidia.com>
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>

This patch is fine:

Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>

> @@ -3989,8 +3989,8 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
>  	if (do_lru)
>  		lruvec = folio_lruvec_lock(folio);
>  
> -	ret = __split_unmapped_folio(folio, new_order, split_at, NULL,
> -				     NULL, split_type);
> +	ret = __split_frozen_folio(folio, new_order, split_at, NULL,
> +				   NULL, split_type);
>  
>  	/*
>  	 * Unfreeze the post-split folios and put them back to the right
> @@ -4053,8 +4053,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
>  	if (do_lru)
>  		lruvec = folio_lruvec_lock(folio);
>  
> -	ret = __split_unmapped_folio(folio, new_order, split_at, xas,
> -				     mapping, split_type);
> +	ret = __split_frozen_folio(folio, new_order, split_at, xas,
> +				   mapping, split_type);
>  
>  	/*
>  	 * Unfreeze after-split folios and put them back to the right

But now you have *unmapped_anon/file() call *frozen_folio().

Given that you introduced *unmapped_anon/file() in the previous patch,
why not give them the right names from the start?

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v3 06/18] mm/huge_memory: consolidate irq and locking for folio split
  2026-08-20 18:55 ` [PATCH v3 06/18] mm/huge_memory: consolidate irq and locking for folio split Kairui Song via B4 Relay
@ 2026-08-27 15:15   ` Kiryl Shutsemau
  2026-08-27 16:24   ` David Hildenbrand (Arm)
  1 sibling, 0 replies; 74+ messages in thread
From: Kiryl Shutsemau @ 2026-08-27 15:15 UTC (permalink / raw)
  To: kasong
  Cc: linux-mm, 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,
	Shivam Kalra, Kairui Song

On Fri, Aug 21, 2026 at 02:55:19AM +0800, 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 helpers manage their own irq and locking state.
> This lets __folio_split() drop its local irq handling and fail label,
> preparing for further cleanup.
> 
> The file path now uses xas_lock_irq() instead of local_irq_disable()
> with xas_lock(). The two are equivalent on non-RT, and
> TRANSPARENT_HUGEPAGE cannot be enabled on RT anyway. This conversion
> also buys consistency: every other place in mm/ that freezes a folio
> while it is still reachable through the page cache already takes the
> lock this way. This was actually the last plain xas_lock() on
> mapping->i_pages left in mm. If we are going to support RT, spinning
> on frozen folio refs could be a problem, but it already exists in
> many places and should be fixed generically.
> 
> The anon helper keeps a single local_irq_disable() as before, because
> it has to cover several plain spinlocks at once.
> 
> The dropped xas_reset() was a no-op as the xa_state is not walked
> before the xas_load() under the lock.
> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>

Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check
  2026-08-20 18:55 ` [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
  2026-08-23  8:53   ` Barry Song
  2026-08-27 14:13   ` Kiryl Shutsemau
@ 2026-08-27 15:57   ` David Hildenbrand (Arm)
  2 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 15:57 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, 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")
> Acked-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


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

* Re: [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-27 14:33   ` Kiryl Shutsemau
@ 2026-08-27 15:58     ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 15:58 UTC (permalink / raw)
  To: Kiryl Shutsemau, kasong
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/27/26 16:33, Kiryl Shutsemau wrote:
> On Fri, Aug 21, 2026 at 02:55:15AM +0800, 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 does a defensive check 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. It
>> rejects the split and returns -EINVAL without unfreezing the folio or
>> restoring the counters. That error path is buggy: if it is ever taken,
>> it leaves the folio frozen and stuck, skews the counters, and fires
>> the VM_WARN_ON_ONCE_FOLIO for a state that is actually legitimate.
> 
> If you are going to repost this, could you make "This error path is
> buggy" a new paragraph?
> 
>>
>> Check for this case up front in folio_check_splittable and return
>> -EBUSY before any state is modified, so the split routine always backs
>> out cleanly.
>>
>> Also fix a bracket style issue that checkpatch.pl keeps complaining
>> about.
>>
>> 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")
> 
> This is inert bug, right? I don't think we want the commit to be
> backported. I expect stable machinery to pick it up based on Fixes.
> 
> Is there a way to express "not stable material" while keeping Fixes: in
> place?
In mm, stable never picks stuff up automatically.

it's usually best to write in the patch description why the decision was made
that it should or should not be, stable material.

-- 
Cheers,

David


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

* Re: [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-20 18:55 ` [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
                     ` (2 preceding siblings ...)
  2026-08-27 14:36   ` Kiryl Shutsemau
@ 2026-08-27 16:00   ` David Hildenbrand (Arm)
  2026-08-27 17:02     ` Kiryl Shutsemau
  2026-08-27 17:07     ` Kairui Song
  3 siblings, 2 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:00 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, 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 does a defensive check 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. It
> rejects the split and returns -EINVAL without unfreezing the folio or
> restoring the counters. That error path is buggy: if it is ever taken,
> it leaves the folio frozen and stuck, skews the counters, and fires
> the VM_WARN_ON_ONCE_FOLIO for a state that is actually legitimate.
> 
> Check for this case up front in folio_check_splittable and return
> -EBUSY before any state is modified, so the split routine always backs
> out cleanly.
> 
> Also fix a bracket style issue that checkpatch.pl keeps complaining
> about.
> 
> 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")
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 27 ++++++++++++++++-----------
>  1 file changed, 16 insertions(+), 11 deletions(-)
> 
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index ced400f72d43..a6759a14e057 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);

Both const please.

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


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

* Re: [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-27 14:36   ` Kiryl Shutsemau
@ 2026-08-27 16:01     ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:01 UTC (permalink / raw)
  To: Kiryl Shutsemau, kasong
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/27/26 16:36, Kiryl Shutsemau wrote:
> On Fri, Aug 21, 2026 at 02:55:15AM +0800, Kairui Song via B4 Relay wrote:
>> @@ -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)
> 
> Just noticed, !folio->mapping is always !is_anon as folio_test_anon() is
> encoded in folio->mapping.

Ack. folio_mapping() behaves differently in that sense. confusing :)

-- 
Cheers,

David


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

* Re: [PATCH v3 03/18] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
  2026-08-20 18:55 ` [PATCH v3 03/18] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation Kairui Song via B4 Relay
  2026-08-27  9:05   ` Barry Song
  2026-08-27 14:39   ` Kiryl Shutsemau
@ 2026-08-27 16:02   ` David Hildenbrand (Arm)
  2 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:02 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, 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.
> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


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

* Re: [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio
  2026-08-20 18:55 ` [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
  2026-08-26  1:31   ` Zi Yan
  2026-08-27 14:58   ` Kiryl Shutsemau
@ 2026-08-27 16:19   ` David Hildenbrand (Arm)
  2026-08-27 17:17     ` Kairui Song
  2 siblings, 1 reply; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:19 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, 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.

Splitting that up makes sense.

> 
> While splitting, some cleanups become easy to apply, and helped drop a
> few now-redundant checks. Also introduce a folio iteration helper to
> avoid a common pitfall of iterating post-split sub-folios: a sub folio
> might get freed mid-iteration as pointed out by Zi [1].
> 
> Link: https://lore.kernel.org/linux-mm/DKJSFCLP967N.YBR4DNK1NM2N@nvidia.com/ [1]
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 119 +++++++++++++++++++++++++++++++++++--------------------
>  1 file changed, 75 insertions(+), 44 deletions(-)
> 
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 7fb603ac500f..c3fd6757c14c 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -3933,11 +3933,9 @@ 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)

Switch to double tab indentation instead while at it. Same for the other function.

>  {
>  	struct folio *end_folio = folio_next(folio);
>  	struct swap_cluster_info *ci = NULL;
> @@ -3948,7 +3946,6 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
>  	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
> @@ -3956,7 +3953,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;
>  
> @@ -3986,24 +3983,72 @@ 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 post-split folios and put them back to the right

Why call it "post-split" here when it's "after-split" in the other comment?

> +	 * place. Keep the head @folio frozen until the end: sub entries
> +	 * in swap cache must be updated first, so a concurrent
> +	 * swap_cache_get_folio() cannot return the head folio for a sub
> +	 * entry (folio_try_get() will fail on the head @folio until unfreeze).
> +	 */
> +	for (new_folio = folio_next(folio); new_folio != end_folio;
> +	     new_folio = next) {
> +		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);
> +		if (ci)
> +			__swap_cache_replace_folio(ci, folio, new_folio);
> +	}

This smells like duplicate code now. That should better be factored out?

[...]

> -	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.
> @@ -4075,8 +4105,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;
>  }
> @@ -4230,10 +4258,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);
>  	}


I was briefly wondering whether shmem folios in the swapcache would now go
through __folio_freeze_split_unmapped_anon(). But folio_check_splittable()
rejects them.

I think we should consider changing all the "if (mapping)" checks there to
"is_anon" instead.

is_anon implies no mapping and !is_anon implies that we need a mapping.


This patch LGTM, but I think we should clean up __folio_split() further. Maybe
taht happens in the next patches in this series :)

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


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

* Re: [PATCH v3 05/18] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio()
  2026-08-27 15:06   ` Kiryl Shutsemau
@ 2026-08-27 16:22     ` David Hildenbrand (Arm)
  2026-08-27 17:21       ` Kairui Song
  0 siblings, 1 reply; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:22 UTC (permalink / raw)
  To: Kiryl Shutsemau, kasong
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/27/26 17:06, Kiryl Shutsemau wrote:
> On Fri, Aug 21, 2026 at 02:55:18AM +0800, Kairui Song via B4 Relay wrote:
>> From: Kairui Song <kasong@tencent.com>
>>
>> The helper splits a folio whose refcount is frozen: the frozen refcount
>> is the state it relies on, while unmapping is arranged by the caller
>> beforehand. The old name caused confusion and people may try to call the
>> helper on non-frozen folios.
>>
>> Suggested-by: Zi Yan <ziy@nvidia.com>
>> Reviewed-by: Zi Yan <ziy@nvidia.com>
>> Signed-off-by: Kairui Song <kasong@tencent.com>
> 
> This patch is fine:
> 
> Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
> 
>> @@ -3989,8 +3989,8 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
>>  	if (do_lru)
>>  		lruvec = folio_lruvec_lock(folio);
>>  
>> -	ret = __split_unmapped_folio(folio, new_order, split_at, NULL,
>> -				     NULL, split_type);
>> +	ret = __split_frozen_folio(folio, new_order, split_at, NULL,
>> +				   NULL, split_type);
>>  
>>  	/*
>>  	 * Unfreeze the post-split folios and put them back to the right
>> @@ -4053,8 +4053,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
>>  	if (do_lru)
>>  		lruvec = folio_lruvec_lock(folio);
>>  
>> -	ret = __split_unmapped_folio(folio, new_order, split_at, xas,
>> -				     mapping, split_type);
>> +	ret = __split_frozen_folio(folio, new_order, split_at, xas,
>> +				   mapping, split_type);
>>  
>>  	/*
>>  	 * Unfreeze after-split folios and put them back to the right
> 
> But now you have *unmapped_anon/file() call *frozen_folio().
> 
> Given that you introduced *unmapped_anon/file() in the previous patch,
> why not give them the right names from the start?
> 

+1

And just for completeness:  we cannot possibly freeze a mapped folio. So frozen
implies unmapped.

So the function can possibly just be called

	__folio_freeze_split_file

And we can can add a

	VM_WARN_ON_ONCE(folio_mapped(folio));

to self-document the expectation in the code.

-- 
Cheers,

David


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

* Re: [PATCH v3 06/18] mm/huge_memory: consolidate irq and locking for folio split
  2026-08-20 18:55 ` [PATCH v3 06/18] mm/huge_memory: consolidate irq and locking for folio split Kairui Song via B4 Relay
  2026-08-27 15:15   ` Kiryl Shutsemau
@ 2026-08-27 16:24   ` David Hildenbrand (Arm)
  1 sibling, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:24 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song


> +
> +	/*
> +	 * 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;
> +	}

How can this actually happen? We locked the folio and folio->mapping is set. How
could it possibly get truncated in the meantime?

> +
> +	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) {
> @@ -4106,6 +4123,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;
>  }
>  
> @@ -4245,19 +4264,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) {

Ah, there it is :)

Nice!


-- 
Cheers,

David


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

* Re: [PATCH v3 07/18] mm/huge_memory: move EOF trimming into the file split helper
  2026-08-20 18:55 ` [PATCH v3 07/18] mm/huge_memory: move EOF trimming into the file split helper Kairui Song via B4 Relay
@ 2026-08-27 16:25   ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:25 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, 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.
> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


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

* Re: [PATCH v3 08/18] mm/huge_memory: move unmap and remap into the split helpers
  2026-08-20 18:55 ` [PATCH v3 08/18] mm/huge_memory: move unmap and remap into the split helpers Kairui Song via B4 Relay
@ 2026-08-27 16:32   ` David Hildenbrand (Arm)
  2026-08-27 17:35     ` Kairui Song
  0 siblings, 1 reply; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:32 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, 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.

I can understand why we would want to move the remap_page().

But why the unmap_folio()?

> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 58 ++++++++++++++++++++++++++++++--------------------------
>  1 file changed, 31 insertions(+), 27 deletions(-)
> 
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 01c8cf428595..af9c2edd1fba 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);

I think we should rename that function now to unmap_folio, but likely better
unmap_anon_folio().

> @@ -3933,19 +3930,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_split_unmap_and_freeze_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, *next;
>  	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);

I really hate that "bool unmap" parameter.

folio_split_unmapped() has this VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);

So you can really just do

if (folio_mapped())
	unmap_folio(folio);

Or have an early exit and hide it in unmap_folio().



-- 
Cheers,

David


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

* Re: [PATCH v3 09/18] mm/huge_memory: move anon_vma and filemap management into split helpers
  2026-08-20 18:55 ` [PATCH v3 09/18] mm/huge_memory: move anon_vma and filemap management into " Kairui Song via B4 Relay
@ 2026-08-27 16:36   ` David Hildenbrand (Arm)
  2026-08-27 17:37     ` Kairui Song
  0 siblings, 1 reply; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:36 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, 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.

This patch seems to do more than that, like relocating the
folio_expected_ref_count check (and duplicating it, which is not so nice).

> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---

[...]

> -	/*
> -	 * 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_split_unmap_and_freeze_file(folio, new_order, split_at, &xas, mapping,
> -							  true, list, split_type);
> -	} else {
> +	if (is_anon)
>  		ret = __folio_split_unmap_and_freeze_anon(folio, new_order, split_at, true,
>  							  true, list, split_type);
> -	}

Inverting the conditional seems to produce unnecessary noise in this patch.



-- 
Cheers,

David


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

* Re: [PATCH v3 10/18] mm/huge_memory: move memcg switch into the file split helper
  2026-08-20 18:55 ` [PATCH v3 10/18] mm/huge_memory: move memcg switch into the file split helper Kairui Song via B4 Relay
@ 2026-08-27 16:37   ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:37 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, 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.
> 
> Acked-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


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

* Re: [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios
  2026-08-20 18:55 ` [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios Kairui Song via B4 Relay
  2026-08-26  1:55   ` Zi Yan
@ 2026-08-27 16:41   ` David Hildenbrand (Arm)
  2026-08-27 17:41     ` Kairui Song
  1 sibling, 1 reply; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:41 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song


> +	if (anon_vma) {
>  		if (!ret && !folio_is_device_private(folio))
>  			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
>  		remap_page(folio, 1 << old_order, ttu_flags);
> @@ -4259,6 +4261,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);
> @@ -4285,8 +4288,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>  	}
>  
>  	if (is_anon)
> -		ret = __folio_split_unmap_and_freeze_anon(folio, new_order, split_at, true,
> -							  true, list, split_type);
> +		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
> +						     true, list, split_type);
> +	else if (is_swapcache)
> +		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
> +						     false, list, split_type);

These two boolean parameters are really a pain. There must be a better way.

Also, I don't like that __folio_split_unmap_and_freeze() sounds generic again.

There must be a better way for that as well. We had a clean split and now we
mangle it again.


... but I am just before my PTO and out of puff, so others can brainstorm on how
to do this cleaner :)

-- 
Cheers,

David


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

* Re: [PATCH v3 12/18] mm/huge_memory: add kerneldoc for the split helpers
  2026-08-20 18:55 ` [PATCH v3 12/18] mm/huge_memory: add kerneldoc for the split helpers Kairui Song via B4 Relay
  2026-08-26  1:57   ` Zi Yan
@ 2026-08-27 16:45   ` David Hildenbrand (Arm)
  2026-08-27 17:43     ` Kairui Song
  1 sibling, 1 reply; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:45 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
> 
> Document __folio_split_unmap_and_freeze() and
> __folio_split_unmap_and_freeze_file(), and rename the file split
> helper's definition to match its call site.

We don't really write kerneldoc for internal helpers.

If you need excessive documentation for internal helpers, it could be that the
function semantics are not clear enough to be expressed by a descriptive
function name + variable names.

Sometimes taking a step back and questioning how the code could be reworked to
be more self-documenting helps.

-- 
Cheers,

David


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

* Re: [PATCH v3 13/18] mm/huge_memory: drop the unused do_lru argument of the file split helper
  2026-08-20 18:55 ` [PATCH v3 13/18] mm/huge_memory: drop the unused do_lru argument of the file split helper Kairui Song via B4 Relay
  2026-08-26  1:57   ` Zi Yan
@ 2026-08-27 16:45   ` David Hildenbrand (Arm)
  1 sibling, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:45 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
> 
> The only caller of __folio_split_unmap_and_freeze_file() always passes
> do_lru as true, so the argument and the branches gated on it are dead
> code.  Drop it.
> 
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David


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

* Re: [PATCH v3 14/18] mm/huge_memory: clean up after-split folio freeing in __folio_split
  2026-08-20 18:55 ` [PATCH v3 14/18] mm/huge_memory: clean up after-split folio freeing in __folio_split Kairui Song via B4 Relay
@ 2026-08-27 16:48   ` David Hildenbrand (Arm)
  2026-08-27 17:47     ` Kairui Song
  0 siblings, 1 reply; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:48 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, 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.
> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 10 ++++++----
>  1 file changed, 6 insertions(+), 4 deletions(-)
> 
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 84c6e4bbaa88..113a33cddace 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -4337,14 +4337,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
> +		 * lru_lock, so we defer folio_put() on tail pages until
>  		 * after the split completes.
>  		 */
> -		free_folio_and_swap_cache(new_folio);
> +		if (is_swapcache && !folio_mapped(new_folio))
> +			folio_free_swap(new_folio);

But this is effectively duplicating code that belongs into swap code.

Should we instead have a helper next to free_folio_and_swap_cache that consumes
an already locked folio?

-- 
Cheers,

David


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

* Re: [PATCH v3 15/18] mm/huge_memory: lift order-0 restriction for swapcache split
  2026-08-20 18:55 ` [PATCH v3 15/18] mm/huge_memory: lift order-0 restriction for swapcache split Kairui Song via B4 Relay
@ 2026-08-27 16:51   ` David Hildenbrand (Arm)
  2026-08-27 17:48     ` Kairui Song
  0 siblings, 1 reply; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:51 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, 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.
> 
> Acked-by: Zi Yan <ziy@nvidia.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---

In contrast to the other cleanup patches, this patch changes behavior.

I would suggest to send all cleanups first, and send this out separately, such
that it gets proper attention and doesn't get buried in a sequence of
harmlessly-looking cleanups :)

-- 
Cheers,

David


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

* Re: [PATCH v3 16/18] mm/huge_memory: clarify supported split orders in comment
  2026-08-20 18:55 ` [PATCH v3 16/18] mm/huge_memory: clarify supported split orders in comment Kairui Song via B4 Relay
  2026-08-26  2:02   ` Zi Yan
@ 2026-08-27 16:54   ` David Hildenbrand (Arm)
  1 sibling, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 16:54 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/20/26 20:55, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
> 
> The doc comment for __split_huge_page_to_list_to_order() needs an
> update: only order 1 is rejected for anon and swapcache folios,
> matching the new_order == 1 check in folio_check_splittable().

I'm confused, why is that not updated in patch #15, which also updates that comment?

If there is some preexisting inconsistency, the comment should be fixed before
patch #15.

> 
> Also realign the continuation line of the function signature while at
> it.
> 
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 11 +++++------
>  1 file changed, 5 insertions(+), 6 deletions(-)
> 
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 06f353f937d1..0a971ca48151 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -4400,11 +4400,10 @@ 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. 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.
> + * 4) @new_order != 1 for anon or swapcache. 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
> @@ -4432,7 +4431,7 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
>   * with the folio. Splitting to order 0 is compatible with all folios.
>   */
>  int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
> -				     unsigned int new_order)
> +				       unsigned int new_order)

These functions should all be concerted to double-tab as we you them.

-- 
Cheers,

David


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

* Re: [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-27 16:00   ` David Hildenbrand (Arm)
@ 2026-08-27 17:02     ` Kiryl Shutsemau
  2026-08-27 17:11       ` David Hildenbrand (Arm)
  2026-08-27 17:07     ` Kairui Song
  1 sibling, 1 reply; 74+ messages in thread
From: Kiryl Shutsemau @ 2026-08-27 17:02 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: kasong, linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On Thu, Aug 27, 2026 at 06:00:33PM +0200, David Hildenbrand (Arm) wrote:
> On 8/20/26 20:55, 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 does a defensive check 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. It
> > rejects the split and returns -EINVAL without unfreezing the folio or
> > restoring the counters. That error path is buggy: if it is ever taken,
> > it leaves the folio frozen and stuck, skews the counters, and fires
> > the VM_WARN_ON_ONCE_FOLIO for a state that is actually legitimate.
> > 
> > Check for this case up front in folio_check_splittable and return
> > -EBUSY before any state is modified, so the split routine always backs
> > out cleanly.
> > 
> > Also fix a bracket style issue that checkpatch.pl keeps complaining
> > about.
> > 
> > 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")
> > Reviewed-by: Zi Yan <ziy@nvidia.com>
> > Signed-off-by: Kairui Song <kasong@tencent.com>
> > ---
> >  mm/huge_memory.c | 27 ++++++++++++++++-----------
> >  1 file changed, 16 insertions(+), 11 deletions(-)
> > 
> > diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> > index ced400f72d43..a6759a14e057 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);
> 
> Both const please.

I see a lot of const everywhere in mm code now. I feel I missed the memo.
Do they make a difference? I was relying on "Compiler does its
job"(TM) for things like this before.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov


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

* Re: [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-27 16:00   ` David Hildenbrand (Arm)
  2026-08-27 17:02     ` Kiryl Shutsemau
@ 2026-08-27 17:07     ` Kairui Song
  1 sibling, 0 replies; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:07 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On Fri, Aug 28, 2026 at 12:00 AM David Hildenbrand (Arm)
<david@kernel.org> wrote:
>
> On 8/20/26 20:55, 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 does a defensive check 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. It
> > rejects the split and returns -EINVAL without unfreezing the folio or
> > restoring the counters. That error path is buggy: if it is ever taken,
> > it leaves the folio frozen and stuck, skews the counters, and fires
> > the VM_WARN_ON_ONCE_FOLIO for a state that is actually legitimate.
> >
> > Check for this case up front in folio_check_splittable and return
> > -EBUSY before any state is modified, so the split routine always backs
> > out cleanly.
> >
> > Also fix a bracket style issue that checkpatch.pl keeps complaining
> > about.
> >
> > 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")
> > Reviewed-by: Zi Yan <ziy@nvidia.com>
> > Signed-off-by: Kairui Song <kasong@tencent.com>
> > ---
> >  mm/huge_memory.c | 27 ++++++++++++++++-----------
> >  1 file changed, 16 insertions(+), 11 deletions(-)
> >
> > diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> > index ced400f72d43..a6759a14e057 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);
>
> Both const please.

Thanks for the review, got it, will update and try to use const more.

>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>


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

* Re: [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-08-27 17:02     ` Kiryl Shutsemau
@ 2026-08-27 17:11       ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 17:11 UTC (permalink / raw)
  To: Kiryl Shutsemau
  Cc: kasong, linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra, Kairui Song

On 8/27/26 19:02, Kiryl Shutsemau wrote:
> On Thu, Aug 27, 2026 at 06:00:33PM +0200, David Hildenbrand (Arm) wrote:
>> On 8/20/26 20:55, 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 does a defensive check 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. It
>>> rejects the split and returns -EINVAL without unfreezing the folio or
>>> restoring the counters. That error path is buggy: if it is ever taken,
>>> it leaves the folio frozen and stuck, skews the counters, and fires
>>> the VM_WARN_ON_ONCE_FOLIO for a state that is actually legitimate.
>>>
>>> Check for this case up front in folio_check_splittable and return
>>> -EBUSY before any state is modified, so the split routine always backs
>>> out cleanly.
>>>
>>> Also fix a bracket style issue that checkpatch.pl keeps complaining
>>> about.
>>>
>>> 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")
>>> Reviewed-by: Zi Yan <ziy@nvidia.com>
>>> Signed-off-by: Kairui Song <kasong@tencent.com>
>>> ---
>>>  mm/huge_memory.c | 27 ++++++++++++++++-----------
>>>  1 file changed, 16 insertions(+), 11 deletions(-)
>>>
>>> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
>>> index ced400f72d43..a6759a14e057 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);
>>
>> Both const please.
> 
> I see a lot of const everywhere in mm code now. I feel I missed the memo.
> Do they make a difference? 

You tell the compiler that you expect this not not be an ordinary variable but
instead ... a constant.

Any future code changes that would try to modify this variable will get a
friendly greeting from the compiler stating that someone assumes this value to
never change.

The compiler can't do that for you if you don't tell it about what you consider
constant.

-- 
Cheers,

David


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

* Re: [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio
  2026-08-27 16:19   ` David Hildenbrand (Arm)
@ 2026-08-27 17:17     ` Kairui Song
  2026-08-27 19:05       ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:17 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On Fri, Aug 28, 2026 at 12:19 AM David Hildenbrand (Arm)
<david@kernel.org> wrote:
>
> Switch to double tab indentation instead while at it. Same for the other function.

Sure, will change it.

> >  {
> >       struct folio *end_folio = folio_next(folio);
> >       struct swap_cluster_info *ci = NULL;
> > @@ -3948,7 +3946,6 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
> >       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
> > @@ -3956,7 +3953,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;
> >
> > @@ -3986,24 +3983,72 @@ 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 post-split folios and put them back to the right
>
> Why call it "post-split" here when it's "after-split" in the other comment?

Will just call it after-split then.

> > +      * place. Keep the head @folio frozen until the end: sub entries
> > +      * in swap cache must be updated first, so a concurrent
> > +      * swap_cache_get_folio() cannot return the head folio for a sub
> > +      * entry (folio_try_get() will fail on the head @folio until unfreeze).
> > +      */
> > +     for (new_folio = folio_next(folio); new_folio != end_folio;
> > +          new_folio = next) {
> > +             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);
> > +             if (ci)
> > +                     __swap_cache_replace_folio(ci, folio, new_folio);
> > +     }
>
> This smells like duplicate code now. That should better be factored out?
>

There were some discussion on this in previous series, I tried to
implement a macro or helper, but it ended up being uglier. Right now
the duplication is just about 3 lines of for loop due to the split,
and the extra loop duplication existed before, so I think it's fine at
this point, maybe a helper later if this grows.

>
> > -     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.
> > @@ -4075,8 +4105,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;
> >  }
> > @@ -4230,10 +4258,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);
> >       }
>
>
> I was briefly wondering whether shmem folios in the swapcache would now go
> through __folio_freeze_split_unmapped_anon(). But folio_check_splittable()
> rejects them.
>
> I think we should consider changing all the "if (mapping)" checks there to
> "is_anon" instead.
>
> is_anon implies no mapping and !is_anon implies that we need a mapping.

Good idea.

> This patch LGTM, but I think we should clean up __folio_split() further. Maybe
> taht happens in the next patches in this series :)
>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>

Thanks!


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

* Re: [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio
  2026-08-27 14:58   ` Kiryl Shutsemau
@ 2026-08-27 17:19     ` Kairui Song
  0 siblings, 0 replies; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:19 UTC (permalink / raw)
  To: Kiryl Shutsemau
  Cc: linux-mm, 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,
	Shivam Kalra

On Thu, Aug 27, 2026 at 10:58 PM Kiryl Shutsemau <kirill@shutemov.name> wrote:
>
> On Fri, Aug 21, 2026 at 02:55:17AM +0800, Kairui Song via B4 Relay wrote:
> > @@ -4064,7 +4095,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.
>
> This gave me a pause.
>
> I don't know much about the zone device private thing and I had to dig
> to check if such folios are always anon. They are. But it is not obvious
> to me why this limitation could not be lifted tomorrow. Nothing in "not
> directly addressable by the CPU" implies anon-only.
>
> I would appreciate some explanation in the commit message and maybe
> VM_WARN_ON_ONCE_FOLIO(folio_is_device_private(folio), folio) in the file
> path to flag surprises.

Right, good idea. A sanity check and some comments are better, will add these.

Currently device private folios are always anon and never even in
swapcache, many comments and sanity checks in migrate_device.c implies
that.


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

* Re: [PATCH v3 05/18] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio()
  2026-08-27 16:22     ` David Hildenbrand (Arm)
@ 2026-08-27 17:21       ` Kairui Song
  0 siblings, 0 replies; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:21 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: Kiryl Shutsemau, linux-mm, linux-kernel, Andrew Morton,
	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,
	Shivam Kalra

On Fri, Aug 28, 2026 at 12:22 AM David Hildenbrand (Arm)
<david@kernel.org> wrote:
>
> On 8/27/26 17:06, Kiryl Shutsemau wrote:
> > On Fri, Aug 21, 2026 at 02:55:18AM +0800, Kairui Song via B4 Relay wrote:
> >> From: Kairui Song <kasong@tencent.com>
> >>
> >> The helper splits a folio whose refcount is frozen: the frozen refcount
> >> is the state it relies on, while unmapping is arranged by the caller
> >> beforehand. The old name caused confusion and people may try to call the
> >> helper on non-frozen folios.
> >>
> >> Suggested-by: Zi Yan <ziy@nvidia.com>
> >> Reviewed-by: Zi Yan <ziy@nvidia.com>
> >> Signed-off-by: Kairui Song <kasong@tencent.com>
> >
> > This patch is fine:
> >
> > Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
> >
> >> @@ -3989,8 +3989,8 @@ static int __folio_freeze_split_unmapped_anon(struct folio *folio, unsigned int
> >>      if (do_lru)
> >>              lruvec = folio_lruvec_lock(folio);
> >>
> >> -    ret = __split_unmapped_folio(folio, new_order, split_at, NULL,
> >> -                                 NULL, split_type);
> >> +    ret = __split_frozen_folio(folio, new_order, split_at, NULL,
> >> +                               NULL, split_type);
> >>
> >>      /*
> >>       * Unfreeze the post-split folios and put them back to the right
> >> @@ -4053,8 +4053,8 @@ static int __folio_freeze_split_unmapped_file(struct folio *folio, unsigned int
> >>      if (do_lru)
> >>              lruvec = folio_lruvec_lock(folio);
> >>
> >> -    ret = __split_unmapped_folio(folio, new_order, split_at, xas,
> >> -                                 mapping, split_type);
> >> +    ret = __split_frozen_folio(folio, new_order, split_at, xas,
> >> +                               mapping, split_type);
> >>
> >>      /*
> >>       * Unfreeze after-split folios and put them back to the right
> >
> > But now you have *unmapped_anon/file() call *frozen_folio().
> >
> > Given that you introduced *unmapped_anon/file() in the previous patch,
> > why not give them the right names from the start?
> >
>
> +1
>
> And just for completeness:  we cannot possibly freeze a mapped folio. So frozen
> implies unmapped.
>
> So the function can possibly just be called
>
>         __folio_freeze_split_file
>
> And we can can add a
>
>         VM_WARN_ON_ONCE(folio_mapped(folio));
>
> to self-document the expectation in the code.
>

Right, I'll just squash this into the previous commit then?


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

* Re: [PATCH v3 08/18] mm/huge_memory: move unmap and remap into the split helpers
  2026-08-27 16:32   ` David Hildenbrand (Arm)
@ 2026-08-27 17:35     ` Kairui Song
  2026-08-27 19:09       ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:35 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On Fri, Aug 28, 2026 at 12:32 AM David Hildenbrand (Arm)
<david@kernel.org> wrote:
>
> On 8/20/26 20:55, 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.
>
> I can understand why we would want to move the remap_page().
>
> But why the unmap_folio()?
>
> >
> > Reviewed-by: Zi Yan <ziy@nvidia.com>
> > Signed-off-by: Kairui Song <kasong@tencent.com>
> > ---
> >  mm/huge_memory.c | 58 ++++++++++++++++++++++++++++++--------------------------
> >  1 file changed, 31 insertions(+), 27 deletions(-)
> >
> > diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> > index 01c8cf428595..af9c2edd1fba 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);
>
> I think we should rename that function now to unmap_folio, but likely better
> unmap_anon_folio().
>
> > @@ -3933,19 +3930,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_split_unmap_and_freeze_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, *next;
> >       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);
>
> I really hate that "bool unmap" parameter.
>
> folio_split_unmapped() has this VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
>
> So you can really just do
>
> if (folio_mapped())
>         unmap_folio(folio);
>
> Or have an early exit and hide it in unmap_folio().

The only special case here is device private memory, from folio_split_unmapped.

Maybe I can just keep the unmap_folfio where it is, but
__folio_split_unmap_and_freeze_anon need to know if the folio need to
be remapped.

Will a "bool remap" parameter look good?


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

* Re: [PATCH v3 09/18] mm/huge_memory: move anon_vma and filemap management into split helpers
  2026-08-27 16:36   ` David Hildenbrand (Arm)
@ 2026-08-27 17:37     ` Kairui Song
  0 siblings, 0 replies; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:37 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On Fri, Aug 28, 2026 at 12:36 AM David Hildenbrand (Arm)
<david@kernel.org> wrote:
>
> On 8/20/26 20:55, 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.
>
> This patch seems to do more than that, like relocating the
> folio_expected_ref_count check (and duplicating it, which is not so nice).
>
> >
> > Reviewed-by: Zi Yan <ziy@nvidia.com>
> > Signed-off-by: Kairui Song <kasong@tencent.com>
> > ---
>
> [...]
>
> > -     /*
> > -      * 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_split_unmap_and_freeze_file(folio, new_order, split_at, &xas, mapping,
> > -                                                       true, list, split_type);
> > -     } else {
> > +     if (is_anon)
> >               ret = __folio_split_unmap_and_freeze_anon(folio, new_order, split_at, true,
> >                                                         true, list, split_type);
> > -     }
>
> Inverting the conditional seems to produce unnecessary noise in this patch.

Will try to make this look nicer.


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

* Re: [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios
  2026-08-27 16:41   ` David Hildenbrand (Arm)
@ 2026-08-27 17:41     ` Kairui Song
  2026-08-27 19:16       ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:41 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On Fri, Aug 28, 2026 at 12:41 AM David Hildenbrand (Arm)
<david@kernel.org> wrote:
> > +     if (anon_vma) {
> >               if (!ret && !folio_is_device_private(folio))
> >                       ttu_flags = TTU_USE_SHARED_ZEROPAGE;
> >               remap_page(folio, 1 << old_order, ttu_flags);
> > @@ -4259,6 +4261,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);
> > @@ -4285,8 +4288,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
> >       }
> >
> >       if (is_anon)
> > -             ret = __folio_split_unmap_and_freeze_anon(folio, new_order, split_at, true,
> > -                                                       true, list, split_type);
> > +             ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
> > +                                                  true, list, split_type);
> > +     else if (is_swapcache)
> > +             ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
> > +                                                  false, list, split_type);
>
> These two boolean parameters are really a pain. There must be a better way.
>
> Also, I don't like that __folio_split_unmap_and_freeze() sounds generic again.
>
> There must be a better way for that as well. We had a clean split and now we
> mangle it again.
>
>
> ... but I am just before my PTO and out of puff, so others can brainstorm on how
> to do this cleaner :)

What if we just keep the __folio_split_unmap_and_freeze_anon name? And
have that "bool remap" mentioned in previous reply? Clean swap cache
could be considered "anon compatible" as they might become anon
anytime, and even for shmem, shmem shares the same IO and LRU like
anon, so considering clean swap cache "psudo anon" seems not too bad?


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

* Re: [PATCH v3 12/18] mm/huge_memory: add kerneldoc for the split helpers
  2026-08-27 16:45   ` David Hildenbrand (Arm)
@ 2026-08-27 17:43     ` Kairui Song
  0 siblings, 0 replies; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:43 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On Fri, Aug 28, 2026 at 12:45 AM David Hildenbrand (Arm)
<david@kernel.org> wrote:
>
> On 8/20/26 20:55, Kairui Song via B4 Relay wrote:
> > From: Kairui Song <kasong@tencent.com>
> >
> > Document __folio_split_unmap_and_freeze() and
> > __folio_split_unmap_and_freeze_file(), and rename the file split
> > helper's definition to match its call site.
>
> We don't really write kerneldoc for internal helpers.
>
> If you need excessive documentation for internal helpers, it could be that the
> function semantics are not clear enough to be expressed by a descriptive
> function name + variable names.
>
> Sometimes taking a step back and questioning how the code could be reworked to
> be more self-documenting helps.

Sure, I don't mind dropping these; they were added during mid-review.


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

* Re: [PATCH v3 14/18] mm/huge_memory: clean up after-split folio freeing in __folio_split
  2026-08-27 16:48   ` David Hildenbrand (Arm)
@ 2026-08-27 17:47     ` Kairui Song
  2026-08-27 19:33       ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:47 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On Fri, Aug 28, 2026 at 12:48 AM David Hildenbrand (Arm)
<david@kernel.org> wrote:
>
> On 8/20/26 20:55, 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.
> >
> > Reviewed-by: Zi Yan <ziy@nvidia.com>
> > Signed-off-by: Kairui Song <kasong@tencent.com>
> > ---
> >  mm/huge_memory.c | 10 ++++++----
> >  1 file changed, 6 insertions(+), 4 deletions(-)
> >
> > diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> > index 84c6e4bbaa88..113a33cddace 100644
> > --- a/mm/huge_memory.c
> > +++ b/mm/huge_memory.c
> > @@ -4337,14 +4337,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
> > +              * lru_lock, so we defer folio_put() on tail pages until
> >                * after the split completes.
> >                */
> > -             free_folio_and_swap_cache(new_folio);
> > +             if (is_swapcache && !folio_mapped(new_folio))
> > +                     folio_free_swap(new_folio);
>
> But this is effectively duplicating code that belongs into swap code.
>
> Should we instead have a helper next to free_folio_and_swap_cache that consumes
> an already locked folio?

I think the only duplication here is the is_swapcache and folio_mapped
check? folio_free_swap is a common swap helper. And putting the
folio_unlock and folio_put in the swap side looks odd to me, since the
folio is locked here and refs get here through the function's
convention. Pairing these unlock and put closer to the convention
doesn't seem too bad IMO.


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

* Re: [PATCH v3 15/18] mm/huge_memory: lift order-0 restriction for swapcache split
  2026-08-27 16:51   ` David Hildenbrand (Arm)
@ 2026-08-27 17:48     ` Kairui Song
  2026-08-27 20:22       ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:48 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On Fri, Aug 28, 2026 at 12:51 AM David Hildenbrand (Arm)
<david@kernel.org> wrote:
>
> On 8/20/26 20:55, 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.
> >
> > Acked-by: Zi Yan <ziy@nvidia.com>
> > Signed-off-by: Kairui Song <kasong@tencent.com>
> > ---
>
> In contrast to the other cleanup patches, this patch changes behavior.
>
> I would suggest to send all cleanups first, and send this out separately, such
> that it gets proper attention and doesn't get buried in a sequence of
> harmlessly-looking cleanups :)

No problem, send this later then :)


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

* Re: [PATCH v3 16/18] mm/huge_memory: clarify supported split orders in comment
  2026-08-26  2:02   ` Zi Yan
@ 2026-08-27 17:48     ` Kairui Song
  0 siblings, 0 replies; 74+ messages in thread
From: Kairui Song @ 2026-08-27 17:48 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,
	Shivam Kalra

On Wed, Aug 26, 2026 at 10:02 AM Zi Yan <ziy@nvidia.com> wrote:
>
> On Thu Aug 20, 2026 at 2:55 PM EDT, Kairui Song via B4 Relay wrote:
> > From: Kairui Song <kasong@tencent.com>
> >
> > The doc comment for __split_huge_page_to_list_to_order() needs an
> > update: only order 1 is rejected for anon and swapcache folios,
> > matching the new_order == 1 check in folio_check_splittable().
> >
> > Also realign the continuation line of the function signature while at
> > it.
> >
> > Signed-off-by: Kairui Song <kasong@tencent.com>
> > ---
> >  mm/huge_memory.c | 11 +++++------
> >  1 file changed, 5 insertions(+), 6 deletions(-)
> >
> > diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> > index 06f353f937d1..0a971ca48151 100644
> > --- a/mm/huge_memory.c
> > +++ b/mm/huge_memory.c
> > @@ -4400,11 +4400,10 @@ 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. 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.
> > + * 4) @new_order != 1 for anon or swapcache. Anon folios need subpage 2 for
>
> We use head page + tail pages instead of subpages, so the sentence could
> be
>
> Anon folios stores _deferred_list in second tail page, which order-1
> folios lack, ...

Good to know, will update.
>
> > + *    _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
> > @@ -4432,7 +4431,7 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
> >   * with the folio. Splitting to order 0 is compatible with all folios.
> >   */
> >  int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
> > -                                  unsigned int new_order)
> > +                                    unsigned int new_order)
> >  {
> >       struct folio *folio = page_folio(page);
> >
>
> Otherwise, LGTM.
>
> Reviewed-by: Zi Yan <ziy@nvidia.com>
>

Thanks!


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

* Re: [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio
  2026-08-27 17:17     ` Kairui Song
@ 2026-08-27 19:05       ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 19:05 UTC (permalink / raw)
  To: Kairui Song
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

> 
>>> +      * place. Keep the head @folio frozen until the end: sub entries
>>> +      * in swap cache must be updated first, so a concurrent
>>> +      * swap_cache_get_folio() cannot return the head folio for a sub
>>> +      * entry (folio_try_get() will fail on the head @folio until unfreeze).
>>> +      */
>>> +     for (new_folio = folio_next(folio); new_folio != end_folio;
>>> +          new_folio = next) {
>>> +             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);
>>> +             if (ci)
>>> +                     __swap_cache_replace_folio(ci, folio, new_folio);
>>> +     }
>>
>> This smells like duplicate code now. That should better be factored out?
>>
> 
> There were some discussion on this in previous series, I tried to
> implement a macro or helper, but it ended up being uglier. Right now
> the duplication is just about 3 lines of for loop due to the split,
> and the extra loop duplication existed before, so I think it's fine at
> this point, maybe a helper later if this grows.

Actually I wanted to drop this comment after digging into the code, but forgot.
So it's fine with me.

-- 
Cheers,

David


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

* Re: [PATCH v3 08/18] mm/huge_memory: move unmap and remap into the split helpers
  2026-08-27 17:35     ` Kairui Song
@ 2026-08-27 19:09       ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 19:09 UTC (permalink / raw)
  To: Kairui Song
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On 8/27/26 19:35, Kairui Song wrote:
> On Fri, Aug 28, 2026 at 12:32 AM David Hildenbrand (Arm)
> <david@kernel.org> wrote:
>>
>> On 8/20/26 20:55, 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.
>>
>> I can understand why we would want to move the remap_page().
>>
>> But why the unmap_folio()?
>>
>>>
>>> Reviewed-by: Zi Yan <ziy@nvidia.com>
>>> Signed-off-by: Kairui Song <kasong@tencent.com>
>>> ---
>>>  mm/huge_memory.c | 58 ++++++++++++++++++++++++++++++--------------------------
>>>  1 file changed, 31 insertions(+), 27 deletions(-)
>>>
>>> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
>>> index 01c8cf428595..af9c2edd1fba 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);
>>
>> I think we should rename that function now to unmap_folio, but likely better
>> unmap_anon_folio().
>>
>>> @@ -3933,19 +3930,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_split_unmap_and_freeze_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, *next;
>>>       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);
>>
>> I really hate that "bool unmap" parameter.
>>
>> folio_split_unmapped() has this VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
>>
>> So you can really just do
>>
>> if (folio_mapped())
>>         unmap_folio(folio);
>>
>> Or have an early exit and hide it in unmap_folio().
> 
> The only special case here is device private memory, from folio_split_unmapped.
> 
> Maybe I can just keep the unmap_folfio where it is, but
> __folio_split_unmap_and_freeze_anon need to know if the folio need to
> be remapped.
> 
> Will a "bool remap" parameter look good?

I'd avoid that and rather have something like the following in the function.

if (folio_mapped()) {
	folio_unmapped = true;
	unmap_folio(folio);
}

...

if (folio_unmapped)
	remap_anon_folio(folio)


...

-- 
Cheers,

David


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

* Re: [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios
  2026-08-27 17:41     ` Kairui Song
@ 2026-08-27 19:16       ` David Hildenbrand (Arm)
  2026-08-27 19:29         ` David Hildenbrand (Arm)
  0 siblings, 1 reply; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 19:16 UTC (permalink / raw)
  To: Kairui Song
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On 8/27/26 19:41, Kairui Song wrote:
> On Fri, Aug 28, 2026 at 12:41 AM David Hildenbrand (Arm)
> <david@kernel.org> wrote:
>>> +     if (anon_vma) {
>>>               if (!ret && !folio_is_device_private(folio))
>>>                       ttu_flags = TTU_USE_SHARED_ZEROPAGE;
>>>               remap_page(folio, 1 << old_order, ttu_flags);
>>> @@ -4259,6 +4261,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);
>>> @@ -4285,8 +4288,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
>>>       }
>>>
>>>       if (is_anon)
>>> -             ret = __folio_split_unmap_and_freeze_anon(folio, new_order, split_at, true,
>>> -                                                       true, list, split_type);
>>> +             ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
>>> +                                                  true, list, split_type);
>>> +     else if (is_swapcache)
>>> +             ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
>>> +                                                  false, list, split_type);
>>
>> These two boolean parameters are really a pain. There must be a better way.
>>
>> Also, I don't like that __folio_split_unmap_and_freeze() sounds generic again.
>>
>> There must be a better way for that as well. We had a clean split and now we
>> mangle it again.
>>
>>
>> ... but I am just before my PTO and out of puff, so others can brainstorm on how
>> to do this cleaner :)
> 
> What if we just keep the __folio_split_unmap_and_freeze_anon name? And
> have that "bool remap" mentioned in previous reply? Clean swap cache

I think you can just rely on folio_mapped() internally. swapcache-only is never
mapped. And anon folios you can just detect internally as well, right?

> could be considered "anon compatible" as they might become anon

Dirty but better than what you have right now. As you say, there really is no
difference.

But for example the mapping/remapping is superfluous. So you could also consider
a _swapcache that reuses some common bits from the _anon helper.

-- 
Cheers,

David


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

* Re: [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios
  2026-08-27 19:16       ` David Hildenbrand (Arm)
@ 2026-08-27 19:29         ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 19:29 UTC (permalink / raw)
  To: Kairui Song
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On 8/27/26 21:16, David Hildenbrand (Arm) wrote:
> On 8/27/26 19:41, Kairui Song wrote:
>> On Fri, Aug 28, 2026 at 12:41 AM David Hildenbrand (Arm)
>> <david@kernel.org> wrote:
>>>
>>> These two boolean parameters are really a pain. There must be a better way.
>>>
>>> Also, I don't like that __folio_split_unmap_and_freeze() sounds generic again.
>>>
>>> There must be a better way for that as well. We had a clean split and now we
>>> mangle it again.
>>>
>>>
>>> ... but I am just before my PTO and out of puff, so others can brainstorm on how
>>> to do this cleaner :)
>>
>> What if we just keep the __folio_split_unmap_and_freeze_anon name? And
>> have that "bool remap" mentioned in previous reply? Clean swap cache
> 
> I think you can just rely on folio_mapped() internally. swapcache-only is never
> mapped. And anon folios you can just detect internally as well, right?
> 
>> could be considered "anon compatible" as they might become anon
> 
> Dirty but better than what you have right now. As you say, there really is no
> difference.
> 
> But for example the mapping/remapping is superfluous. So you could also consider
> a _swapcache that reuses some common bits from the _anon helper.
> 

Okay, I took another look at the resulting __folio_split() and I think if you
pack the "Unlock all after-split folios" into a separate helper function, and
call this helper from inside the _anon / _file etc, you can just end up with
very nice function names:

	ret = folio_check_splittable(folio, new_order, split_type);
	...

	if (is_anon)
		ret = __folio_split_anon(...);
	else if (is_swapcache)
		ret = __folio_split_swapcache(...)
	else
		ret = __folio_split_file(...)

out:
	if (is_pmd_order(old_order))
		...
	return ret;

IOW, the common bits don't have to be called by __folio_split(), but can be
called by the other helpers.


I guess it would be ok to have something like

__folio_split_anon_or_swapcache(...);

(the function name length is acceptable)

-- 
Cheers,

David


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

* Re: [PATCH v3 14/18] mm/huge_memory: clean up after-split folio freeing in __folio_split
  2026-08-27 17:47     ` Kairui Song
@ 2026-08-27 19:33       ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 19:33 UTC (permalink / raw)
  To: Kairui Song
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On 8/27/26 19:47, Kairui Song wrote:
> On Fri, Aug 28, 2026 at 12:48 AM David Hildenbrand (Arm)
> <david@kernel.org> wrote:
>>
>> On 8/20/26 20:55, 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.
>>>
>>> Reviewed-by: Zi Yan <ziy@nvidia.com>
>>> Signed-off-by: Kairui Song <kasong@tencent.com>
>>> ---
>>>  mm/huge_memory.c | 10 ++++++----
>>>  1 file changed, 6 insertions(+), 4 deletions(-)
>>>
>>> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
>>> index 84c6e4bbaa88..113a33cddace 100644
>>> --- a/mm/huge_memory.c
>>> +++ b/mm/huge_memory.c
>>> @@ -4337,14 +4337,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
>>> +              * lru_lock, so we defer folio_put() on tail pages until
>>>                * after the split completes.
>>>                */
>>> -             free_folio_and_swap_cache(new_folio);
>>> +             if (is_swapcache && !folio_mapped(new_folio))
>>> +                     folio_free_swap(new_folio);
>>
>> But this is effectively duplicating code that belongs into swap code.
>>
>> Should we instead have a helper next to free_folio_and_swap_cache that consumes
>> an already locked folio?
> 
> I think the only duplication here is the is_swapcache and folio_mapped
> check? folio_free_swap is a common swap helper. And putting the
> folio_unlock and folio_put in the swap side looks odd to me, since the
> folio is locked here and refs get here through the function's
> convention. Pairing these unlock and put closer to the convention
> doesn't seem too bad IMO.

I was more concerned about the folio_mapped() check. Because the code now has to
guess under which circumstances it makes sense to call folio_free_swap(), which
is precisely what free_folio_and_swap_cache() avoided.

And we already do have other code doing that, like KSM's replace_page(), and it
feels like that should all be cleaned up.

But no strong objection to your current code. It just looks a bit suboptimal
that we have no helper that wraps the swapcache and folio_mapped() check.

-- 
Cheers,

David


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

* Re: [PATCH v3 15/18] mm/huge_memory: lift order-0 restriction for swapcache split
  2026-08-27 17:48     ` Kairui Song
@ 2026-08-27 20:22       ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 74+ messages in thread
From: David Hildenbrand (Arm) @ 2026-08-27 20:22 UTC (permalink / raw)
  To: Kairui Song
  Cc: linux-mm, linux-kernel, Andrew Morton, 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, Shivam Kalra

On 8/27/26 19:48, Kairui Song wrote:
> On Fri, Aug 28, 2026 at 12:51 AM David Hildenbrand (Arm)
> <david@kernel.org> wrote:
>>
>> On 8/20/26 20:55, 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.
>>>
>>> Acked-by: Zi Yan <ziy@nvidia.com>
>>> Signed-off-by: Kairui Song <kasong@tencent.com>
>>> ---
>>
>> In contrast to the other cleanup patches, this patch changes behavior.
>>
>> I would suggest to send all cleanups first, and send this out separately, such
>> that it gets proper attention and doesn't get buried in a sequence of
>> harmlessly-looking cleanups :)
> 
> No problem, send this later then :)

On second thought, the

"mm/huge_memory: allow splitting mappingless swap cache folios"

is already such a change. And I guess only having both these behavior-changing
things separated from the cleanups might be harder, right?

-- 
Cheers,

David


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

end of thread, other threads:[~2026-08-27 20:23 UTC | newest]

Thread overview: 74+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-20 18:55 [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Kairui Song via B4 Relay
2026-08-20 18:55 ` [PATCH v3 01/18] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
2026-08-23  8:53   ` Barry Song
2026-08-27 14:13   ` Kiryl Shutsemau
2026-08-27 15:57   ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 02/18] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
2026-08-27  8:46   ` Barry Song
2026-08-27  9:31     ` Kairui Song
2026-08-27 14:33   ` Kiryl Shutsemau
2026-08-27 15:58     ` David Hildenbrand (Arm)
2026-08-27 14:36   ` Kiryl Shutsemau
2026-08-27 16:01     ` David Hildenbrand (Arm)
2026-08-27 16:00   ` David Hildenbrand (Arm)
2026-08-27 17:02     ` Kiryl Shutsemau
2026-08-27 17:11       ` David Hildenbrand (Arm)
2026-08-27 17:07     ` Kairui Song
2026-08-20 18:55 ` [PATCH v3 03/18] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation Kairui Song via B4 Relay
2026-08-27  9:05   ` Barry Song
2026-08-27 14:39   ` Kiryl Shutsemau
2026-08-27 16:02   ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 04/18] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
2026-08-26  1:31   ` Zi Yan
2026-08-27 14:58   ` Kiryl Shutsemau
2026-08-27 17:19     ` Kairui Song
2026-08-27 16:19   ` David Hildenbrand (Arm)
2026-08-27 17:17     ` Kairui Song
2026-08-27 19:05       ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 05/18] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio() Kairui Song via B4 Relay
2026-08-27 15:06   ` Kiryl Shutsemau
2026-08-27 16:22     ` David Hildenbrand (Arm)
2026-08-27 17:21       ` Kairui Song
2026-08-20 18:55 ` [PATCH v3 06/18] mm/huge_memory: consolidate irq and locking for folio split Kairui Song via B4 Relay
2026-08-27 15:15   ` Kiryl Shutsemau
2026-08-27 16:24   ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 07/18] mm/huge_memory: move EOF trimming into the file split helper Kairui Song via B4 Relay
2026-08-27 16:25   ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 08/18] mm/huge_memory: move unmap and remap into the split helpers Kairui Song via B4 Relay
2026-08-27 16:32   ` David Hildenbrand (Arm)
2026-08-27 17:35     ` Kairui Song
2026-08-27 19:09       ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 09/18] mm/huge_memory: move anon_vma and filemap management into " Kairui Song via B4 Relay
2026-08-27 16:36   ` David Hildenbrand (Arm)
2026-08-27 17:37     ` Kairui Song
2026-08-20 18:55 ` [PATCH v3 10/18] mm/huge_memory: move memcg switch into the file split helper Kairui Song via B4 Relay
2026-08-27 16:37   ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 11/18] mm/huge_memory: allow splitting mappingless swap cache folios Kairui Song via B4 Relay
2026-08-26  1:55   ` Zi Yan
2026-08-27 16:41   ` David Hildenbrand (Arm)
2026-08-27 17:41     ` Kairui Song
2026-08-27 19:16       ` David Hildenbrand (Arm)
2026-08-27 19:29         ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 12/18] mm/huge_memory: add kerneldoc for the split helpers Kairui Song via B4 Relay
2026-08-26  1:57   ` Zi Yan
2026-08-27 16:45   ` David Hildenbrand (Arm)
2026-08-27 17:43     ` Kairui Song
2026-08-20 18:55 ` [PATCH v3 13/18] mm/huge_memory: drop the unused do_lru argument of the file split helper Kairui Song via B4 Relay
2026-08-26  1:57   ` Zi Yan
2026-08-27 16:45   ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 14/18] mm/huge_memory: clean up after-split folio freeing in __folio_split Kairui Song via B4 Relay
2026-08-27 16:48   ` David Hildenbrand (Arm)
2026-08-27 17:47     ` Kairui Song
2026-08-27 19:33       ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 15/18] mm/huge_memory: lift order-0 restriction for swapcache split Kairui Song via B4 Relay
2026-08-27 16:51   ` David Hildenbrand (Arm)
2026-08-27 17:48     ` Kairui Song
2026-08-27 20:22       ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 16/18] mm/huge_memory: clarify supported split orders in comment Kairui Song via B4 Relay
2026-08-26  2:02   ` Zi Yan
2026-08-27 17:48     ` Kairui Song
2026-08-27 16:54   ` David Hildenbrand (Arm)
2026-08-20 18:55 ` [PATCH v3 17/18] mm/huge_memory: count only swap cache refs in anon folio split Kairui Song via B4 Relay
2026-08-20 18:55 ` [PATCH v3 18/18] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio Kairui Song via B4 Relay
2026-08-26  2:06   ` Zi Yan
2026-08-27 12:19 ` [PATCH v3 00/18] mm/huge_memory: clean up folio split and lift swapcache split limits Yeoreum Yun

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox