mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers
@ 2026-09-07 18:12 Kairui Song via B4 Relay
  2026-09-07 18:12 ` [PATCH v4 01/17] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
                   ` (16 more replies)
  0 siblings, 17 replies; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song, Kairui Song

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

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

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

Testing:

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

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

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

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

Signed-off-by: Kairui Song <kasong@tencent.com>
---
Changes in v4:
- Rebase on top of mm-unstable, also mergable on mm-new now.
- Patch 12 might need some attention as we can skip anon_vma locking for
  unmapped anon folio now due to further cleanup, including unmapped anon
  swap cache (not mappingless swap cache).
- Drop the two swap cache behavior changes, will be sent separately once
  this lands, so it gets its own attention. Also drop related doc
  updates. [ David Hildenbrand ]
- Rename the split helpers to __folio_freeze_split_anon() and
  __folio_freeze_split_file(), frozen implies unmapped.
  [ Kiryl Shutsemau, David Hildenbrand ]
- Keep zone_device_private_split_cb() in the anon helper only, and add
  VM_WARN_ON_ONCE_FOLIO(folio_is_device_private()) to the file helper:
  device private folios can only back anonymous memory.
  [ Kiryl Shutsemau ]
- Replace the "bool unmap" parameter with a folio_mapped(), rename
  remap_page() to remap_folio() and document it. [ David Hildenbrand ]
- Move the racy folio_expected_ref_count() check into unmap_folio()
  instead of duplicating it.
- Make more locals const.
- Reword the mm/swap off-by-one fix: non-uniform swap cache split is not
  enabled yet, so the bogus sanity check is currently dead code.
- Collect Reviewed-by/Acked-by tags.
- Link to v3: https://patch.msgid.link/20260821-swap-thp-cleanup-v3-0-9b43f5163238@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 (17):
      mm/swap: fix off-by-one in swap cache replace sanity check
      mm/huge_memory: fix rejection of swap cache folios with a mapping
      mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
      mm/huge_memory: split the routine for splitting anon and file folio
      mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio()
      mm/huge_memory: consolidate irq and locking for folio split
      mm/huge_memory: move EOF trimming into the file split helper
      mm/huge_memory: move unmap and remap into the split helpers
      mm/huge_memory: rename remap_page() to remap_folio()
      mm/huge_memory: move the racy refcount check into unmap_folio()
      mm/huge_memory: move filemap management into the file split helper
      mm/huge_memory: move anon_vma handling into the anon split helper
      mm/huge_memory: move memcg switch into the file split helper
      mm/huge_memory: drop the unused do_lru argument of the file split helper
      mm/huge_memory: clean up after-split folio freeing in __folio_split
      mm/huge_memory: count only swap cache refs in anon folio split
      mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio

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

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



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

* [PATCH v4 01/17] mm/swap: fix off-by-one in swap cache replace sanity check
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-07 18:12 ` [PATCH v4 02/17] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
                   ` (15 subsequent siblings)
  16 siblings, 0 replies; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	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.

Currently we don't support non-uniform swapcache split, but this still
needs a fix to clean it up and prepare for non-uniform swap cache split.

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>
Reviewed-by: Barry Song <baohua@kernel.org>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Acked-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
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 305877e1f4d7..2cf7ed2a4df4 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	[flat|nested] 37+ messages in thread

* [PATCH v4 02/17] mm/huge_memory: fix rejection of swap cache folios with a mapping
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
  2026-09-07 18:12 ` [PATCH v4 01/17] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-07 18:12 ` [PATCH v4 03/17] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation Kairui Song via B4 Relay
                   ` (14 subsequent siblings)
  16 siblings, 0 replies; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	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>
Reviewed-by: Barry Song <baohua@kernel.org>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 26 ++++++++++++++++----------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index dd66c6ad5af1..09cf40357557 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3929,6 +3929,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)
 {
+	const bool is_anon = folio_test_anon(folio);
+	const 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
@@ -3937,11 +3940,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;
 
 	/*
@@ -3952,7 +3955,7 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	 * swapcache folio split. Only uniform split to order-0 can be used
 	 * here.
 	 */
-	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && folio_test_swapcache(folio))
+	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && is_swapcache)
 		return -EINVAL;
 
 	if (is_huge_zero_folio(folio))
@@ -3961,6 +3964,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;
 }
 
@@ -4019,14 +4031,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	[flat|nested] 37+ messages in thread

* [PATCH v4 03/17] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
  2026-09-07 18:12 ` [PATCH v4 01/17] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
  2026-09-07 18:12 ` [PATCH v4 02/17] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-07 18:12 ` [PATCH v4 04/17] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
                   ` (13 subsequent siblings)
  16 siblings, 0 replies; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	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>
Reviewed-by: Barry Song <baohua@kernel.org>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 185 +++++++++++++++++++++++++++----------------------------
 1 file changed, 91 insertions(+), 94 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 09cf40357557..ad45b86819fd 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3991,126 +3991,123 @@ 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 lruvec *lruvec;
 	int ret = 0;
 
 	VM_WARN_ON_ONCE(!mapping && end);
 
-	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))
+		return -EAGAIN;
 
-		/* Take off the deferred split queue while frozen and memcg set */
-		folio_unqueue_deferred_split(folio);
+	/* Take off the deferred split queue while frozen and memcg set */
+	folio_unqueue_deferred_split(folio);
 
-		/*
-		 * deferred_split_scan() takes the folio off the queue before it
-		 * splits it, so the unqueue above finds an empty list and
-		 * leaves PG_partially_mapped set.
-		 * Clear it here: the flag does not survive the split.
-		 */
-		if (folio_test_partially_mapped(folio)) {
-			folio_clear_partially_mapped(folio);
-			mod_mthp_stat(old_order,
-				      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
-		}
+	/*
+	 * deferred_split_scan() takes the folio off the queue before it
+	 * splits it, so the unqueue above finds an empty list and
+	 * leaves PG_partially_mapped set.
+	 * Clear it here: the flag does not survive the split.
+	 */
+	if (folio_test_partially_mapped(folio)) {
+		folio_clear_partially_mapped(folio);
+		mod_mthp_stat(old_order,
+			      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
+	}
 
-		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 (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 (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);
+	if (folio_test_swapcache(folio))
+		ci = swap_cluster_get_and_lock(folio);
 
-		ret = __split_unmapped_folio(folio, new_order, split_at, xas,
-					     mapping, split_type);
+	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	if (do_lru)
+		lruvec = folio_lruvec_lock(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);
+	ret = __split_unmapped_folio(folio, new_order, split_at, xas,
+				     mapping, split_type);
 
-			next = folio_next(new_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);
 
-			zone_device_private_split_cb(folio, new_folio);
+		next = folio_next(new_folio);
 
-			folio_ref_unfreeze(new_folio,
-					   folio_cache_ref_count(new_folio) + 1);
+		zone_device_private_split_cb(folio, new_folio);
 
-			if (do_lru)
-				lru_add_split_folio(folio, new_folio, lruvec, list);
+		folio_ref_unfreeze(new_folio,
+				   folio_cache_ref_count(new_folio) + 1);
 
-			/*
-			 * 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;
-			}
+		if (do_lru)
+			lru_add_split_folio(folio, new_folio, lruvec, list);
 
-			/* Anonymous folio without swap cache */
-			if (!mapping)
-				continue;
+		/*
+		 * 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;
+		}
 
-			/* 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;
-			}
+		/* Anonymous folio without swap cache */
+		if (!mapping)
+			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;
-			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);
+		/* 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;
 		}
 
-		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);
+		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)
-			lruvec_unlock(lruvec);
+	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 (ci)
-			swap_cluster_unlock(ci);
-	} else {
-		return -EAGAIN;
-	}
+	if (do_lru)
+		lruvec_unlock(lruvec);
+	if (ci)
+		swap_cluster_unlock(ci);
 
 	return ret;
 }

-- 
2.55.0



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

* [PATCH v4 04/17] mm/huge_memory: split the routine for splitting anon and file folio
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (2 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 03/17] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-08 15:11   ` Kiryl Shutsemau
  2026-09-07 18:12 ` [PATCH v4 05/17] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio() Kairui Song via B4 Relay
                   ` (12 subsequent siblings)
  16 siblings, 1 reply; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	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.

The zone_device_private_split_cb() calls are only kept in the anon
variant, as device private folios can only back anonymous memory, and
add a VM_WARN_ON_ONCE_FOLIO() at the entry of the file variant.

Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 123 +++++++++++++++++++++++++++++++++++--------------------
 1 file changed, 78 insertions(+), 45 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ad45b86819fd..0e28a3e66b67 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3984,11 +3984,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_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;
@@ -3997,8 +3995,6 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 	struct lruvec *lruvec;
 	int ret = 0;
 
-	VM_WARN_ON_ONCE(!mapping && end);
-
 	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1))
 		return -EAGAIN;
 
@@ -4017,24 +4013,75 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 			      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
 	}
 
-	if (mapping) {
+	if (folio_test_swapcache(folio))
+		ci = swap_cluster_get_and_lock(folio);
+
+	if (do_lru)
+		lruvec = folio_lruvec_lock(folio);
+
+	ret = __split_unmapped_folio(folio, new_order, split_at, NULL,
+				     NULL, split_type);
+
+	/*
+	 * Unfreeze the after-split folios and put them back to the right
+	 * place. 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_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;
+
+	/* Currently device private folios can only back anonymous memory. */
+	VM_WARN_ON_ONCE_FOLIO(folio_is_device_private(folio), folio);
+
+	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);
@@ -4044,7 +4091,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
@@ -4054,29 +4101,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,
@@ -4095,7 +4128,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.
@@ -4106,8 +4138,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;
 }
@@ -4250,7 +4280,10 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	/* block interrupt reentry in xa_lock and spinlock */
 	local_irq_disable();
-	if (mapping) {
+	if (is_anon) {
+		ret = __folio_freeze_split_anon(folio, new_order, split_at,
+						true, list, split_type);
+	} else {
 		/*
 		 * Check if the folio is present in page cache.
 		 * We assume all tail are present too, if folio is there.
@@ -4261,10 +4294,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 			ret = -EAGAIN;
 			goto fail;
 		}
+		ret = __folio_freeze_split_file(folio, new_order, split_at, &xas, mapping,
+						true, list, split_type, end,
+						&nr_shmem_dropped);
 	}
 
-	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);
@@ -4364,9 +4398,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_anon(folio, new_order, &folio->page,
+					false, NULL, SPLIT_TYPE_UNIFORM);
 	local_irq_enable();
 	return ret;
 }

-- 
2.55.0



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

* [PATCH v4 05/17] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio()
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (3 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 04/17] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-09 17:36   ` David Hildenbrand (Arm)
  2026-09-07 18:12 ` [PATCH v4 06/17] mm/huge_memory: consolidate irq and locking for folio split Kairui Song via B4 Relay
                   ` (11 subsequent siblings)
  16 siblings, 1 reply; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	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.

Also add a VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio)) to self document
that frozen implies unmapped.

Suggested-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 0e28a3e66b67..80291fac78e2 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3806,8 +3806,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
@@ -3846,7 +3846,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)
 {
@@ -3856,6 +3856,9 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 	struct folio *old_folio = folio;
 	int split_order;
 
+	/* Frozen implies unmapped, callers unmap before splitting. */
+	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
+
 	/*
 	 * split to new_order one order at a time. For uniform split,
 	 * folio is split to new_order directly.
@@ -4019,8 +4022,8 @@ static int __folio_freeze_split_anon(struct folio *folio,
 	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 after-split folios and put them back to the right
@@ -4086,8 +4089,8 @@ static int __folio_freeze_split_file(struct folio *folio,
 	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
@@ -4151,9 +4154,9 @@ static int __folio_freeze_split_file(struct folio *folio,
  * @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:
@@ -4256,7 +4259,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	[flat|nested] 37+ messages in thread

* [PATCH v4 06/17] mm/huge_memory: consolidate irq and locking for folio split
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (4 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 05/17] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio() Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-09 17:39   ` David Hildenbrand (Arm)
  2026-09-07 18:12 ` [PATCH v4 07/17] mm/huge_memory: move EOF trimming into the file split helper Kairui Song via B4 Relay
                   ` (10 subsequent siblings)
  16 siblings, 1 reply; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	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>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 58 ++++++++++++++++++++++++++------------------------------
 1 file changed, 27 insertions(+), 31 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 80291fac78e2..4ddef481cfdc 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3998,8 +3998,12 @@ static int __folio_freeze_split_anon(struct folio *folio,
 	struct lruvec *lruvec;
 	int ret = 0;
 
-	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1))
+	local_irq_disable();
+
+	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
+		local_irq_enable();
 		return -EAGAIN;
+	}
 
 	/* Take off the deferred split queue while frozen and memcg set */
 	folio_unqueue_deferred_split(folio);
@@ -4051,6 +4055,7 @@ static int __folio_freeze_split_anon(struct folio *folio,
 		lruvec_unlock(lruvec);
 	if (ci)
 		swap_cluster_unlock(ci);
+	local_irq_enable();
 
 	return ret;
 }
@@ -4069,8 +4074,21 @@ static int __folio_freeze_split_file(struct folio *folio,
 	/* Currently device private folios can only back anonymous memory. */
 	VM_WARN_ON_ONCE_FOLIO(folio_is_device_private(folio), folio);
 
-	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) {
@@ -4142,6 +4160,8 @@ static int __folio_freeze_split_file(struct folio *folio,
 	if (do_lru)
 		lruvec_unlock(lruvec);
 
+fail:
+	xas_unlock_irq(xas);
 	return ret;
 }
 
@@ -4281,32 +4301,13 @@ 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 (is_anon) {
+	if (is_anon)
 		ret = __folio_freeze_split_anon(folio, new_order, split_at,
 						true, list, split_type);
-	} else {
-		/*
-		 * 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;
-		}
+	else
 		ret = __folio_freeze_split_file(folio, new_order, split_at, &xas, mapping,
 						true, list, split_type, end,
 						&nr_shmem_dropped);
-	}
-
-fail:
-	if (mapping)
-		xas_unlock(&xas);
-
-	local_irq_enable();
 
 	if (nr_shmem_dropped)
 		shmem_uncharge(mapping->host, nr_shmem_dropped);
@@ -4390,8 +4391,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);
@@ -4400,11 +4399,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_anon(folio, new_order, &folio->page,
-					false, NULL, SPLIT_TYPE_UNIFORM);
-	local_irq_enable();
-	return ret;
+	return __folio_freeze_split_anon(folio, new_order, &folio->page,
+					 false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*

-- 
2.55.0



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

* [PATCH v4 07/17] mm/huge_memory: move EOF trimming into the file split helper
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (5 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 06/17] mm/huge_memory: consolidate irq and locking for folio split Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-07 18:12 ` [PATCH v4 08/17] mm/huge_memory: move unmap and remap into the split helpers Kairui Song via B4 Relay
                   ` (9 subsequent siblings)
  16 siblings, 0 replies; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	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>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 41 +++++++++++++++++++----------------------
 1 file changed, 19 insertions(+), 22 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 4ddef481cfdc..47ccf2326cfe 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4064,16 +4064,29 @@ static int __folio_freeze_split_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)
+		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;
 	int ret;
 
 	/* Currently device private folios can only back anonymous memory. */
 	VM_WARN_ON_ONCE_FOLIO(folio_is_device_private(folio), folio);
 
+	/*
+	 * The loop below 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);
 
 	/*
@@ -4138,10 +4151,9 @@ static int __folio_freeze_split_file(struct folio *folio,
 			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));
@@ -4162,6 +4174,8 @@ static int __folio_freeze_split_file(struct folio *folio,
 
 fail:
 	xas_unlock_irq(xas);
+	if (nr_shmem_dropped)
+		shmem_uncharge(mapping->host, nr_shmem_dropped);
 	return ret;
 }
 
@@ -4198,9 +4212,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);
@@ -4277,17 +4289,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);
 	}
 
 	/*
@@ -4306,11 +4307,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 						true, list, split_type);
 	else
 		ret = __folio_freeze_split_file(folio, new_order, split_at, &xas, mapping,
-						true, list, split_type, end,
-						&nr_shmem_dropped);
-
-	if (nr_shmem_dropped)
-		shmem_uncharge(mapping->host, nr_shmem_dropped);
+						true, list, split_type);
 
 	if (!ret && is_anon && !folio_is_device_private(folio))
 		ttu_flags = TTU_USE_SHARED_ZEROPAGE;

-- 
2.55.0



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

* [PATCH v4 08/17] mm/huge_memory: move unmap and remap into the split helpers
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (6 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 07/17] mm/huge_memory: move EOF trimming into the file split helper Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-08 15:13   ` Kiryl Shutsemau
  2026-09-07 18:12 ` [PATCH v4 09/17] mm/huge_memory: rename remap_page() to remap_folio() Kairui Song via B4 Relay
                   ` (8 subsequent siblings)
  16 siblings, 1 reply; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	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>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 35 +++++++++++++++++++++--------------
 1 file changed, 21 insertions(+), 14 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 47ccf2326cfe..7d7ce0726c19 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3640,9 +3640,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);
@@ -3995,14 +3992,21 @@ static int __folio_freeze_split_anon(struct folio *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 lruvec *lruvec;
+	bool need_remap = false;
 	int ret = 0;
 
+	if (folio_mapped(folio)) {
+		need_remap = true;
+		unmap_folio(folio);
+	}
+
 	local_irq_disable();
 
 	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
-		local_irq_enable();
-		return -EAGAIN;
+		ret = -EAGAIN;
+		goto out_no_split;
 	}
 
 	/* Take off the deferred split queue while frozen and memcg set */
@@ -4055,7 +4059,13 @@ static int __folio_freeze_split_anon(struct folio *folio,
 		lruvec_unlock(lruvec);
 	if (ci)
 		swap_cluster_unlock(ci);
+out_no_split:
 	local_irq_enable();
+	if (need_remap) {
+		if (!ret && !folio_is_device_private(folio))
+			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
+		remap_page(folio, 1 << old_order, ttu_flags);
+	}
 
 	return ret;
 }
@@ -4087,6 +4097,8 @@ static int __folio_freeze_split_file(struct folio *folio,
 	if (shmem_mapping(mapping))
 		end = shmem_fallocend(mapping->host, end);
 
+	unmap_folio(folio);
+
 	xas_lock_irq(xas);
 
 	/*
@@ -4171,8 +4183,11 @@ static int __folio_freeze_split_file(struct folio *folio,
 
 	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);
@@ -4212,7 +4227,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);
@@ -4300,8 +4314,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		goto out_unlock;
 	}
 
-	unmap_folio(folio);
-
 	if (is_anon)
 		ret = __folio_freeze_split_anon(folio, new_order, split_at,
 						true, list, split_type);
@@ -4309,11 +4321,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		ret = __folio_freeze_split_file(folio, new_order, split_at, &xas, mapping,
 						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

-- 
2.55.0



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

* [PATCH v4 09/17] mm/huge_memory: rename remap_page() to remap_folio()
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (7 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 08/17] mm/huge_memory: move unmap and remap into the split helpers Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-07 19:48   ` Zi Yan
  2026-09-08 15:17   ` Kiryl Shutsemau
  2026-09-07 18:12 ` [PATCH v4 10/17] mm/huge_memory: move the racy refcount check into unmap_folio() Kairui Song via B4 Relay
                   ` (7 subsequent siblings)
  16 siblings, 2 replies; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song, Kairui Song

From: Kairui Song <kasong@tencent.com>

remap_page() now only has one caller, __folio_freeze_split_anon(), so
rename it to remap_folio() to match the sibling helper unmap_folio().

Also add a VM_WARN_ON_FOLIO() documenting that remap_folio() is only
ever called for anon folios: unmap_folio() currently leaves file
folios unmapped after the split, so they need no remapping.

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

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 7d7ce0726c19..c5279c0d0e59 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3551,7 +3551,7 @@ static void unmap_folio(struct folio *folio)
 	/*
 	 * Anon pages need migration entries to preserve them, but file
 	 * pages can simply be left unmapped, then faulted back on demand.
-	 * If that is ever changed (perhaps for mlock), update remap_page().
+	 * If that is ever changed (perhaps for mlock), update remap_folio().
 	 */
 	if (folio_test_anon(folio))
 		try_to_migrate(folio, ttu_flags);
@@ -3636,10 +3636,17 @@ bool unmap_huge_pmd_locked(struct vm_area_struct *vma, unsigned long addr,
 	return __discard_anon_folio_pmd_locked(vma, addr, pmdp, folio);
 }
 
-static void remap_page(struct folio *folio, unsigned long nr, int flags)
+static void remap_folio(struct folio *folio, unsigned long nr, int flags)
 {
 	int i = 0;
 
+	/*
+	 * unmap_folio() installs migration entries only for anon folios,
+	 * so currently only anon folios need to be remapped. File folios
+	 * stay unmapped after the split and are faulted back on demand.
+	 */
+	VM_WARN_ON_FOLIO(!folio_test_anon(folio), folio);
+
 	for (;;) {
 		remove_migration_ptes(folio, folio, TTU_RMAP_LOCKED | flags);
 		i += folio_nr_pages(folio);
@@ -3723,7 +3730,7 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
 		 *
 		 * Note that for mapped sub-pages of an anonymous THP,
 		 * PG_anon_exclusive has been cleared in unmap_folio() and is stored in
-		 * the migration entry instead from where remap_page() will restore it.
+		 * the migration entry instead from where remap_folio() will restore it.
 		 * We can still have PG_anon_exclusive set on effectively unmapped and
 		 * unreferenced sub-pages of an anonymous THP: we can simply drop
 		 * PG_anon_exclusive (-> PG_mappedtodisk) for these here.
@@ -4064,7 +4071,7 @@ static int __folio_freeze_split_anon(struct folio *folio,
 	if (need_remap) {
 		if (!ret && !folio_is_device_private(folio))
 			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
-		remap_page(folio, 1 << old_order, ttu_flags);
+		remap_folio(folio, 1 << old_order, ttu_flags);
 	}
 
 	return ret;
@@ -4186,7 +4193,7 @@ static int __folio_freeze_split_file(struct folio *folio,
 fail:
 	/*
 	 * If we want to use try_to_migrate() on file in unmap_folio,
-	 * remember to add remap_page() and adapt it.
+	 * remember to add remap_folio() and adapt it.
 	 */
 	xas_unlock_irq(xas);
 	if (nr_shmem_dropped)

-- 
2.55.0



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

* [PATCH v4 10/17] mm/huge_memory: move the racy refcount check into unmap_folio()
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (8 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 09/17] mm/huge_memory: rename remap_page() to remap_folio() Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-07 19:52   ` Zi Yan
  2026-09-08 15:20   ` Kiryl Shutsemau
  2026-09-07 18:12 ` [PATCH v4 11/17] mm/huge_memory: move filemap management into the file split helper Kairui Song via B4 Relay
                   ` (6 subsequent siblings)
  16 siblings, 2 replies; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song, Kairui Song

From: Kairui Song <kasong@tencent.com>

The check only exists to avoid the expensive PMD-splitting unmap of a
folio that cannot be split anyway.  Move it from __folio_split() into
unmap_folio(), right before the PMD split, so both the anon and file
split helpers get the early check without repeating it.

unmap_folio() now returns -EAGAIN if the check fails and the split
helpers propagate the error.  folio_split_unmapped() drops its own
copy of the check: it works on already unmapped folios and the
definitive folio_ref_freeze() in __folio_freeze_split_anon() still
catches unexpected references.

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

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index c5279c0d0e59..17bd2c053210 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3538,13 +3538,17 @@ void vma_adjust_trans_huge(struct vm_area_struct *vma,
 		split_huge_pmd_if_needed(next, end);
 }
 
-static void unmap_folio(struct folio *folio)
+static int unmap_folio(struct folio *folio)
 {
 	enum ttu_flags ttu_flags = TTU_RMAP_LOCKED | TTU_SYNC |
 		TTU_BATCH_FLUSH;
 
 	VM_BUG_ON_FOLIO(!folio_test_large(folio), folio);
 
+	/* Racy check if we can split the page, before we split PMDs */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
+		return -EAGAIN;
+
 	if (folio_test_pmd_mappable(folio))
 		ttu_flags |= TTU_SPLIT_HUGE_PMD;
 
@@ -3559,6 +3563,8 @@ static void unmap_folio(struct folio *folio)
 		try_to_unmap(folio, ttu_flags | TTU_IGNORE_MLOCK);
 
 	try_to_unmap_flush();
+
+	return 0;
 }
 
 static bool __discard_anon_folio_pmd_locked(struct vm_area_struct *vma,
@@ -4006,7 +4012,9 @@ static int __folio_freeze_split_anon(struct folio *folio,
 
 	if (folio_mapped(folio)) {
 		need_remap = true;
-		unmap_folio(folio);
+		ret = unmap_folio(folio);
+		if (ret)
+			return ret;
 	}
 
 	local_irq_disable();
@@ -4104,7 +4112,9 @@ static int __folio_freeze_split_file(struct folio *folio,
 	if (shmem_mapping(mapping))
 		end = shmem_fallocend(mapping->host, end);
 
-	unmap_folio(folio);
+	ret = unmap_folio(folio);
+	if (ret)
+		return ret;
 
 	xas_lock_irq(xas);
 
@@ -4312,15 +4322,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		i_mmap_lock_read(mapping);
 	}
 
-	/*
-	 * Racy check if we can split the page, before unmap_folio() will
-	 * split PMDs
-	 */
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
-		ret = -EAGAIN;
-		goto out_unlock;
-	}
-
 	if (is_anon)
 		ret = __folio_freeze_split_anon(folio, new_order, split_at,
 						true, list, split_type);
@@ -4359,7 +4360,6 @@ 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);
@@ -4407,9 +4407,6 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);
 
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
-		return -EAGAIN;
-
 	return __folio_freeze_split_anon(folio, new_order, &folio->page,
 					 false, NULL, SPLIT_TYPE_UNIFORM);
 }

-- 
2.55.0



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

* [PATCH v4 11/17] mm/huge_memory: move filemap management into the file split helper
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (9 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 10/17] mm/huge_memory: move the racy refcount check into unmap_folio() Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-07 19:57   ` Zi Yan
  2026-09-08 15:25   ` Kiryl Shutsemau
  2026-09-07 18:12 ` [PATCH v4 12/17] mm/huge_memory: move anon_vma handling into the anon " Kairui Song via B4 Relay
                   ` (5 subsequent siblings)
  16 siblings, 2 replies; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song, Kairui Song

From: Kairui Song <kasong@tencent.com>

Only file split needs the filemap and xarray handling and related
variables.  Move them out of __folio_split() into the file helper so
the helper is self-contained, and simplify the parameters.

No functional change.

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

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 17bd2c053210..c4910c0b6018 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4087,16 +4087,42 @@ static int __folio_freeze_split_anon(struct folio *folio,
 
 static int __folio_freeze_split_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 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;
-	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);
 
 	/* Currently device private folios can only back anonymous memory. */
 	VM_WARN_ON_ONCE_FOLIO(folio_is_device_private(folio), folio);
@@ -4114,15 +4140,15 @@ static int __folio_freeze_split_file(struct folio *folio,
 
 	ret = unmap_folio(folio);
 	if (ret)
-		return ret;
+		goto fail_mmap_unlock;
 
-	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;
 	}
@@ -4149,7 +4175,7 @@ static int __folio_freeze_split_file(struct folio *folio,
 	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);
 
 	/*
@@ -4205,9 +4231,19 @@ static int __folio_freeze_split_file(struct folio *folio,
 	 * If we want to use try_to_migrate() on file in unmap_folio,
 	 * remember to add remap_folio() 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;
 }
 
@@ -4236,11 +4272,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;
@@ -4287,60 +4321,15 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 			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);
 	}
 
 	if (is_anon)
 		ret = __folio_freeze_split_anon(folio, new_order, split_at,
 						true, list, split_type);
 	else
-		ret = __folio_freeze_split_file(folio, new_order, split_at, &xas, mapping,
+		ret = __folio_freeze_split_file(folio, new_order, split_at,
 						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;
-	}
-
 	/*
 	 * Unlock all after-split folios except the one containing
 	 * @lock_at page. If @folio is not split, it will be kept locked.
@@ -4364,14 +4353,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		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);

-- 
2.55.0



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

* [PATCH v4 12/17] mm/huge_memory: move anon_vma handling into the anon split helper
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (10 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 11/17] mm/huge_memory: move filemap management into the file split helper Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-07 20:39   ` Zi Yan
  2026-09-08 15:56   ` Kiryl Shutsemau
  2026-09-07 18:12 ` [PATCH v4 13/17] mm/huge_memory: move memcg switch into the file " Kairui Song via B4 Relay
                   ` (4 subsequent siblings)
  16 siblings, 2 replies; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song, Kairui Song

From: Kairui Song <kasong@tencent.com>

Only anon split needs the anon_vma, and it only needs it to unmap and
remap.  Move the folio_get_anon_vma()/anon_vma_lock_write() pair out of
__folio_split() into the anon helper next to the folio_mapped()
check that already gates unmap_folio().

This makes the anon_vma conditional on folio_mapped(), which is a
behaviour change but should be fine.  folio_get_anon_vma() returns
NULL whenever !folio_mapped(), so an anon folio with
folio_mapcount() == 0 used to get -EBUSY from split_huge_page() and is
now split instead.  The realistic case is a THP that has been fully
swapped out and is still in the swap cache: swap PTEs do not contribute
mapcount, so it is !folio_mapped() but still alive.

That should be safe and right to have because:

- folio_ref_freeze() below still rejects a folio that picked up any
  reference, a mapping or a GUP pin, in the meantime.

- A parallel split is excluded by the folio lock.  The anon_vma
  write lock was added to serialize split in commit 062f1af2170a
  ("mm: thp: acquire the anon_vma rwsem for write during split"), when
  split_huge_page() did not hold the folio lock throughout. commit
  e9b61f19858a ("thp: reintroduce split_huge_page()") later made the
  folio lock a caller requirement and added the folio_ref_freeze()
  scheme, so that has been covered ever since.

- Unmapped path is already exercised by folio_split_unmapped(),
  and the swap cache split already runs well for a partially
  swapped-out mapped THP.

- folio_get_anon_vma() and folio_lock_anon_vma_read() both bail out
  on !folio_mapped() before taking the anon_vma lock, so there is
  nothing to lock against.

For mapped folios the anon_vma write lock is now released before
__folio_split() unlocks the after-split sub-folios, where previously it
was held across that loop, that window is harmless as the sub-folios
stay folio-locked and ref pinned.

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

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index c4910c0b6018..53614b875794 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4005,16 +4005,31 @@ static int __folio_freeze_split_anon(struct folio *folio,
 	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 lruvec *lruvec;
-	bool need_remap = false;
 	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.
+	 *
+	 * An unmapped folio needs none of this: folio_get_anon_vma() and
+	 * folio_lock_anon_vma_read() both bail out on !folio_mapped()
+	 * before taking the lock, and folio_ref_freeze() below still
+	 * rejects a folio that picked up a reference meanwhile. Note
+	 * a swapped-out THP counts as unmapped here as swap PTEs do
+	 * not contribute mapcount, and they are splittable.
+	 */
 	if (folio_mapped(folio)) {
-		need_remap = true;
+		anon_vma = folio_get_anon_vma(folio);
+		if (!anon_vma)
+			return -EBUSY;
+		anon_vma_lock_write(anon_vma);
 		ret = unmap_folio(folio);
 		if (ret)
-			return ret;
+			goto out_unlock;
 	}
 
 	local_irq_disable();
@@ -4076,11 +4091,16 @@ static int __folio_freeze_split_anon(struct folio *folio,
 		swap_cluster_unlock(ci);
 out_no_split:
 	local_irq_enable();
-	if (need_remap) {
+	if (anon_vma) {
 		if (!ret && !folio_is_device_private(folio))
 			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
 		remap_folio(folio, 1 << old_order, ttu_flags);
 	}
+out_unlock:
+	if (anon_vma) {
+		anon_vma_unlock_write(anon_vma);
+		put_anon_vma(anon_vma);
+	}
 
 	return ret;
 }
@@ -4275,7 +4295,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;
-	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
 	int ret;
@@ -4306,23 +4325,6 @@ 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);
-	}
-
 	if (is_anon)
 		ret = __folio_freeze_split_anon(folio, new_order, split_at,
 						true, list, split_type);
@@ -4349,11 +4351,6 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		free_folio_and_swap_cache(new_folio);
 	}
 
-	if (anon_vma) {
-		anon_vma_unlock_write(anon_vma);
-		put_anon_vma(anon_vma);
-	}
-out:
 	/* restore to caller's old_memcg */
 	set_active_memcg(old_memcg);
 	mem_cgroup_put(memcg);

-- 
2.55.0



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

* [PATCH v4 13/17] mm/huge_memory: move memcg switch into the file split helper
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (11 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 12/17] mm/huge_memory: move anon_vma handling into the anon " Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-09  8:21   ` Kiryl Shutsemau
  2026-09-07 18:12 ` [PATCH v4 14/17] mm/huge_memory: drop the unused do_lru argument of " Kairui Song via B4 Relay
                   ` (3 subsequent siblings)
  16 siblings, 1 reply; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song, Kairui Song

From: Kairui Song <kasong@tencent.com>

The xarray node allocations in __folio_freeze_split_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>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.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 53614b875794..ccd48cd5f94b 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4113,6 +4113,7 @@ static int __folio_freeze_split_file(struct folio *folio,
 	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;
@@ -4125,9 +4126,18 @@ static int __folio_freeze_split_file(struct folio *folio,
 	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);
 
@@ -4263,6 +4273,9 @@ static int __folio_freeze_split_file(struct folio *folio,
 	 */
 	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;
 }
@@ -4294,7 +4307,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;
@@ -4304,27 +4316,20 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	if (folio != page_folio(split_at) || folio != page_folio(lock_at)) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	if (new_order >= old_order) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	ret = folio_check_splittable(folio, new_order, split_type);
 	if (ret) {
 		VM_WARN_ONCE(ret == -EINVAL, "Tried to split an unsplittable folio");
-		goto out_no_memcg;
+		goto out;
 	}
 
-	/*
-	 * switch to folio's memcg as xarray node allocation can happen and
-	 * needs to charge to it.
-	 */
-	memcg = get_mem_cgroup_from_folio(folio);
-	old_memcg = set_active_memcg(memcg);
-
 	if (is_anon)
 		ret = __folio_freeze_split_anon(folio, new_order, split_at,
 						true, list, split_type);
@@ -4351,10 +4356,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	[flat|nested] 37+ messages in thread

* [PATCH v4 14/17] mm/huge_memory: drop the unused do_lru argument of the file split helper
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (12 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 13/17] mm/huge_memory: move memcg switch into the file " Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-09  8:22   ` Kiryl Shutsemau
  2026-09-07 18:12 ` [PATCH v4 15/17] mm/huge_memory: clean up after-split folio freeing in __folio_split Kairui Song via B4 Relay
                   ` (2 subsequent siblings)
  16 siblings, 1 reply; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song, Kairui Song

From: Kairui Song <kasong@tencent.com>

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

Reviewed-by: Zi Yan <ziy@nvidia.com>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ccd48cd5f94b..6ce58a5d93d8 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4107,8 +4107,7 @@ static int __folio_freeze_split_anon(struct folio *folio,
 
 static int __folio_freeze_split_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 list_head *list, enum split_type split_type)
 {
 	struct address_space *mapping = folio->mapping;
 	XA_STATE(xas, &mapping->i_pages, folio->index);
@@ -4202,9 +4201,7 @@ static int __folio_freeze_split_file(struct folio *folio,
 	}
 
 	/* 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);
 
@@ -4226,8 +4223,7 @@ static int __folio_freeze_split_file(struct folio *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);
+		lru_add_split_folio(folio, new_folio, lruvec, list);
 
 		/* Add the new folio to the page cache. */
 		if (new_folio->index < end) {
@@ -4253,9 +4249,7 @@ static int __folio_freeze_split_file(struct folio *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);
+	lruvec_unlock(lruvec);
 fail:
 	/*
 	 * If we want to use try_to_migrate() on file in unmap_folio,
@@ -4335,7 +4329,7 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 						true, list, split_type);
 	else
 		ret = __folio_freeze_split_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	[flat|nested] 37+ messages in thread

* [PATCH v4 15/17] mm/huge_memory: clean up after-split folio freeing in __folio_split
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (13 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 14/17] mm/huge_memory: drop the unused do_lru argument of " Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-09  8:27   ` Kiryl Shutsemau
  2026-09-07 18:12 ` [PATCH v4 16/17] mm/huge_memory: count only swap cache refs in anon folio split Kairui Song via B4 Relay
  2026-09-07 18:12 ` [PATCH v4 17/17] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio Kairui Song via B4 Relay
  16 siblings, 1 reply; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	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() 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>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Signed-off-by: Kairui Song <kasong@tencent.com>
---
 mm/huge_memory.c | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 6ce58a5d93d8..f2862556d715 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -4300,7 +4300,8 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
-	bool is_anon = folio_test_anon(folio);
+	const bool is_anon = folio_test_anon(folio);
+	const bool is_swapcache = folio_test_swapcache(folio);
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
 	int ret;
@@ -4340,14 +4341,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:
@@ -4374,7 +4377,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	[flat|nested] 37+ messages in thread

* [PATCH v4 16/17] mm/huge_memory: count only swap cache refs in anon folio split
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (14 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 15/17] mm/huge_memory: clean up after-split folio freeing in __folio_split Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-09  8:31   ` Kiryl Shutsemau
  2026-09-07 18:12 ` [PATCH v4 17/17] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio Kairui Song via B4 Relay
  16 siblings, 1 reply; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song, Kairui Song

From: Kairui Song <kasong@tencent.com>

Only __folio_freeze_split_anon() 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.

No feature change.

Reviewed-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.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 f2862556d715..661b1c747c33 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3989,10 +3989,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);
 }
@@ -4034,7 +4034,7 @@ static int __folio_freeze_split_anon(struct folio *folio,
 
 	local_irq_disable();
 
-	if (!folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
+	if (!folio_ref_freeze(folio, folio_swapcache_ref_count(folio) + 1)) {
 		ret = -EAGAIN;
 		goto out_no_split;
 	}
@@ -4075,7 +4075,7 @@ static int __folio_freeze_split_anon(struct folio *folio,
 		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)
@@ -4083,7 +4083,7 @@ static int __folio_freeze_split_anon(struct folio *folio,
 	}
 
 	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);
@@ -4112,6 +4112,7 @@ static int __folio_freeze_split_file(struct folio *folio,
 	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;
@@ -4182,22 +4183,16 @@ static int __folio_freeze_split_file(struct folio *folio,
 		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 */
@@ -4221,7 +4216,7 @@ static int __folio_freeze_split_file(struct folio *folio,
 		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);
 
@@ -4248,7 +4243,7 @@ static int __folio_freeze_split_file(struct folio *folio,
 	 * 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	[flat|nested] 37+ messages in thread

* [PATCH v4 17/17] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio
  2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
                   ` (15 preceding siblings ...)
  2026-09-07 18:12 ` [PATCH v4 16/17] mm/huge_memory: count only swap cache refs in anon folio split Kairui Song via B4 Relay
@ 2026-09-07 18:12 ` Kairui Song via B4 Relay
  2026-09-09  8:32   ` Kiryl Shutsemau
  16 siblings, 1 reply; 37+ messages in thread
From: Kairui Song via B4 Relay @ 2026-09-07 18:12 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	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.

Reviewed-by: Zi Yan <ziy@nvidia.com>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
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 661b1c747c33..c98756bcc5ec 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3825,7 +3825,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)
  *
  *
@@ -3858,7 +3857,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);
 	int old_order = folio_order(folio);
@@ -3882,7 +3881,7 @@ static int __split_frozen_folio(struct folio *folio, int new_order,
 		if (is_anon && split_order == 1)
 			continue;
 
-		if (mapping) {
+		if (xas) {
 			/*
 			 * uniform split has xas_split_alloc() called before
 			 * irq is disabled to allocate enough memory, whereas
@@ -4060,8 +4059,7 @@ static int __folio_freeze_split_anon(struct folio *folio,
 	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 after-split folios and put them back to the right
@@ -4197,8 +4195,7 @@ static int __folio_freeze_split_file(struct folio *folio,
 
 	/* 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	[flat|nested] 37+ messages in thread

* Re: [PATCH v4 09/17] mm/huge_memory: rename remap_page() to remap_folio()
  2026-09-07 18:12 ` [PATCH v4 09/17] mm/huge_memory: rename remap_page() to remap_folio() Kairui Song via B4 Relay
@ 2026-09-07 19:48   ` Zi Yan
  2026-09-08 15:17   ` Kiryl Shutsemau
  1 sibling, 0 replies; 37+ messages in thread
From: Zi Yan @ 2026-09-07 19:48 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song

On Mon Sep 7, 2026 at 2:12 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> remap_page() now only has one caller, __folio_freeze_split_anon(), so
> rename it to remap_folio() to match the sibling helper unmap_folio().
>
> Also add a VM_WARN_ON_FOLIO() documenting that remap_folio() is only
> ever called for anon folios: unmap_folio() currently leaves file
> folios unmapped after the split, so they need no remapping.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 17 ++++++++++++-----
>  1 file changed, 12 insertions(+), 5 deletions(-)
>
LGTM.

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

-- 
Best Regards,
Yan, Zi


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

* Re: [PATCH v4 10/17] mm/huge_memory: move the racy refcount check into unmap_folio()
  2026-09-07 18:12 ` [PATCH v4 10/17] mm/huge_memory: move the racy refcount check into unmap_folio() Kairui Song via B4 Relay
@ 2026-09-07 19:52   ` Zi Yan
  2026-09-08 15:20   ` Kiryl Shutsemau
  1 sibling, 0 replies; 37+ messages in thread
From: Zi Yan @ 2026-09-07 19:52 UTC (permalink / raw)
  To: kasong, linux-mm
  Cc: linux-kernel, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
	Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
	Lance Yang, Usama Arif, Vlastimil Babka, Mike Rapoport,
	Suren Baghdasaryan, Michal Hocko, Chris Li, Kemeng Shi,
	Nhat Pham, Baoquan He, Barry Song, Youngjun Park, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song

On Mon Sep 7, 2026 at 2:12 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> The check only exists to avoid the expensive PMD-splitting unmap of a
> folio that cannot be split anyway.  Move it from __folio_split() into
> unmap_folio(), right before the PMD split, so both the anon and file
> split helpers get the early check without repeating it.
>
> unmap_folio() now returns -EAGAIN if the check fails and the split
> helpers propagate the error.  folio_split_unmapped() drops its own
> copy of the check: it works on already unmapped folios and the
> definitive folio_ref_freeze() in __folio_freeze_split_anon() still
> catches unexpected references.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 29 +++++++++++++----------------
>  1 file changed, 13 insertions(+), 16 deletions(-)
>
LGTM.

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


-- 
Best Regards,
Yan, Zi


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

* Re: [PATCH v4 11/17] mm/huge_memory: move filemap management into the file split helper
  2026-09-07 18:12 ` [PATCH v4 11/17] mm/huge_memory: move filemap management into the file split helper Kairui Song via B4 Relay
@ 2026-09-07 19:57   ` Zi Yan
  2026-09-08 15:25   ` Kiryl Shutsemau
  1 sibling, 0 replies; 37+ messages in thread
From: Zi Yan @ 2026-09-07 19: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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song

On Mon Sep 7, 2026 at 2:12 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> Only file split needs the filemap and xarray handling and related
> variables.  Move them out of __folio_split() into the file helper so
> the helper is self-contained, and simplify the parameters.
>
> No functional change.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 102 ++++++++++++++++++++++++-------------------------------
>  1 file changed, 44 insertions(+), 58 deletions(-)
>
LGTM.

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


-- 
Best Regards,
Yan, Zi


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

* Re: [PATCH v4 12/17] mm/huge_memory: move anon_vma handling into the anon split helper
  2026-09-07 18:12 ` [PATCH v4 12/17] mm/huge_memory: move anon_vma handling into the anon " Kairui Song via B4 Relay
@ 2026-09-07 20:39   ` Zi Yan
  2026-09-08  6:32     ` Kairui Song
  2026-09-08 15:56   ` Kiryl Shutsemau
  1 sibling, 1 reply; 37+ messages in thread
From: Zi Yan @ 2026-09-07 20:39 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song

On Mon Sep 7, 2026 at 2:12 PM EDT, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
>
> Only anon split needs the anon_vma, and it only needs it to unmap and
> remap.  Move the folio_get_anon_vma()/anon_vma_lock_write() pair out of
> __folio_split() into the anon helper next to the folio_mapped()
> check that already gates unmap_folio().
>
> This makes the anon_vma conditional on folio_mapped(), which is a
> behaviour change but should be fine.  folio_get_anon_vma() returns
> NULL whenever !folio_mapped(), so an anon folio with
> folio_mapcount() == 0 used to get -EBUSY from split_huge_page() and is
> now split instead.  The realistic case is a THP that has been fully
> swapped out and is still in the swap cache: swap PTEs do not contribute
> mapcount, so it is !folio_mapped() but still alive.
>
> That should be safe and right to have because:
>
> - folio_ref_freeze() below still rejects a folio that picked up any
>   reference, a mapping or a GUP pin, in the meantime.
>
> - A parallel split is excluded by the folio lock.  The anon_vma
>   write lock was added to serialize split in commit 062f1af2170a
>   ("mm: thp: acquire the anon_vma rwsem for write during split"), when
>   split_huge_page() did not hold the folio lock throughout. commit
>   e9b61f19858a ("thp: reintroduce split_huge_page()") later made the
>   folio lock a caller requirement and added the folio_ref_freeze()
>   scheme, so that has been covered ever since.
>
> - Unmapped path is already exercised by folio_split_unmapped(),
>   and the swap cache split already runs well for a partially
>   swapped-out mapped THP.
>
> - folio_get_anon_vma() and folio_lock_anon_vma_read() both bail out

You mean folio_lock_anon_vma_read() called by others? Since there is no
folio_lock_anon_vma_read() in folio split functions.

>   on !folio_mapped() before taking the anon_vma lock, so there is
>   nothing to lock against.
>
> For mapped folios the anon_vma write lock is now released before
> __folio_split() unlocks the after-split sub-folios, where previously it
> was held across that loop, that window is harmless as the sub-folios
> stay folio-locked and ref pinned.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
>  mm/huge_memory.c | 51 ++++++++++++++++++++++++---------------------------
>  1 file changed, 24 insertions(+), 27 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index c4910c0b6018..53614b875794 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -4005,16 +4005,31 @@ static int __folio_freeze_split_anon(struct folio *folio,
>  	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 lruvec *lruvec;
> -	bool need_remap = false;
>  	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.
> +	 *
> +	 * An unmapped folio needs none of this: folio_get_anon_vma() and

I guess you mean an unmapped folio does not need a ref or a lock on
anon_vma. It is better to be explicit about them.

> +	 * folio_lock_anon_vma_read() both bail out on !folio_mapped()

Mentioning folio_lock_anon_vma_read() is confusing since folio split
does not call it.

> +	 * before taking the lock, and folio_ref_freeze() below still
> +	 * rejects a folio that picked up a reference meanwhile. Note
> +	 * a swapped-out THP counts as unmapped here as swap PTEs do
> +	 * not contribute mapcount, and they are splittable.
> +	 */
>  	if (folio_mapped(folio)) {
> -		need_remap = true;
> +		anon_vma = folio_get_anon_vma(folio);
> +		if (!anon_vma)
> +			return -EBUSY;
> +		anon_vma_lock_write(anon_vma);
>  		ret = unmap_folio(folio);
>  		if (ret)
> -			return ret;
> +			goto out_unlock;
>  	}
>  
>  	local_irq_disable();

Otherwise, LGTM. With the comit message and comments addressed, feel
free to add

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


-- 
Best Regards,
Yan, Zi


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

* Re: [PATCH v4 12/17] mm/huge_memory: move anon_vma handling into the anon split helper
  2026-09-07 20:39   ` Zi Yan
@ 2026-09-08  6:32     ` Kairui Song
  0 siblings, 0 replies; 37+ messages in thread
From: Kairui Song @ 2026-09-08  6:32 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,
	Yeoreum Yun, Kiryl Shutsemau (Meta),
	Shivam Kalra

On Tue, Sep 8, 2026 at 4:40 AM Zi Yan <ziy@nvidia.com> wrote:
>
> On Mon Sep 7, 2026 at 2:12 PM EDT, Kairui Song via B4 Relay wrote:
> > From: Kairui Song <kasong@tencent.com>
> >
> > Only anon split needs the anon_vma, and it only needs it to unmap and
> > remap.  Move the folio_get_anon_vma()/anon_vma_lock_write() pair out of
> > __folio_split() into the anon helper next to the folio_mapped()
> > check that already gates unmap_folio().
> >
> > This makes the anon_vma conditional on folio_mapped(), which is a
> > behaviour change but should be fine.  folio_get_anon_vma() returns
> > NULL whenever !folio_mapped(), so an anon folio with
> > folio_mapcount() == 0 used to get -EBUSY from split_huge_page() and is
> > now split instead.  The realistic case is a THP that has been fully
> > swapped out and is still in the swap cache: swap PTEs do not contribute
> > mapcount, so it is !folio_mapped() but still alive.
> >
> > That should be safe and right to have because:
> >
> > - folio_ref_freeze() below still rejects a folio that picked up any
> >   reference, a mapping or a GUP pin, in the meantime.
> >
> > - A parallel split is excluded by the folio lock.  The anon_vma
> >   write lock was added to serialize split in commit 062f1af2170a
> >   ("mm: thp: acquire the anon_vma rwsem for write during split"), when
> >   split_huge_page() did not hold the folio lock throughout. commit
> >   e9b61f19858a ("thp: reintroduce split_huge_page()") later made the
> >   folio lock a caller requirement and added the folio_ref_freeze()
> >   scheme, so that has been covered ever since.
> >
> > - Unmapped path is already exercised by folio_split_unmapped(),
> >   and the swap cache split already runs well for a partially
> >   swapped-out mapped THP.
> >
> > - folio_get_anon_vma() and folio_lock_anon_vma_read() both bail out
>
> You mean folio_lock_anon_vma_read() called by others? Since there is no
> folio_lock_anon_vma_read() in folio split functions.

Oh, the original comment mentioned it, so I also looked into that.

>
> >   on !folio_mapped() before taking the anon_vma lock, so there is
> >   nothing to lock against.
> >
> > For mapped folios the anon_vma write lock is now released before
> > __folio_split() unlocks the after-split sub-folios, where previously it
> > was held across that loop, that window is harmless as the sub-folios
> > stay folio-locked and ref pinned.
> >
> > Signed-off-by: Kairui Song <kasong@tencent.com>
> > ---
> >  mm/huge_memory.c | 51 ++++++++++++++++++++++++---------------------------
> >  1 file changed, 24 insertions(+), 27 deletions(-)
> >
> > diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> > index c4910c0b6018..53614b875794 100644
> > --- a/mm/huge_memory.c
> > +++ b/mm/huge_memory.c
> > @@ -4005,16 +4005,31 @@ static int __folio_freeze_split_anon(struct folio *folio,
> >       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 lruvec *lruvec;
> > -     bool need_remap = false;
> >       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.
> > +      *
> > +      * An unmapped folio needs none of this: folio_get_anon_vma() and
>
> I guess you mean an unmapped folio does not need a ref or a lock on
> anon_vma. It is better to be explicit about them.
>
> > +      * folio_lock_anon_vma_read() both bail out on !folio_mapped()
>
> Mentioning folio_lock_anon_vma_read() is confusing since folio split
> does not call it.

Right, I actually just copied the original comment, and I'm a little
bit confused about it too, I can try to improve it while I'm at it.

> > +      * before taking the lock, and folio_ref_freeze() below still
> > +      * rejects a folio that picked up a reference meanwhile. Note
> > +      * a swapped-out THP counts as unmapped here as swap PTEs do
> > +      * not contribute mapcount, and they are splittable.
> > +      */

So how about rewrite this comment chunk as follows:

      /*
       * 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 on it and then lock
       * it for write, letting unmap_folio() walk the rmap with
       * TTU_RMAP_LOCKED.
       *
       * An unmapped folio needs neither the reference nor the lock:
       * no unmap walk happens for a folio without mappings, so there
       * is no anon_vma to pin and nobody to lock against, and the
       * folio_ref_freeze() below still rejects any reference picked
       * up meanwhile. Note that a fully swapped-out THP still in
       * the swap cache counts as unmapped here, as swap PTEs do not
       * contribute to the mapcount, it is splittable just fine.
       */

> >       if (folio_mapped(folio)) {
> > -             need_remap = true;
> > +             anon_vma = folio_get_anon_vma(folio);
> > +             if (!anon_vma)
> > +                     return -EBUSY;
> > +             anon_vma_lock_write(anon_vma);
> >               ret = unmap_folio(folio);
> >               if (ret)
> > -                     return ret;
> > +                     goto out_unlock;
> >       }
> >
> >       local_irq_disable();
>
> Otherwise, LGTM. With the comit message and comments addressed, feel
> free to add
>
> Reviewed-by: Zi Yan <ziy@nvidia.com>

Thanks for the review!

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

* Re: [PATCH v4 04/17] mm/huge_memory: split the routine for splitting anon and file folio
  2026-09-07 18:12 ` [PATCH v4 04/17] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
@ 2026-09-08 15:11   ` Kiryl Shutsemau
  0 siblings, 0 replies; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-08 15:11 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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:08AM +0800, 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.
> 
> The zone_device_private_split_cb() calls are only kept in the anon
> variant, as device private folios can only back anonymous memory, and
> add a VM_WARN_ON_ONCE_FOLIO() at the entry of the file variant.
> 
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.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] 37+ messages in thread

* Re: [PATCH v4 08/17] mm/huge_memory: move unmap and remap into the split helpers
  2026-09-07 18:12 ` [PATCH v4 08/17] mm/huge_memory: move unmap and remap into the split helpers Kairui Song via B4 Relay
@ 2026-09-08 15:13   ` Kiryl Shutsemau
  0 siblings, 0 replies; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-08 15: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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:12AM +0800, 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.
> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.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] 37+ messages in thread

* Re: [PATCH v4 09/17] mm/huge_memory: rename remap_page() to remap_folio()
  2026-09-07 18:12 ` [PATCH v4 09/17] mm/huge_memory: rename remap_page() to remap_folio() Kairui Song via B4 Relay
  2026-09-07 19:48   ` Zi Yan
@ 2026-09-08 15:17   ` Kiryl Shutsemau
  2026-09-09 17:37     ` David Hildenbrand (Arm)
  1 sibling, 1 reply; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-08 15:17 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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:13AM +0800, Kairui Song via B4 Relay wrote:
> @@ -3636,10 +3636,17 @@ bool unmap_huge_pmd_locked(struct vm_area_struct *vma, unsigned long addr,
>  	return __discard_anon_folio_pmd_locked(vma, addr, pmdp, folio);
>  }
>  
> -static void remap_page(struct folio *folio, unsigned long nr, int flags)
> +static void remap_folio(struct folio *folio, unsigned long nr, int flags)

You opted out of indicating that the function target anon pages as David
suggested? remap_anon_folio() seems to be fitting better.

But I don't care too much:

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

-- 
  Kiryl Shutsemau / Kirill A. Shutemov

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

* Re: [PATCH v4 10/17] mm/huge_memory: move the racy refcount check into unmap_folio()
  2026-09-07 18:12 ` [PATCH v4 10/17] mm/huge_memory: move the racy refcount check into unmap_folio() Kairui Song via B4 Relay
  2026-09-07 19:52   ` Zi Yan
@ 2026-09-08 15:20   ` Kiryl Shutsemau
  1 sibling, 0 replies; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-08 15:20 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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:14AM +0800, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
> 
> The check only exists to avoid the expensive PMD-splitting unmap of a
> folio that cannot be split anyway.  Move it from __folio_split() into
> unmap_folio(), right before the PMD split, so both the anon and file
> split helpers get the early check without repeating it.
> 
> unmap_folio() now returns -EAGAIN if the check fails and the split
> helpers propagate the error.  folio_split_unmapped() drops its own
> copy of the check: it works on already unmapped folios and the
> definitive folio_ref_freeze() in __folio_freeze_split_anon() still
> catches unexpected references.
> 
> 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] 37+ messages in thread

* Re: [PATCH v4 11/17] mm/huge_memory: move filemap management into the file split helper
  2026-09-07 18:12 ` [PATCH v4 11/17] mm/huge_memory: move filemap management into the file split helper Kairui Song via B4 Relay
  2026-09-07 19:57   ` Zi Yan
@ 2026-09-08 15:25   ` Kiryl Shutsemau
  1 sibling, 0 replies; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-08 15:25 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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:15AM +0800, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
> 
> Only file split needs the filemap and xarray handling and related
> variables.  Move them out of __folio_split() into the file helper so
> the helper is self-contained, and simplify the parameters.
> 
> No functional change.
> 
> 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] 37+ messages in thread

* Re: [PATCH v4 12/17] mm/huge_memory: move anon_vma handling into the anon split helper
  2026-09-07 18:12 ` [PATCH v4 12/17] mm/huge_memory: move anon_vma handling into the anon " Kairui Song via B4 Relay
  2026-09-07 20:39   ` Zi Yan
@ 2026-09-08 15:56   ` Kiryl Shutsemau
  1 sibling, 0 replies; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-08 15:56 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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:16AM +0800, Kairui Song via B4 Relay wrote:
> +	/*
> +	 * 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.
> +	 *
> +	 * An unmapped folio needs none of this: folio_get_anon_vma() and
> +	 * folio_lock_anon_vma_read() both bail out on !folio_mapped()
> +	 * before taking the lock, and folio_ref_freeze() below still
> +	 * rejects a folio that picked up a reference meanwhile. Note
> +	 * a swapped-out THP counts as unmapped here as swap PTEs do
> +	 * not contribute mapcount, and they are splittable.
> +	 */
>  	if (folio_mapped(folio)) {
> -		need_remap = true;
> +		anon_vma = folio_get_anon_vma(folio);
> +		if (!anon_vma)
> +			return -EBUSY;
> +		anon_vma_lock_write(anon_vma);
>  		ret = unmap_folio(folio);
>  		if (ret)
> -			return ret;
> +			goto out_unlock;
>  	}

Hm. Nothing serializes folio_mapped() here. I believe it is fine, but the
reasoning deserves a comment, since it is what the whole change rests on.
Something along the lines of:

       * folio_mapped() is not stable here, but it can only change in
       * one direction while the folio is locked. The mapcount can drop
       * to zero at any time, zap_pte_range() takes no folio lock. It
       * cannot go up: swapin, migration and uffd move all lock the folio
       * before mapping it, and fork only copies PTEs that already exist.
       *
       * So if we see the folio mapped, the worst case is an empty rmap
       * walk. If we see it unmapped, it stays unmapped. Anything else
       * needs a reference first and folio_ref_freeze() below catches it.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov

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

* Re: [PATCH v4 13/17] mm/huge_memory: move memcg switch into the file split helper
  2026-09-07 18:12 ` [PATCH v4 13/17] mm/huge_memory: move memcg switch into the file " Kairui Song via B4 Relay
@ 2026-09-09  8:21   ` Kiryl Shutsemau
  0 siblings, 0 replies; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-09  8:21 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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:17AM +0800, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
> 
> The xarray node allocations in __folio_freeze_split_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>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.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] 37+ messages in thread

* Re: [PATCH v4 14/17] mm/huge_memory: drop the unused do_lru argument of the file split helper
  2026-09-07 18:12 ` [PATCH v4 14/17] mm/huge_memory: drop the unused do_lru argument of " Kairui Song via B4 Relay
@ 2026-09-09  8:22   ` Kiryl Shutsemau
  0 siblings, 0 replies; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-09  8:22 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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:18AM +0800, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
> 
> The only caller of __folio_freeze_split_file() always passes do_lru as
> true, so the argument and the branches gated on it are dead code.
> Drop it.
> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Acked-by: David Hildenbrand (Arm) <david@kernel.org>
> Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.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] 37+ messages in thread

* Re: [PATCH v4 15/17] mm/huge_memory: clean up after-split folio freeing in __folio_split
  2026-09-07 18:12 ` [PATCH v4 15/17] mm/huge_memory: clean up after-split folio freeing in __folio_split Kairui Song via B4 Relay
@ 2026-09-09  8:27   ` Kiryl Shutsemau
  0 siblings, 0 replies; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-09  8:27 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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:19AM +0800, 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,

Are they mapped? remap_page() is above the point in function. It worth
rewording.

Code looks okay.

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

-- 
  Kiryl Shutsemau / Kirill A. Shutemov

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

* Re: [PATCH v4 16/17] mm/huge_memory: count only swap cache refs in anon folio split
  2026-09-07 18:12 ` [PATCH v4 16/17] mm/huge_memory: count only swap cache refs in anon folio split Kairui Song via B4 Relay
@ 2026-09-09  8:31   ` Kiryl Shutsemau
  0 siblings, 0 replies; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-09  8:31 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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:20AM +0800, Kairui Song via B4 Relay wrote:
> From: Kairui Song <kasong@tencent.com>
> 
> Only __folio_freeze_split_anon() 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.
> 
> No feature change.
> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.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] 37+ messages in thread

* Re: [PATCH v4 17/17] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio
  2026-09-07 18:12 ` [PATCH v4 17/17] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio Kairui Song via B4 Relay
@ 2026-09-09  8:32   ` Kiryl Shutsemau
  0 siblings, 0 replies; 37+ messages in thread
From: Kiryl Shutsemau @ 2026-09-09  8:32 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, Yeoreum Yun, Shivam Kalra, Kairui Song

On Tue, Sep 08, 2026 at 02:12:21AM +0800, 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.
> 
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.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] 37+ messages in thread

* Re: [PATCH v4 05/17] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio()
  2026-09-07 18:12 ` [PATCH v4 05/17] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio() Kairui Song via B4 Relay
@ 2026-09-09 17:36   ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 37+ messages in thread
From: David Hildenbrand (Arm) @ 2026-09-09 17: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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song

On 9/7/26 20:12, 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.
> 
> Also add a VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio)) to self document
> that frozen implies unmapped.
> 
> Suggested-by: Zi Yan <ziy@nvidia.com>
> Reviewed-by: Zi Yan <ziy@nvidia.com>
> Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
> Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David

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

* Re: [PATCH v4 09/17] mm/huge_memory: rename remap_page() to remap_folio()
  2026-09-08 15:17   ` Kiryl Shutsemau
@ 2026-09-09 17:37     ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 37+ messages in thread
From: David Hildenbrand (Arm) @ 2026-09-09 17:37 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, Yeoreum Yun,
	Shivam Kalra, Kairui Song

On 9/8/26 17:17, Kiryl Shutsemau wrote:
> On Tue, Sep 08, 2026 at 02:12:13AM +0800, Kairui Song via B4 Relay wrote:
>> @@ -3636,10 +3636,17 @@ bool unmap_huge_pmd_locked(struct vm_area_struct *vma, unsigned long addr,
>>  	return __discard_anon_folio_pmd_locked(vma, addr, pmdp, folio);
>>  }
>>  
>> -static void remap_page(struct folio *folio, unsigned long nr, int flags)
>> +static void remap_folio(struct folio *folio, unsigned long nr, int flags)
> 
> You opted out of indicating that the function target anon pages as David
> suggested? remap_anon_folio() seems to be fitting better.

Yeah, let's use that unless there is a good reason not to.

-- 
Cheers,

David

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

* Re: [PATCH v4 06/17] mm/huge_memory: consolidate irq and locking for folio split
  2026-09-07 18:12 ` [PATCH v4 06/17] mm/huge_memory: consolidate irq and locking for folio split Kairui Song via B4 Relay
@ 2026-09-09 17:39   ` David Hildenbrand (Arm)
  0 siblings, 0 replies; 37+ messages in thread
From: David Hildenbrand (Arm) @ 2026-09-09 17:39 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, Yeoreum Yun,
	Kiryl Shutsemau (Meta),
	Shivam Kalra, Kairui Song

On 9/7/26 20:12, 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>
> Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
> Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---

[...]

>  	if (nr_shmem_dropped)
>  		shmem_uncharge(mapping->host, nr_shmem_dropped);
> @@ -4390,8 +4391,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);
> @@ -4400,11 +4399,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_anon(folio, new_order, &folio->page,
> -					false, NULL, SPLIT_TYPE_UNIFORM);
> -	local_irq_enable();
> -	return ret;
> +	return __folio_freeze_split_anon(folio, new_order, &folio->page,
> +					 false, NULL, SPLIT_TYPE_UNIFORM);

Yeah, that bit looks especially nice.

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

-- 
Cheers,

David

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

end of thread, other threads:[~2026-09-09 17:39 UTC | newest]

Thread overview: 37+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-07 18:12 [PATCH v4 00/17] mm/huge_memory: clean up and decouple the anon and file split helpers Kairui Song via B4 Relay
2026-09-07 18:12 ` [PATCH v4 01/17] mm/swap: fix off-by-one in swap cache replace sanity check Kairui Song via B4 Relay
2026-09-07 18:12 ` [PATCH v4 02/17] mm/huge_memory: fix rejection of swap cache folios with a mapping Kairui Song via B4 Relay
2026-09-07 18:12 ` [PATCH v4 03/17] mm/huge_memory: invert folio_ref_freeze() check to reduce indentation Kairui Song via B4 Relay
2026-09-07 18:12 ` [PATCH v4 04/17] mm/huge_memory: split the routine for splitting anon and file folio Kairui Song via B4 Relay
2026-09-08 15:11   ` Kiryl Shutsemau
2026-09-07 18:12 ` [PATCH v4 05/17] mm/huge_memory: rename __split_unmapped_folio() to __split_frozen_folio() Kairui Song via B4 Relay
2026-09-09 17:36   ` David Hildenbrand (Arm)
2026-09-07 18:12 ` [PATCH v4 06/17] mm/huge_memory: consolidate irq and locking for folio split Kairui Song via B4 Relay
2026-09-09 17:39   ` David Hildenbrand (Arm)
2026-09-07 18:12 ` [PATCH v4 07/17] mm/huge_memory: move EOF trimming into the file split helper Kairui Song via B4 Relay
2026-09-07 18:12 ` [PATCH v4 08/17] mm/huge_memory: move unmap and remap into the split helpers Kairui Song via B4 Relay
2026-09-08 15:13   ` Kiryl Shutsemau
2026-09-07 18:12 ` [PATCH v4 09/17] mm/huge_memory: rename remap_page() to remap_folio() Kairui Song via B4 Relay
2026-09-07 19:48   ` Zi Yan
2026-09-08 15:17   ` Kiryl Shutsemau
2026-09-09 17:37     ` David Hildenbrand (Arm)
2026-09-07 18:12 ` [PATCH v4 10/17] mm/huge_memory: move the racy refcount check into unmap_folio() Kairui Song via B4 Relay
2026-09-07 19:52   ` Zi Yan
2026-09-08 15:20   ` Kiryl Shutsemau
2026-09-07 18:12 ` [PATCH v4 11/17] mm/huge_memory: move filemap management into the file split helper Kairui Song via B4 Relay
2026-09-07 19:57   ` Zi Yan
2026-09-08 15:25   ` Kiryl Shutsemau
2026-09-07 18:12 ` [PATCH v4 12/17] mm/huge_memory: move anon_vma handling into the anon " Kairui Song via B4 Relay
2026-09-07 20:39   ` Zi Yan
2026-09-08  6:32     ` Kairui Song
2026-09-08 15:56   ` Kiryl Shutsemau
2026-09-07 18:12 ` [PATCH v4 13/17] mm/huge_memory: move memcg switch into the file " Kairui Song via B4 Relay
2026-09-09  8:21   ` Kiryl Shutsemau
2026-09-07 18:12 ` [PATCH v4 14/17] mm/huge_memory: drop the unused do_lru argument of " Kairui Song via B4 Relay
2026-09-09  8:22   ` Kiryl Shutsemau
2026-09-07 18:12 ` [PATCH v4 15/17] mm/huge_memory: clean up after-split folio freeing in __folio_split Kairui Song via B4 Relay
2026-09-09  8:27   ` Kiryl Shutsemau
2026-09-07 18:12 ` [PATCH v4 16/17] mm/huge_memory: count only swap cache refs in anon folio split Kairui Song via B4 Relay
2026-09-09  8:31   ` Kiryl Shutsemau
2026-09-07 18:12 ` [PATCH v4 17/17] mm/huge_memory: drop the redundant mapping argument of __split_frozen_folio Kairui Song via B4 Relay
2026-09-09  8:32   ` Kiryl Shutsemau

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

all inboxes | Powered by JetHome®