mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB
@ 2026-11-04  2:34 Muchun Song
  2026-11-04  2:34 ` [PATCH v3 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros Muchun Song
                   ` (12 more replies)
  0 siblings, 13 replies; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:34 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

Hi,

Section-based vmemmap optimization is implemented in sparse-vmemmap.c,
while folio-based optimization currently lives in hugetlb_vmemmap.c.
The latter is tightly coupled to HugeTLB-specific state even though the
underlying page-table remapping operates on one folio at a time.

To make these interfaces easier to maintain, the folio-based remapping
mechanics first need a boundary that is independent of HugeTLB-specific
state and raw address ranges. This will allow the mechanics to be moved
into a common vmemmap optimization layer alongside the section-based
interface.

This series establishes that boundary through three folio-oriented
operations, each with a distinct role:

  - vmemmap_optimize_folio() optimizes the vmemmap backing of a folio;
  - vmemmap_restore_folio() restores the vmemmap backing of a folio; and
  - vmemmap_pteify_folio() converts the vmemmap backing of a folio to
    PTE mappings.

Together, these operations encapsulate the address-range calculation and
remapping mechanics for a folio. HugeTLB wrappers retain policy decisions
such as eligibility, optimized state, batching and TLB flush handling.

Patches 1-8 generalize the underlying vmemmap remapping infrastructure by
removing HugeTLB-specific assumptions and simplifying remap control and
resource handling. Patches 9-12 then establish folio-oriented operations
for optimization, restoration and PTE conversion.

The implementation remains in hugetlb_vmemmap.c for now. A follow-up can
move the folio-based operations into the common vmemmap optimization layer
once the interface boundary is in place.

v3:
- Rebase onto mm/mm-new at 74d73ffcffe0
- Use two tabs for continued function parameters in modified declarations
  (suggested by Mike Rapoport)
- Rework vmemmap page allocation failure cleanup to free temporary pages
  directly while keeping accounting batched (suggested by Mike Rapoport)
- Collect Reviewed-by tags from Mike Rapoport

v2: https://lore.kernel.org/20261102032525.3597743-1-songmuchun@bytedance.com/
- Rebase onto mm/mm-new at d42dfde37af7
- Rename the unified TLB flag to VMEMMAP_DEFER_TLB_FLUSH and clarify that
  it defers only safely batchable flushes (suggested by Qi Zheng)
- Update the vmemmap page-list documentation to describe its input/output
  contract (suggested by Lance Yang and Qi Zheng)
- Use page_address() instead of page_to_virt() when copying a vmemmap
  replacement page
- Collect Reviewed-by tags from Lance Yang and Qi Zheng

v1: https://lore.kernel.org/20261020115306.813483-1-songmuchun@bytedance.com/

Muchun Song (12):
  mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization
    macros
  mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH
  mm: hugetlb_vmemmap: simplify vmemmap head page handling
  mm: hugetlb_vmemmap: generalize vmemmap page allocation
  mm: hugetlb_vmemmap: unify writable vmemmap page preparation
  mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to
    vmemmap_optimize_pte()
  mm: hugetlb_vmemmap: simplify shared tail page handling
  mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free
    helper
  mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented
  mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented
  mm: hugetlb_vmemmap: unify vmemmap range restoration
  mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented

 include/linux/vmemmap-optimization.h |  10 +-
 mm/hugetlb.c                         |   4 +-
 mm/hugetlb_vmemmap.c                 | 375 ++++++++++++---------------
 mm/hugetlb_vmemmap.h                 |  10 +-
 mm/mm_init.c                         |   6 +-
 mm/sparse-vmemmap.c                  |   7 +-
 6 files changed, 183 insertions(+), 229 deletions(-)


base-commit: 74d73ffcffe03804f97e9fb82301dadf75671282
-- 
2.54.0


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

* [PATCH v3 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
@ 2026-11-04  2:34 ` Muchun Song
  2026-11-04  9:15   ` Oscar Salvador (SUSE)
  2026-11-04  2:35 ` [PATCH v3 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH Muchun Song
                   ` (11 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:34 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

Vmemmap optimization is no longer HugeTLB-specific, so the remaining
HugeTLB-local reserve macros are redundant.

Replace them with the generic definitions to remove duplication and keep
the HugeTLB vmemmap code aligned with the common optimization macros.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v3:
- Collect Reviewed-by from Mike Rapoport

v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 include/linux/vmemmap-optimization.h |  4 ++--
 mm/hugetlb.c                         |  4 ++--
 mm/hugetlb_vmemmap.c                 |  6 +++---
 mm/hugetlb_vmemmap.h                 | 10 ++--------
 4 files changed, 9 insertions(+), 15 deletions(-)

diff --git a/include/linux/vmemmap-optimization.h b/include/linux/vmemmap-optimization.h
index fa9e9abd6656..065840f3c241 100644
--- a/include/linux/vmemmap-optimization.h
+++ b/include/linux/vmemmap-optimization.h
@@ -27,8 +27,8 @@
 
 /* The number of retained vmemmap pages with HVO enabled. */
 #define VMEMMAP_OPTIMIZATION_PAGES		1
-#define VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES	\
-	(VMEMMAP_OPTIMIZATION_PAGES * PAGE_SIZE / sizeof(struct page))
+#define VMEMMAP_OPTIMIZATION_SIZE		(VMEMMAP_OPTIMIZATION_PAGES * PAGE_SIZE)
+#define VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES	(VMEMMAP_OPTIMIZATION_SIZE / sizeof(struct page))
 #define VMEMMAP_OPTIMIZATION_MIN_ORDER		(ilog2(VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES) + 1)
 
 #ifdef CONFIG_VMEMMAP_OPTIMIZATION
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 779b1de33c78..c8c5a996e20a 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -3317,7 +3317,7 @@ static void __init prep_and_add_bootmem_folios(struct hstate *h,
 			 * be no contention.
 			 */
 			hugetlb_folio_init_tail_vmemmap(folio, h,
-					HUGETLB_VMEMMAP_RESERVE_PAGES,
+					VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES,
 					pages_per_huge_page(h));
 		}
 		hugetlb_bootmem_init_migratetype(folio, h);
@@ -3357,7 +3357,7 @@ static void __init gather_bootmem_prealloc_node(unsigned long nid)
 		WARN_ON(folio_ref_count(folio) != 1);
 
 		hugetlb_folio_init_vmemmap(folio, h,
-					   HUGETLB_VMEMMAP_RESERVE_PAGES);
+					   VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES);
 		init_new_hugetlb_folio(folio);
 
 		if (vmemmap_optimizable_order(pfn_to_section_compound_order(pfn)))
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index b8ce014ba6e6..4b73d6ec8dce 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -408,7 +408,7 @@ static int __hugetlb_vmemmap_restore_folio(const struct hstate *h,
 	vmemmap_start	= (unsigned long)&folio->page;
 	vmemmap_end	= vmemmap_start + hugetlb_vmemmap_size(h);
 
-	vmemmap_start	+= HUGETLB_VMEMMAP_RESERVE_SIZE;
+	vmemmap_start	+= VMEMMAP_OPTIMIZATION_SIZE;
 
 	/*
 	 * The pages which the vmemmap virtual address range [@vmemmap_start,
@@ -676,8 +676,8 @@ static int __init hugetlb_vmemmap_init(void)
 {
 	const struct hstate *h;
 
-	/* HUGETLB_VMEMMAP_RESERVE_SIZE should cover all used struct pages */
-	BUILD_BUG_ON(__NR_USED_SUBPAGE > HUGETLB_VMEMMAP_RESERVE_PAGES);
+	/* VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES should cover all used struct pages */
+	BUILD_BUG_ON(__NR_USED_SUBPAGE > VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES);
 
 	for_each_hstate(h) {
 		if (hugetlb_vmemmap_optimizable(h)) {
diff --git a/mm/hugetlb_vmemmap.h b/mm/hugetlb_vmemmap.h
index d717d0d10960..d057e8b12bf1 100644
--- a/mm/hugetlb_vmemmap.h
+++ b/mm/hugetlb_vmemmap.h
@@ -9,13 +9,7 @@
 #ifndef _LINUX_HUGETLB_VMEMMAP_H
 #define _LINUX_HUGETLB_VMEMMAP_H
 #include <linux/hugetlb.h>
-
-/*
- * Reserve one vmemmap page, all vmemmap addresses are mapped to it. See
- * Documentation/mm/vmemmap_dedup.rst.
- */
-#define HUGETLB_VMEMMAP_RESERVE_SIZE	PAGE_SIZE
-#define HUGETLB_VMEMMAP_RESERVE_PAGES	(HUGETLB_VMEMMAP_RESERVE_SIZE / sizeof(struct page))
+#include <linux/vmemmap-optimization.h>
 
 #ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
 int hugetlb_vmemmap_restore_folio(const struct hstate *h, struct folio *folio);
@@ -37,7 +31,7 @@ static inline unsigned int hugetlb_vmemmap_size(const struct hstate *h)
  */
 static inline unsigned int hugetlb_vmemmap_optimizable_size(const struct hstate *h)
 {
-	int size = hugetlb_vmemmap_size(h) - HUGETLB_VMEMMAP_RESERVE_SIZE;
+	int size = hugetlb_vmemmap_size(h) - VMEMMAP_OPTIMIZATION_SIZE;
 
 	if (!is_power_of_2(sizeof(struct page)))
 		return 0;
-- 
2.54.0


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

* [PATCH v3 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
  2026-11-04  2:34 ` [PATCH v3 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04  9:22   ` Oscar Salvador (SUSE)
  2026-11-04 11:06   ` Mike Rapoport
  2026-11-04  2:35 ` [PATCH v3 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling Muchun Song
                   ` (10 subsequent siblings)
  12 siblings, 2 replies; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

VMEMMAP_SPLIT_NO_TLB_FLUSH and VMEMMAP_REMAP_NO_TLB_FLUSH do not
describe fully independent choices. A split-only walk can defer the
PMD-split flush to a later global flush, and a remap-only walk can
defer the final remap flush. However, when a PMD split is followed
by PTE remapping, the split flush must happen before the PTEs are
changed, because stale large-page translations must not overlap with
new small-page translations that map different physical pages.

Replace the two flags with a single VMEMMAP_DEFER_TLB_FLUSH flag. The
new name reflects that only safely batchable flushes are deferred. Keep
split-only walks able to defer the flush, but force the split flush
whenever a remap_pte callback is present. Callers that batch HVO work
can still defer the final remap flush and issue their own global flush.

Current callers keep the same flushing behavior.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
---
v2:
- Rename the unified flag to VMEMMAP_DEFER_TLB_FLUSH and update its
  comment (suggested by Qi Zheng)
- Update the commit message for the new flag name
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 4b73d6ec8dce..63e899cfaac3 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -42,10 +42,8 @@ struct vmemmap_remap_walk {
 	struct list_head	*vmemmap_pages;
 
 
-/* Skip the TLB flush when we split the PMD */
-#define VMEMMAP_SPLIT_NO_TLB_FLUSH	BIT(0)
-/* Skip the TLB flush when we remap the PTE */
-#define VMEMMAP_REMAP_NO_TLB_FLUSH	BIT(1)
+/* Defer safely batchable TLB flushes to the caller. */
+#define VMEMMAP_DEFER_TLB_FLUSH	BIT(0)
 	unsigned long		flags;
 };
 
@@ -85,7 +83,12 @@ static int vmemmap_split_pmd(pmd_t *pmd, struct page *head, unsigned long start,
 		/* Make pte visible before pmd. See comment in pmd_install(). */
 		smp_wmb();
 		pmd_populate_kernel(&init_mm, pmd, pgtable);
-		if (!(walk->flags & VMEMMAP_SPLIT_NO_TLB_FLUSH))
+		/*
+		 * Modern MMUs allow overlapping TLB entries with different mapping
+		 * granularities only if they map the same physical address. So
+		 * remapping must not proceed without first flushing the TLB.
+		 */
+		if (walk->remap_pte || !(walk->flags & VMEMMAP_DEFER_TLB_FLUSH))
 			flush_tlb_kernel_range(start, start + PMD_SIZE);
 	} else {
 		pte_free_kernel(&init_mm, pgtable);
@@ -166,7 +169,7 @@ static int vmemmap_remap_range(unsigned long start, unsigned long end,
 	if (ret)
 		return ret;
 
-	if (walk->remap_pte && !(walk->flags & VMEMMAP_REMAP_NO_TLB_FLUSH))
+	if (walk->remap_pte && !(walk->flags & VMEMMAP_DEFER_TLB_FLUSH))
 		flush_tlb_kernel_range(start, end);
 
 	return 0;
@@ -274,7 +277,7 @@ static int vmemmap_remap_split(unsigned long start, unsigned long end)
 {
 	struct vmemmap_remap_walk walk = {
 		.remap_pte	= NULL,
-		.flags		= VMEMMAP_SPLIT_NO_TLB_FLUSH,
+		.flags		= VMEMMAP_DEFER_TLB_FLUSH,
 	};
 
 	return vmemmap_remap_range(start, end, &walk);
@@ -458,7 +461,7 @@ long hugetlb_vmemmap_restore_folios(const struct hstate *h,
 	struct folio *folio, *t_folio;
 	long restored = 0;
 	long ret = 0;
-	unsigned long flags = VMEMMAP_REMAP_NO_TLB_FLUSH;
+	unsigned long flags = VMEMMAP_DEFER_TLB_FLUSH;
 
 	list_for_each_entry_safe(folio, t_folio, folio_list, lru) {
 		if (folio_test_hugetlb_vmemmap_optimized(folio)) {
@@ -516,7 +519,7 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
 
 	/*
 	 * Very Subtle
-	 * If VMEMMAP_REMAP_NO_TLB_FLUSH is set, TLB flushing is not performed
+	 * If VMEMMAP_DEFER_TLB_FLUSH is set, TLB flushing is not performed
 	 * immediately after remapping.  As a result, subsequent accesses
 	 * and modifications to struct pages associated with the hugetlb
 	 * page could be to the OLD struct pages.  Set the vmemmap optimized
@@ -595,7 +598,7 @@ void hugetlb_vmemmap_optimize_folios(struct hstate *h, struct list_head *folio_l
 	struct folio *folio;
 	unsigned long nr_to_optimize = 0;
 	LIST_HEAD(vmemmap_pages);
-	unsigned long flags = VMEMMAP_REMAP_NO_TLB_FLUSH;
+	unsigned long flags = VMEMMAP_DEFER_TLB_FLUSH;
 
 	if (list_empty(folio_list))
 		return;
-- 
2.54.0


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

* [PATCH v3 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
  2026-11-04  2:34 ` [PATCH v3 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros Muchun Song
  2026-11-04  2:35 ` [PATCH v3 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04  9:40   ` Oscar Salvador (SUSE)
  2026-11-04  2:35 ` [PATCH v3 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation Muchun Song
                   ` (9 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

The new head page is already tracked on the vmemmap page list before
remapping starts.  Passing it separately gives the remap walker two
ways to refer to the same page and unnecessarily complicates its
interface.

Use the page list as the single handoff mechanism for the head page,
removing the redundant state from the walker.

No functional change intended.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v3:
- Collect Reviewed-by from Mike Rapoport

v2:
- Remove outdated head-page wording from vmemmap_remap_free() kernel-doc
  (suggested by Lance Yang)
- Document the vmemmap page list contract (suggested by Qi Zheng)
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 30 +++++++++++++-----------------
 1 file changed, 13 insertions(+), 17 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 63e899cfaac3..0e5b2b182d1b 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -25,10 +25,9 @@
  *
  * @remap_pte:		called for each lowest-level entry (PTE).
  * @nr_walked:		the number of walked pte.
- * @vmemmap_head:	the page to be installed as first in the vmemmap range
  * @vmemmap_tail:	the page to be installed as non-first in the vmemmap range
- * @vmemmap_pages:	the list head of the vmemmap pages that can be freed
- *			or is mapped from.
+ * @vmemmap_pages:	list supplying replacement pages for the remap and
+ *			receiving displaced backing pages for the caller to free.
  * @flags:		used to modify behavior in vmemmap page table walking
  *			operations.
  */
@@ -37,7 +36,6 @@ struct vmemmap_remap_walk {
 					     struct vmemmap_remap_walk *walk);
 
 	unsigned long		nr_walked;
-	struct page		*vmemmap_head;
 	struct page		*vmemmap_tail;
 	struct list_head	*vmemmap_pages;
 
@@ -208,10 +206,13 @@ static void vmemmap_remap_pte(pte_t *pte, unsigned long addr,
 	pte_t entry;
 
 	/* Remapping the head page requires r/w */
-	if (unlikely(walk->nr_walked == 0 && walk->vmemmap_head)) {
+	if (unlikely(walk->nr_walked == 0)) {
+		struct page *head;
+
 		VM_WARN_ON_ONCE(!PageHead((const struct page *)addr));
 
-		list_del(&walk->vmemmap_head->lru);
+		head = list_first_entry(walk->vmemmap_pages, struct page, lru);
+		list_del(&head->lru);
 
 		/*
 		 * Makes sure that preceding stores to the page contents from
@@ -220,7 +221,7 @@ static void vmemmap_remap_pte(pte_t *pte, unsigned long addr,
 		 */
 		smp_wmb();
 
-		entry = mk_pte(walk->vmemmap_head, PAGE_KERNEL);
+		entry = mk_pte(head, PAGE_KERNEL);
 	} else {
 		VM_WARN_ON_ONCE(!PageTail((const struct page *)addr));
 
@@ -284,23 +285,20 @@ static int vmemmap_remap_split(unsigned long start, unsigned long end)
 }
 
 /**
- * vmemmap_remap_free - remap the vmemmap virtual address range [@start, @end)
- *			to use @vmemmap_head/tail, then free vmemmap which
- *			the range are mapped to.
+ * vmemmap_remap_free - remap a vmemmap range and collect old backing pages
  * @start:	start address of the vmemmap virtual address range that we want
  *		to remap.
  * @end:	end address of the vmemmap virtual address range that we want to
  *		remap.
- * @vmemmap_head: the page to be installed as first in the vmemmap range
  * @vmemmap_tail: the page to be installed as non-first in the vmemmap range
- * @vmemmap_pages: list to deposit vmemmap pages to be freed.  It is callers
- *		responsibility to free pages.
+ * @vmemmap_pages: input/output list whose first entry supplies the new backing
+ *		page for @start; replaced backing pages are added to the list for
+ *		the caller to free.
  * @flags:	modifications to vmemmap_remap_walk flags
  *
  * Return: %0 on success, negative error code otherwise.
  */
 static int vmemmap_remap_free(unsigned long start, unsigned long end,
-			      struct page *vmemmap_head,
 			      struct page *vmemmap_tail,
 			      struct list_head *vmemmap_pages,
 			      unsigned long flags)
@@ -308,7 +306,6 @@ static int vmemmap_remap_free(unsigned long start, unsigned long end,
 	int ret;
 	struct vmemmap_remap_walk walk = {
 		.remap_pte	= vmemmap_remap_pte,
-		.vmemmap_head	= vmemmap_head,
 		.vmemmap_tail	= vmemmap_tail,
 		.vmemmap_pages	= vmemmap_pages,
 		.flags		= flags,
@@ -549,8 +546,7 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
 	 * they can be freed by the caller.
 	 */
 	ret = vmemmap_remap_free(vmemmap_start, vmemmap_end,
-				 vmemmap_head, vmemmap_tail,
-				 vmemmap_pages, flags);
+				 vmemmap_tail, vmemmap_pages, flags);
 out:
 	if (ret)
 		folio_clear_hugetlb_vmemmap_optimized(folio);
-- 
2.54.0


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

* [PATCH v3 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (2 preceding siblings ...)
  2026-11-04  2:35 ` [PATCH v3 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04  9:58   ` Oscar Salvador (SUSE)
  2026-11-04 11:06   ` Mike Rapoport
  2026-11-04  2:35 ` [PATCH v3 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation Muchun Song
                   ` (8 subsequent siblings)
  12 siblings, 2 replies; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

Vmemmap page allocation is currently tied to restoring a virtual range
and assumes an empty destination list.  These constraints prevent it from
being reused when pages are requested independently of a vmemmap range or
appended to an existing list.

Generalize the allocator and give it an all-or-nothing list contract so
the restore and optimization paths can share the same allocation policy.
This also allows HVO head-page allocation to fail gracefully under memory
pressure without triggering the OOM killer.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
---
v3:
- Use two tabs for continued function parameters (suggested by Mike
  Rapoport)
- Rename the local allocation list to new_pages (suggested by Mike
  Rapoport)
- Use a goto cleanup path for allocation failures (suggested by Mike
  Rapoport)
- Free failed allocations directly and retain batched accounting (suggested
  by Mike Rapoport)

v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 31 ++++++++++++++-----------------
 1 file changed, 14 insertions(+), 17 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 0e5b2b182d1b..d3058e106795 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -334,26 +334,24 @@ static int vmemmap_remap_free(unsigned long start, unsigned long end,
 	return ret;
 }
 
-static int alloc_vmemmap_page_list(unsigned long start, unsigned long end,
-				   struct list_head *list)
+static int alloc_vmemmap_page_list(struct list_head *head,
+		unsigned int nr_pages, int nid)
 {
-	gfp_t gfp_mask = GFP_KERNEL | __GFP_RETRY_MAYFAIL;
-	unsigned long nr_pages = (end - start) >> PAGE_SHIFT;
-	int nid = page_to_nid((struct page *)start);
+	LIST_HEAD(new_pages);
 	struct page *page, *next;
-	int i;
 
-	for (i = 0; i < nr_pages; i++) {
-		page = alloc_pages_node(nid, gfp_mask, 0);
+	for (unsigned int i = 0; i < nr_pages; i++) {
+		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
 		if (!page)
-			goto out;
-		list_add(&page->lru, list);
+			goto free_pages;
+		list_add(&page->lru, &new_pages);
 	}
+	list_splice(&new_pages, head);
 	memmap_pages_add(nr_pages);
 
 	return 0;
-out:
-	list_for_each_entry_safe(page, next, list, lru)
+free_pages:
+	list_for_each_entry_safe(page, next, &new_pages, lru)
 		__free_page(page);
 	return -ENOMEM;
 }
@@ -374,13 +372,14 @@ static int vmemmap_remap_alloc(unsigned long start, unsigned long end,
 			       unsigned long flags)
 {
 	LIST_HEAD(vmemmap_pages);
+	int nid = page_to_nid((struct page *)start);
 	struct vmemmap_remap_walk walk = {
 		.remap_pte	= vmemmap_restore_pte,
 		.vmemmap_pages	= &vmemmap_pages,
 		.flags		= flags,
 	};
 
-	if (alloc_vmemmap_page_list(start, end, &vmemmap_pages))
+	if (alloc_vmemmap_page_list(&vmemmap_pages, (end - start) / PAGE_SIZE, nid))
 		return -ENOMEM;
 
 	return vmemmap_remap_range(start, end, &walk);
@@ -527,15 +526,13 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
 	 */
 	folio_set_hugetlb_vmemmap_optimized(folio);
 
-	vmemmap_head = alloc_pages_node(nid, GFP_KERNEL, 0);
-	if (!vmemmap_head) {
+	if (alloc_vmemmap_page_list(vmemmap_pages, VMEMMAP_OPTIMIZATION_PAGES, nid)) {
 		ret = -ENOMEM;
 		goto out;
 	}
 
+	vmemmap_head = list_first_entry(vmemmap_pages, struct page, lru);
 	copy_page(page_to_virt(vmemmap_head), folio);
-	list_add(&vmemmap_head->lru, vmemmap_pages);
-	memmap_pages_add(1);
 
 	vmemmap_start	= (unsigned long)&folio->page;
 	vmemmap_end	= vmemmap_start + hugetlb_vmemmap_size(h);
-- 
2.54.0


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

* [PATCH v3 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (3 preceding siblings ...)
  2026-11-04  2:35 ` [PATCH v3 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04 10:02   ` Oscar Salvador (SUSE)
  2026-11-04  2:35 ` [PATCH v3 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte() Muchun Song
                   ` (7 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

Installing a writable vmemmap page requires consuming a replacement page,
copying the current contents into it and making the copy visible before
publishing the new mapping.  HVO currently splits this sequence between
the page-table walker and its caller depending on the remap path.

Centralize the sequence in the walker so all writable replacements follow
the same copy-and-publish protocol.  This keeps page preparation with the
mapping update and removes path-specific handling from higher-level HVO
code.

No functional change intended.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v3:
- Use two tabs for the new helper parameter continuation (suggested by Mike
  Rapoport)
- Collect Reviewed-by from Mike Rapoport

v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
- Replace page_to_virt() with page_address()
---
 mm/hugetlb_vmemmap.c | 40 +++++++++++++++++-----------------------
 1 file changed, 17 insertions(+), 23 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index d3058e106795..531150870aac 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -199,6 +199,20 @@ static void free_vmemmap_page_list(struct list_head *list)
 		free_vmemmap_page(page);
 }
 
+static struct page *vmemmap_dup_page_from_list(const struct page *src,
+		struct list_head *vmemmap_pages)
+{
+	struct page *dst = list_first_entry(vmemmap_pages, struct page, lru);
+
+	list_del(&dst->lru);
+	copy_page(page_address(dst), page_address(src));
+
+	/* Ensure updates to @dst page are visible before set_pte_at(). */
+	smp_wmb();
+
+	return dst;
+}
+
 static void vmemmap_remap_pte(pte_t *pte, unsigned long addr,
 			      struct vmemmap_remap_walk *walk)
 {
@@ -211,16 +225,7 @@ static void vmemmap_remap_pte(pte_t *pte, unsigned long addr,
 
 		VM_WARN_ON_ONCE(!PageHead((const struct page *)addr));
 
-		head = list_first_entry(walk->vmemmap_pages, struct page, lru);
-		list_del(&head->lru);
-
-		/*
-		 * Makes sure that preceding stores to the page contents from
-		 * vmemmap_remap_free() become visible before the set_pte_at()
-		 * write.
-		 */
-		smp_wmb();
-
+		head = vmemmap_dup_page_from_list(page, walk->vmemmap_pages);
 		entry = mk_pte(head, PAGE_KERNEL);
 	} else {
 		VM_WARN_ON_ONCE(!PageTail((const struct page *)addr));
@@ -253,15 +258,7 @@ static void vmemmap_restore_pte(pte_t *pte, unsigned long addr,
 
 	VM_WARN_ON_ONCE(PageHead((const struct page *)addr));
 
-	dst = list_first_entry(walk->vmemmap_pages, struct page, lru);
-	list_del(&dst->lru);
-	copy_page(page_to_virt(dst), page_to_virt(src));
-
-	/*
-	 * Makes sure that preceding stores to the page contents become visible
-	 * before the set_pte_at() write.
-	 */
-	smp_wmb();
+	dst = vmemmap_dup_page_from_list(src, walk->vmemmap_pages);
 	set_pte_at(&init_mm, addr, pte, mk_pte(dst, PAGE_KERNEL));
 }
 
@@ -499,7 +496,7 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
 					    unsigned long flags)
 {
 	unsigned long vmemmap_start, vmemmap_end;
-	struct page *vmemmap_head, *vmemmap_tail;
+	struct page *vmemmap_tail;
 	int nid, ret = 0;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_hugetlb(folio), folio);
@@ -531,9 +528,6 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
 		goto out;
 	}
 
-	vmemmap_head = list_first_entry(vmemmap_pages, struct page, lru);
-	copy_page(page_to_virt(vmemmap_head), folio);
-
 	vmemmap_start	= (unsigned long)&folio->page;
 	vmemmap_end	= vmemmap_start + hugetlb_vmemmap_size(h);
 
-- 
2.54.0


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

* [PATCH v3 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte()
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (4 preceding siblings ...)
  2026-11-04  2:35 ` [PATCH v3 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04 10:04   ` Oscar Salvador (SUSE)
  2026-11-04  2:35 ` [PATCH v3 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling Muchun Song
                   ` (6 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

Both optimization and restoration update vmemmap PTEs by remapping them,
so "remap" does not identify the callback's role.  Use "optimize" for the
optimization callback to match "restore" and make the two directions
explicit and consistent.

No functional change intended.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v3:
- Use two tabs for the renamed helper parameter continuation (suggested by
  Mike Rapoport)
- Collect Reviewed-by from Mike Rapoport

v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 531150870aac..90175499b9eb 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -213,8 +213,8 @@ static struct page *vmemmap_dup_page_from_list(const struct page *src,
 	return dst;
 }
 
-static void vmemmap_remap_pte(pte_t *pte, unsigned long addr,
-			      struct vmemmap_remap_walk *walk)
+static void vmemmap_optimize_pte(pte_t *pte, unsigned long addr,
+		struct vmemmap_remap_walk *walk)
 {
 	struct page *page = pte_page(ptep_get(pte));
 	pte_t entry;
@@ -302,7 +302,7 @@ static int vmemmap_remap_free(unsigned long start, unsigned long end,
 {
 	int ret;
 	struct vmemmap_remap_walk walk = {
-		.remap_pte	= vmemmap_remap_pte,
+		.remap_pte	= vmemmap_optimize_pte,
 		.vmemmap_tail	= vmemmap_tail,
 		.vmemmap_pages	= vmemmap_pages,
 		.flags		= flags,
-- 
2.54.0


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

* [PATCH v3 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (5 preceding siblings ...)
  2026-11-04  2:35 ` [PATCH v3 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte() Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04 10:31   ` Oscar Salvador (SUSE)
  2026-11-04  2:35 ` [PATCH v3 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper Muchun Song
                   ` (5 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

The remap walker currently carries a shared tail page selected by its
caller, even though the optimize and restore callbacks can derive the
same information from the folio being processed.  This adds redundant
state to the walker and couples its interface to caller-side preparation.

Derive the shared mapping state in the callbacks instead, keeping the
mapping policy with the PTE being processed and simplifying the remap
interface.

No functional change intended.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v3:
- Use two tabs for function parameter continuations (suggested by Mike
  Rapoport)
- Collect Reviewed-by from Mike Rapoport

v2:
- Update the vmemmap page-list contract for shared tail derivation
  (follow-up to review by Lance Yang and Qi Zheng)
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 include/linux/vmemmap-optimization.h |  6 +-
 mm/hugetlb_vmemmap.c                 | 93 ++++++++++++----------------
 mm/mm_init.c                         |  6 +-
 mm/sparse-vmemmap.c                  |  7 ++-
 4 files changed, 52 insertions(+), 60 deletions(-)

diff --git a/include/linux/vmemmap-optimization.h b/include/linux/vmemmap-optimization.h
index 065840f3c241..858311a71b0c 100644
--- a/include/linux/vmemmap-optimization.h
+++ b/include/linux/vmemmap-optimization.h
@@ -91,11 +91,13 @@ static inline struct page *vmemmap_shared_tail_page(unsigned int order,
 }
 #endif /* CONFIG_VMEMMAP_OPTIMIZATION */
 
-static inline bool vmemmap_optimizable_pfn(unsigned long pfn)
+static inline bool vmemmap_optimizable_pfn(unsigned long pfn, unsigned int order)
 {
-	const unsigned int order = pfn_to_section_compound_order(pfn);
 	const unsigned long nr_pages = 1UL << order;
 
+	if (!IS_ENABLED(CONFIG_VMEMMAP_OPTIMIZATION))
+		return false;
+
 	if (!is_power_of_2(sizeof(struct page)))
 		return false;
 
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 90175499b9eb..2724539f9c62 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -25,21 +25,17 @@
  *
  * @remap_pte:		called for each lowest-level entry (PTE).
  * @nr_walked:		the number of walked pte.
- * @vmemmap_tail:	the page to be installed as non-first in the vmemmap range
  * @vmemmap_pages:	list supplying replacement pages for the remap and
  *			receiving displaced backing pages for the caller to free.
  * @flags:		used to modify behavior in vmemmap page table walking
  *			operations.
  */
 struct vmemmap_remap_walk {
-	void			(*remap_pte)(pte_t *pte, unsigned long addr,
+	int			(*remap_pte)(pte_t *pte, unsigned long addr,
 					     struct vmemmap_remap_walk *walk);
-
 	unsigned long		nr_walked;
-	struct page		*vmemmap_tail;
 	struct list_head	*vmemmap_pages;
 
-
 /* Defer safely batchable TLB flushes to the caller. */
 #define VMEMMAP_DEFER_TLB_FLUSH	BIT(0)
 	unsigned long		flags;
@@ -140,12 +136,14 @@ static int vmemmap_pmd_entry(pmd_t *pmd, unsigned long addr,
 static int vmemmap_pte_entry(pte_t *pte, unsigned long addr,
 			     unsigned long next, struct mm_walk *walk)
 {
+	int ret;
 	struct vmemmap_remap_walk *vmemmap_walk = walk->private;
 
-	vmemmap_walk->remap_pte(pte, addr, vmemmap_walk);
-	vmemmap_walk->nr_walked++;
+	ret = vmemmap_walk->remap_pte(pte, addr, vmemmap_walk);
+	if (!ret)
+		vmemmap_walk->nr_walked++;
 
-	return 0;
+	return ret;
 }
 
 static const struct mm_walk_ops vmemmap_remap_ops = {
@@ -213,53 +211,53 @@ static struct page *vmemmap_dup_page_from_list(const struct page *src,
 	return dst;
 }
 
-static void vmemmap_optimize_pte(pte_t *pte, unsigned long addr,
-		struct vmemmap_remap_walk *walk)
+static inline struct page *folio_vmemmap_shared_tail_page(const struct folio *folio)
 {
-	struct page *page = pte_page(ptep_get(pte));
-	pte_t entry;
-
-	/* Remapping the head page requires r/w */
-	if (unlikely(walk->nr_walked == 0)) {
-		struct page *head;
+	return vmemmap_shared_tail_page(folio_order(folio), folio_zone(folio));
+}
 
-		VM_WARN_ON_ONCE(!PageHead((const struct page *)addr));
+static int vmemmap_optimize_pte(pte_t *pte, unsigned long addr,
+		struct vmemmap_remap_walk *walk)
+{
+	const struct page *page = (const struct page *)addr;
+	const struct folio *folio = page_folio(page);
+	struct page *src = pte_page(ptep_get(pte)), *dst;
 
-		head = vmemmap_dup_page_from_list(page, walk->vmemmap_pages);
-		entry = mk_pte(head, PAGE_KERNEL);
+	if (likely(vmemmap_optimizable_pfn(page_to_pfn(page), folio_order(folio)))) {
+		dst = folio_vmemmap_shared_tail_page(folio);
+		if (!dst)
+			return -ENOMEM;
 	} else {
-		VM_WARN_ON_ONCE(!PageTail((const struct page *)addr));
-
-		/*
-		 * Map the shared tail page writable. A lockless get that read
-		 * a non-zero refcount before the remap can still do its
-		 * cmpxchg after it. The refcount is zero, so the cmpxchg
-		 * fails and leaves the page unchanged.
-		 */
-		entry = mk_pte(walk->vmemmap_tail, PAGE_KERNEL);
+		dst = vmemmap_dup_page_from_list(src, walk->vmemmap_pages);
 	}
 
-	list_add(&page->lru, walk->vmemmap_pages);
-	set_pte_at(&init_mm, addr, pte, entry);
+	list_add(&src->lru, walk->vmemmap_pages);
+	/*
+	 * Map the shared tail page writable intentionally. A lockless get that
+	 * read a non-zero refcount before the remap can still do its cmpxchg
+	 * after it. The refcount is zero, so the cmpxchg fails and leaves the
+	 * page unchanged.
+	 */
+	set_pte_at(&init_mm, addr, pte, mk_pte(dst, PAGE_KERNEL));
+
+	return 0;
 }
 
-static void vmemmap_restore_pte(pte_t *pte, unsigned long addr,
-				struct vmemmap_remap_walk *walk)
+static int vmemmap_restore_pte(pte_t *pte, unsigned long addr,
+		struct vmemmap_remap_walk *walk)
 {
+	const struct page *page = (const struct page *)addr;
+	const struct folio *folio = page_folio(page);
 	struct page *src = pte_page(ptep_get(pte)), *dst;
 
-	/*
-	 * When rolling back vmemmap_remap_free(), keep the copied head page
-	 * mapping and restore only PTEs currently pointing at the shared tail
-	 * page.
-	 */
-	if (walk->vmemmap_tail && walk->vmemmap_tail != src)
-		return;
-
-	VM_WARN_ON_ONCE(PageHead((const struct page *)addr));
+	if (unlikely(!vmemmap_optimizable_pfn(page_to_pfn(page), folio_order(folio))))
+		return 0;
 
+	VM_WARN_ON_ONCE(folio_vmemmap_shared_tail_page(folio) != src);
 	dst = vmemmap_dup_page_from_list(src, walk->vmemmap_pages);
 	set_pte_at(&init_mm, addr, pte, mk_pte(dst, PAGE_KERNEL));
+
+	return 0;
 }
 
 /**
@@ -287,7 +285,6 @@ static int vmemmap_remap_split(unsigned long start, unsigned long end)
  *		to remap.
  * @end:	end address of the vmemmap virtual address range that we want to
  *		remap.
- * @vmemmap_tail: the page to be installed as non-first in the vmemmap range
  * @vmemmap_pages: input/output list whose first entry supplies the new backing
  *		page for @start; replaced backing pages are added to the list for
  *		the caller to free.
@@ -296,14 +293,11 @@ static int vmemmap_remap_split(unsigned long start, unsigned long end)
  * Return: %0 on success, negative error code otherwise.
  */
 static int vmemmap_remap_free(unsigned long start, unsigned long end,
-			      struct page *vmemmap_tail,
-			      struct list_head *vmemmap_pages,
-			      unsigned long flags)
+		struct list_head *vmemmap_pages, unsigned long flags)
 {
 	int ret;
 	struct vmemmap_remap_walk walk = {
 		.remap_pte	= vmemmap_optimize_pte,
-		.vmemmap_tail	= vmemmap_tail,
 		.vmemmap_pages	= vmemmap_pages,
 		.flags		= flags,
 	};
@@ -321,7 +315,6 @@ static int vmemmap_remap_free(unsigned long start, unsigned long end,
 	 */
 	walk = (struct vmemmap_remap_walk) {
 		.remap_pte	= vmemmap_restore_pte,
-		.vmemmap_tail	= vmemmap_tail,
 		.vmemmap_pages	= vmemmap_pages,
 		.flags		= 0,
 	};
@@ -496,7 +489,6 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
 					    unsigned long flags)
 {
 	unsigned long vmemmap_start, vmemmap_end;
-	struct page *vmemmap_tail;
 	int nid, ret = 0;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_hugetlb(folio), folio);
@@ -506,9 +498,6 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
 		return ret;
 
 	nid = folio_nid(folio);
-	vmemmap_tail = vmemmap_shared_tail_page(h->order, folio_zone(folio));
-	if (!vmemmap_tail)
-		return -ENOMEM;
 
 	/*
 	 * Very Subtle
@@ -537,7 +526,7 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
 	 * they can be freed by the caller.
 	 */
 	ret = vmemmap_remap_free(vmemmap_start, vmemmap_end,
-				 vmemmap_tail, vmemmap_pages, flags);
+				 vmemmap_pages, flags);
 out:
 	if (ret)
 		folio_clear_hugetlb_vmemmap_optimized(folio);
diff --git a/mm/mm_init.c b/mm/mm_init.c
index d99af80ec752..a9d848c92678 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -914,6 +914,8 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone
 #endif
 
 	for (pfn = start_pfn; pfn < end_pfn; ) {
+		const unsigned int order = pfn_to_section_compound_order(pfn);
+
 		/*
 		 * There can be holes in boot-time mem_map[]s handed to this
 		 * function.  They do not exist on hotplugged memory.
@@ -929,9 +931,7 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone
 		 * Vmemmap-optimizable PFNs are backed by shared tail struct pages,
 		 * which have already been initialized during vmemmap population.
 		 */
-		if (vmemmap_optimizable_pfn(pfn)) {
-			const unsigned int order = pfn_to_section_compound_order(pfn);
-
+		if (vmemmap_optimizable_pfn(pfn, order)) {
 			pfn = min(ALIGN(pfn, 1UL << order), end_pfn);
 			continue;
 		}
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 3e9d6617c4bb..b8b55e12f5ba 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -241,7 +241,7 @@ static __meminit void *vmemmap_alloc_pte(unsigned long pfn, int node,
 	struct page *page;
 	const unsigned int order = pfn_to_section_compound_order(pfn);
 
-	if (!vmemmap_optimizable_pfn(pfn))
+	if (!vmemmap_optimizable_pfn(pfn, order))
 		return vmemmap_alloc_block_buf(PAGE_SIZE, node, altmap);
 
 	/*
@@ -286,6 +286,7 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
 {
 	pte_t *pte = pte_offset_kernel(pmd, addr);
 	unsigned long pfn = page_to_pfn((struct page *)addr);
+	const unsigned int order = pfn_to_section_compound_order(pfn);
 
 	if (pte_none(ptep_get(pte))) {
 		pte_t entry;
@@ -296,10 +297,10 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
 
 		entry = pfn_pte(PHYS_PFN(__pa(p)), PAGE_KERNEL);
 		/* Map the shared tail page as read-only to catch illegal writes. */
-		if (vmemmap_optimizable_pfn(pfn))
+		if (vmemmap_optimizable_pfn(pfn, order))
 			entry = pte_wrprotect(entry);
 		set_pte_at(&init_mm, addr, pte, entry);
-	} else if (WARN_ON_ONCE(vmemmap_optimizable_pfn(pfn)))
+	} else if (WARN_ON_ONCE(vmemmap_optimizable_pfn(pfn, order)))
 		return NULL;
 	return pte;
 }
-- 
2.54.0


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

* [PATCH v3 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (6 preceding siblings ...)
  2026-11-04  2:35 ` [PATCH v3 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04 10:36   ` Oscar Salvador (SUSE)
  2026-11-04  2:35 ` [PATCH v3 09/12] mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented Muchun Song
                   ` (4 subsequent siblings)
  12 siblings, 1 reply; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

Keep alloc_vmemmap_page_list() together with
free_vmemmap_page_list(). This also allows the folio-oriented
optimization helper introduced in a subsequent change to reuse the
allocator without a forward declaration.

No functional change intended.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v3:
- Carry the revised allocation cleanup through the helper move (follow-up
  to review by Mike Rapoport)
- Collect Reviewed-by from Mike Rapoport

v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 44 ++++++++++++++++++++++----------------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 2724539f9c62..7d51df1cb78b 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -197,6 +197,28 @@ static void free_vmemmap_page_list(struct list_head *list)
 		free_vmemmap_page(page);
 }
 
+static int alloc_vmemmap_page_list(struct list_head *head,
+		unsigned int nr_pages, int nid)
+{
+	LIST_HEAD(new_pages);
+	struct page *page, *next;
+
+	for (unsigned int i = 0; i < nr_pages; i++) {
+		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
+		if (!page)
+			goto free_pages;
+		list_add(&page->lru, &new_pages);
+	}
+	list_splice(&new_pages, head);
+	memmap_pages_add(nr_pages);
+
+	return 0;
+free_pages:
+	list_for_each_entry_safe(page, next, &new_pages, lru)
+		__free_page(page);
+	return -ENOMEM;
+}
+
 static struct page *vmemmap_dup_page_from_list(const struct page *src,
 		struct list_head *vmemmap_pages)
 {
@@ -324,28 +346,6 @@ static int vmemmap_remap_free(unsigned long start, unsigned long end,
 	return ret;
 }
 
-static int alloc_vmemmap_page_list(struct list_head *head,
-		unsigned int nr_pages, int nid)
-{
-	LIST_HEAD(new_pages);
-	struct page *page, *next;
-
-	for (unsigned int i = 0; i < nr_pages; i++) {
-		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
-		if (!page)
-			goto free_pages;
-		list_add(&page->lru, &new_pages);
-	}
-	list_splice(&new_pages, head);
-	memmap_pages_add(nr_pages);
-
-	return 0;
-free_pages:
-	list_for_each_entry_safe(page, next, &new_pages, lru)
-		__free_page(page);
-	return -ENOMEM;
-}
-
 /**
  * vmemmap_remap_alloc - remap the vmemmap virtual address range [@start, end)
  *			 to the page which is from the @vmemmap_pages
-- 
2.54.0


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

* [PATCH v3 09/12] mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (7 preceding siblings ...)
  2026-11-04  2:35 ` [PATCH v3 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04  2:35 ` [PATCH v3 10/12] mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented Muchun Song
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

Vmemmap optimization operates on one folio at a time, but the current
interface exposes a raw address range and requires the HugeTLB caller to
prepare part of the operation.  This splits the common mechanism across
layers and obscures its natural unit of work.

Make the folio the unit of vmemmap optimization and move the common
mechanics behind a folio-oriented interface.  This keeps HugeTLB-specific
eligibility and state in the caller while making the remapping code
applicable to any eligible folio.

No functional change intended.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v3:
- Collect Reviewed-by from Mike Rapoport

v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 64 +++++++++++++++++++++-----------------------
 1 file changed, 30 insertions(+), 34 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 7d51df1cb78b..64967f18e3c3 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -233,6 +233,11 @@ static struct page *vmemmap_dup_page_from_list(const struct page *src,
 	return dst;
 }
 
+static inline unsigned long folio_vmemmap_size(const struct folio *folio)
+{
+	return folio_nr_pages(folio) * sizeof(struct page);
+}
+
 static inline struct page *folio_vmemmap_shared_tail_page(const struct folio *folio)
 {
 	return vmemmap_shared_tail_page(folio_order(folio), folio_zone(folio));
@@ -302,28 +307,39 @@ static int vmemmap_remap_split(unsigned long start, unsigned long end)
 }
 
 /**
- * vmemmap_remap_free - remap a vmemmap range and collect old backing pages
- * @start:	start address of the vmemmap virtual address range that we want
- *		to remap.
- * @end:	end address of the vmemmap virtual address range that we want to
- *		remap.
- * @vmemmap_pages: input/output list whose first entry supplies the new backing
- *		page for @start; replaced backing pages are added to the list for
- *		the caller to free.
- * @flags:	modifications to vmemmap_remap_walk flags
+ * vmemmap_optimize_folio - optimize the vmemmap backing of a folio
+ * @folio:	folio whose vmemmap pages should be optimized.
+ * @vmemmap_pages: list holding replacement pages for the remap and displaced
+ *		backing pages for the caller to free.
+ * @flags:	vmemmap remap control flags passed to vmemmap_remap_walk.
+ *
+ * Remap @folio's vmemmap so optimizable entries point at the shared tail page
+ * while non-optimizable entries are copied into replacement pages. Caller is
+ * responsible for freeing the pages left on the @vmemmap_pages list, even on
+ * failure.
  *
  * Return: %0 on success, negative error code otherwise.
  */
-static int vmemmap_remap_free(unsigned long start, unsigned long end,
+static int vmemmap_optimize_folio(const struct folio *folio,
 		struct list_head *vmemmap_pages, unsigned long flags)
 {
-	int ret;
+	int ret, nid = folio_nid(folio);
+	unsigned long start = (unsigned long)folio_page(folio, 0);
+	unsigned long end = start + folio_vmemmap_size(folio);
 	struct vmemmap_remap_walk walk = {
 		.remap_pte	= vmemmap_optimize_pte,
 		.vmemmap_pages	= vmemmap_pages,
 		.flags		= flags,
 	};
 
+	VM_WARN_ON_ONCE_FOLIO(folio_ref_count(folio), folio);
+
+	if (!vmemmap_optimizable_order(folio_order(folio)))
+		return -EOPNOTSUPP;
+
+	if (alloc_vmemmap_page_list(vmemmap_pages, VMEMMAP_OPTIMIZATION_PAGES, nid))
+		return -ENOMEM;
+
 	ret = vmemmap_remap_range(start, end, &walk);
 	if (!ret || !walk.nr_walked)
 		return ret;
@@ -488,16 +504,12 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
 					    struct list_head *vmemmap_pages,
 					    unsigned long flags)
 {
-	unsigned long vmemmap_start, vmemmap_end;
-	int nid, ret = 0;
+	int ret;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_hugetlb(folio), folio);
-	VM_WARN_ON_ONCE_FOLIO(folio_ref_count(folio), folio);
 
 	if (!vmemmap_should_optimize_folio(h, folio))
-		return ret;
-
-	nid = folio_nid(folio);
+		return 0;
 
 	/*
 	 * Very Subtle
@@ -511,23 +523,7 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
 	 * the TLB and clear the flag below.
 	 */
 	folio_set_hugetlb_vmemmap_optimized(folio);
-
-	if (alloc_vmemmap_page_list(vmemmap_pages, VMEMMAP_OPTIMIZATION_PAGES, nid)) {
-		ret = -ENOMEM;
-		goto out;
-	}
-
-	vmemmap_start	= (unsigned long)&folio->page;
-	vmemmap_end	= vmemmap_start + hugetlb_vmemmap_size(h);
-
-	/*
-	 * Remap the vmemmap virtual address range [@vmemmap_start, @vmemmap_end).
-	 * Add pages previously mapping the range to vmemmap_pages list so that
-	 * they can be freed by the caller.
-	 */
-	ret = vmemmap_remap_free(vmemmap_start, vmemmap_end,
-				 vmemmap_pages, flags);
-out:
+	ret = vmemmap_optimize_folio(folio, vmemmap_pages, flags);
 	if (ret)
 		folio_clear_hugetlb_vmemmap_optimized(folio);
 
-- 
2.54.0


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

* [PATCH v3 10/12] mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (8 preceding siblings ...)
  2026-11-04  2:35 ` [PATCH v3 09/12] mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04  2:35 ` [PATCH v3 11/12] mm: hugetlb_vmemmap: unify vmemmap range restoration Muchun Song
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

Like optimization, vmemmap restoration operates on one folio at a time,
but the current interface exposes a raw address range and requires the
HugeTLB caller to prepare part of the operation.  This splits the common
mechanism across layers and obscures its natural unit of work.

Make the folio the unit of vmemmap restoration and move the common
mechanics behind a folio-oriented interface.  This keeps HugeTLB-specific
eligibility and state in the caller and gives both directions of HVO
consistent folio-level interfaces.

No functional change intended.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v3:
- Collect Reviewed-by from Mike Rapoport

v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 37 +++++++++++++------------------------
 1 file changed, 13 insertions(+), 24 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 64967f18e3c3..e424cb0b1a78 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -363,28 +363,29 @@ static int vmemmap_optimize_folio(const struct folio *folio,
 }
 
 /**
- * vmemmap_remap_alloc - remap the vmemmap virtual address range [@start, end)
- *			 to the page which is from the @vmemmap_pages
- *			 respectively.
- * @start:	start address of the vmemmap virtual address range that we want
- *		to remap.
- * @end:	end address of the vmemmap virtual address range that we want to
- *		remap.
- * @flags:	modifications to vmemmap_remap_walk flags
+ * vmemmap_restore_folio - restore the vmemmap backing of a folio
+ * @folio:	folio whose vmemmap pages should be restored.
+ * @flags:	vmemmap remap control flags passed to vmemmap_remap_walk.
+ *
+ * Remap @folio's vmemmap so optimizable entries are restored from the shared
+ * tail page back to distinct backing pages allocated for @folio, while
+ * non-optimizable entries remain unchanged.
  *
  * Return: %0 on success, negative error code otherwise.
  */
-static int vmemmap_remap_alloc(unsigned long start, unsigned long end,
-			       unsigned long flags)
+static int vmemmap_restore_folio(const struct folio *folio, unsigned long flags)
 {
 	LIST_HEAD(vmemmap_pages);
-	int nid = page_to_nid((struct page *)start);
+	int nid = folio_nid(folio);
+	unsigned long start = (unsigned long)folio_page(folio, 0);
+	unsigned long end = start + folio_vmemmap_size(folio);
 	struct vmemmap_remap_walk walk = {
 		.remap_pte	= vmemmap_restore_pte,
 		.vmemmap_pages	= &vmemmap_pages,
 		.flags		= flags,
 	};
 
+	start += VMEMMAP_OPTIMIZATION_SIZE;
 	if (alloc_vmemmap_page_list(&vmemmap_pages, (end - start) / PAGE_SIZE, nid))
 		return -ENOMEM;
 
@@ -402,7 +403,6 @@ static int __hugetlb_vmemmap_restore_folio(const struct hstate *h,
 					   struct folio *folio, unsigned long flags)
 {
 	int ret;
-	unsigned long vmemmap_start, vmemmap_end;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_hugetlb(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(folio_ref_count(folio), folio);
@@ -410,18 +410,7 @@ static int __hugetlb_vmemmap_restore_folio(const struct hstate *h,
 	if (!folio_test_hugetlb_vmemmap_optimized(folio))
 		return 0;
 
-	vmemmap_start	= (unsigned long)&folio->page;
-	vmemmap_end	= vmemmap_start + hugetlb_vmemmap_size(h);
-
-	vmemmap_start	+= VMEMMAP_OPTIMIZATION_SIZE;
-
-	/*
-	 * The pages which the vmemmap virtual address range [@vmemmap_start,
-	 * @vmemmap_end) are mapped to are freed to the buddy allocator.
-	 * When a HugeTLB page is freed to the buddy allocator, previously
-	 * discarded vmemmap pages must be allocated and remapping.
-	 */
-	ret = vmemmap_remap_alloc(vmemmap_start, vmemmap_end, flags);
+	ret = vmemmap_restore_folio(folio, flags);
 	if (!ret)
 		folio_clear_hugetlb_vmemmap_optimized(folio);
 
-- 
2.54.0


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

* [PATCH v3 11/12] mm: hugetlb_vmemmap: unify vmemmap range restoration
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (9 preceding siblings ...)
  2026-11-04  2:35 ` [PATCH v3 10/12] mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04  2:35 ` [PATCH v3 12/12] mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented Muchun Song
  2026-11-04  6:46 ` [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Andrew Morton
  12 siblings, 0 replies; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

Restoring an optimized folio and rolling back a partial optimization
both restore a vmemmap range, but the operation is currently implemented
separately in each path.

Use a single range-restoration path for both cases, making rollback an
explicit bounded form of normal restoration.

No functional change intended.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v3:
- Collect Reviewed-by from Mike Rapoport

v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 33 ++++++++++++++-------------------
 1 file changed, 14 insertions(+), 19 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index e424cb0b1a78..0dcbc2b72384 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -287,6 +287,18 @@ static int vmemmap_restore_pte(pte_t *pte, unsigned long addr,
 	return 0;
 }
 
+static inline int vmemmap_restore_range(unsigned long start, unsigned long end,
+		struct list_head *vmemmap_pages, unsigned long flags)
+{
+	struct vmemmap_remap_walk walk = {
+		.remap_pte	= vmemmap_restore_pte,
+		.vmemmap_pages	= vmemmap_pages,
+		.flags		= flags,
+	};
+
+	return vmemmap_remap_range(start, end, &walk);
+}
+
 /**
  * vmemmap_remap_split - split the vmemmap virtual address range [@start, @end)
  *                      backing PMDs of the directmap into PTEs
@@ -345,19 +357,7 @@ static int vmemmap_optimize_folio(const struct folio *folio,
 		return ret;
 
 	end = start + walk.nr_walked * PAGE_SIZE;
-
-	/*
-	 * vmemmap_pages contains pages from the previous vmemmap_remap_range()
-	 * call which failed.  These are pages which were removed from
-	 * the vmemmap. They will be restored in the following call.
-	 */
-	walk = (struct vmemmap_remap_walk) {
-		.remap_pte	= vmemmap_restore_pte,
-		.vmemmap_pages	= vmemmap_pages,
-		.flags		= 0,
-	};
-
-	vmemmap_remap_range(start, end, &walk);
+	vmemmap_restore_range(start, end, vmemmap_pages, 0);
 
 	return ret;
 }
@@ -379,17 +379,12 @@ static int vmemmap_restore_folio(const struct folio *folio, unsigned long flags)
 	int nid = folio_nid(folio);
 	unsigned long start = (unsigned long)folio_page(folio, 0);
 	unsigned long end = start + folio_vmemmap_size(folio);
-	struct vmemmap_remap_walk walk = {
-		.remap_pte	= vmemmap_restore_pte,
-		.vmemmap_pages	= &vmemmap_pages,
-		.flags		= flags,
-	};
 
 	start += VMEMMAP_OPTIMIZATION_SIZE;
 	if (alloc_vmemmap_page_list(&vmemmap_pages, (end - start) / PAGE_SIZE, nid))
 		return -ENOMEM;
 
-	return vmemmap_remap_range(start, end, &walk);
+	return vmemmap_restore_range(start, end, &vmemmap_pages, flags);
 }
 
 static bool vmemmap_optimize_enabled = IS_ENABLED(CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP_DEFAULT_ON);
-- 
2.54.0


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

* [PATCH v3 12/12] mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (10 preceding siblings ...)
  2026-11-04  2:35 ` [PATCH v3 11/12] mm: hugetlb_vmemmap: unify vmemmap range restoration Muchun Song
@ 2026-11-04  2:35 ` Muchun Song
  2026-11-04  6:46 ` [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Andrew Morton
  12 siblings, 0 replies; 26+ messages in thread
From: Muchun Song @ 2026-11-04  2:35 UTC (permalink / raw)
  To: Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm, linux-kernel, Muchun Song

Converting a folio's vmemmap mappings from PMDs to PTEs is a per-folio
operation, but the current interface exposes a raw address range and
hardcodes deferred TLB flushing.  This splits the common mechanism
across layers and couples it to the HugeTLB batching policy.

Make the folio the unit of PTE conversion and let callers select the TLB
flush policy through the folio-oriented interface.  This keeps
HugeTLB-specific eligibility and batching outside the common operation.

No functional change intended.

Signed-off-by: Muchun Song <songmuchun@bytedance.com>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
---
v3:
- Use two tabs for the new wrapper parameter continuation (suggested by
  Mike Rapoport)
- Collect Reviewed-by from Mike Rapoport

v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 40 +++++++++++++++++++---------------------
 1 file changed, 19 insertions(+), 21 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 0dcbc2b72384..17fac64d411f 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -300,20 +300,26 @@ static inline int vmemmap_restore_range(unsigned long start, unsigned long end,
 }
 
 /**
- * vmemmap_remap_split - split the vmemmap virtual address range [@start, @end)
- *                      backing PMDs of the directmap into PTEs
- * @start:     start address of the vmemmap virtual address range that we want
- *             to remap.
- * @end:       end address of the vmemmap virtual address range that we want to
- *             remap.
+ * vmemmap_pteify_folio - convert the vmemmap backing of a folio to PTE mappings
+ * @folio:	folio whose vmemmap range should be converted to PTE mappings.
+ * @flags:	vmemmap remap control flags passed to vmemmap_remap_walk.
+ *
+ * Convert mappings covering @folio's vmemmap into PTE mappings. If TLB flushing
+ * is skipped, the caller must flush TLBs after all pteify operations are complete
+ * before remapping the folio's vmemmap pages.
+ *
  * Return: %0 on success, negative error code otherwise.
  */
-static int vmemmap_remap_split(unsigned long start, unsigned long end)
+static int vmemmap_pteify_folio(const struct folio *folio, unsigned long flags)
 {
 	struct vmemmap_remap_walk walk = {
-		.remap_pte	= NULL,
-		.flags		= VMEMMAP_DEFER_TLB_FLUSH,
+		.flags		= flags,
 	};
+	unsigned long start = (unsigned long)folio_page(folio, 0);
+	unsigned long end = start + folio_vmemmap_size(folio);
+
+	if (!vmemmap_optimizable_order(folio_order(folio)))
+		return -EOPNOTSUPP;
 
 	return vmemmap_remap_range(start, end, &walk);
 }
@@ -532,21 +538,13 @@ void hugetlb_vmemmap_optimize_folio(const struct hstate *h, struct folio *folio)
 	free_vmemmap_page_list(&vmemmap_pages);
 }
 
-static int hugetlb_vmemmap_split_folio(const struct hstate *h, struct folio *folio)
+static int hugetlb_vmemmap_pteify_folio(const struct hstate *h, struct folio *folio,
+		unsigned long flags)
 {
-	unsigned long vmemmap_start, vmemmap_end;
-
 	if (!vmemmap_should_optimize_folio(h, folio))
 		return 0;
 
-	vmemmap_start	= (unsigned long)&folio->page;
-	vmemmap_end	= vmemmap_start + hugetlb_vmemmap_size(h);
-
-	/*
-	 * Split PMDs on the vmemmap virtual address range [@vmemmap_start,
-	 * @vmemmap_end]
-	 */
-	return vmemmap_remap_split(vmemmap_start, vmemmap_end);
+	return vmemmap_pteify_folio(folio, flags);
 }
 
 void hugetlb_vmemmap_optimize_folios(struct hstate *h, struct list_head *folio_list)
@@ -571,7 +569,7 @@ void hugetlb_vmemmap_optimize_folios(struct hstate *h, struct list_head *folio_l
 
 		nr_to_optimize++;
 
-		ret = hugetlb_vmemmap_split_folio(h, folio);
+		ret = hugetlb_vmemmap_pteify_folio(h, folio, flags);
 
 		/*
 		 * Splitting the PMD requires allocating a page, thus let's fail
-- 
2.54.0


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

* Re: [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB
  2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (11 preceding siblings ...)
  2026-11-04  2:35 ` [PATCH v3 12/12] mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented Muchun Song
@ 2026-11-04  6:46 ` Andrew Morton
  2026-11-04  6:55   ` Muchun Song
  12 siblings, 1 reply; 26+ messages in thread
From: Andrew Morton @ 2026-11-04  6:46 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Mike Rapoport, Qi Zheng, Lance Yang,
	James Houghton, David Hildenbrand, Muchun Song, linux-mm,
	linux-kernel

On Wed,  4 Nov 2026 10:34:58 +0800 Muchun Song <songmuchun@bytedance.com> wrote:

> Hi,
> 
> Section-based vmemmap optimization is implemented in sparse-vmemmap.c,
> while folio-based optimization currently lives in hugetlb_vmemmap.c.
> The latter is tightly coupled to HugeTLB-specific state even though the
> underlying page-table remapping operates on one folio at a time.
> 
> To make these interfaces easier to maintain, the folio-based remapping
> mechanics first need a boundary that is independent of HugeTLB-specific
> state and raw address ranges. This will allow the mechanics to be moved
> into a common vmemmap optimization layer alongside the section-based
> interface.
> 
> This series establishes that boundary through three folio-oriented
> operations, each with a distinct role:
> 
>   - vmemmap_optimize_folio() optimizes the vmemmap backing of a folio;
>   - vmemmap_restore_folio() restores the vmemmap backing of a folio; and
>   - vmemmap_pteify_folio() converts the vmemmap backing of a folio to
>     PTE mappings.
> 
> Together, these operations encapsulate the address-range calculation and
> remapping mechanics for a folio. HugeTLB wrappers retain policy decisions
> such as eligibility, optimized state, batching and TLB flush handling.
> 
> Patches 1-8 generalize the underlying vmemmap remapping infrastructure by
> removing HugeTLB-specific assumptions and simplifying remap control and
> resource handling. Patches 9-12 then establish folio-oriented operations
> for optimization, restoration and PTE conversion.
> 
> The implementation remains in hugetlb_vmemmap.c for now. A follow-up can
> move the folio-based operations into the common vmemmap optimization layer
> once the interface boundary is in place.
> 

Thanks.  I fed this into my evolving LLM-controlled testing setup and it
said

: I tested all twelve patches together in an x86-64 Debian VM under
: QEMU/KVM, with 4 KiB base pages and KASAN enabled.
:
: I grew the pool by 64 two-MiB huge pages and drained it again. With
: vmemmap optimization enabled, the kernel released 448 vmemmap pages
: on growth and restored all 448 on shrink, in each of three cycles.
: With optimization disabled, the vmemmap count stayed constant. I also
: faulted in a single surplus huge page. It released seven vmemmap pages
: and restored all seven when I unmapped it.
:
: I tested both boot-reserved and runtime-allocated 1 GiB huge pages.
: For the runtime case, I grew the pool from zero to one, drained it,
: and allocated again. I mapped the page and wrote and verified a byte
: in every 4 KiB base page. After unmapping it, I demoted it to 512
: two-MiB huge pages and drained the resulting pool.
:
: All twelve programs in the HugeTLB selftest group passed, including
: the cgroup reservation, fault-charge and reparenting tests. Both
: soft-offline cases passed, as did all twelve poisoned-page read test
: cases. I saw no HugeTLB warnings or KASAN reports.
:
: On a diagnostic build, I failed the head-page allocation and, in a
: separate run, interrupted optimization after three PTEs had changed.
: The trace showed rollback restoring the two tail entries to distinct
: backing pages. The folio remained usable in both runs. I also failed
: restoration after three of seven replacement pages had been allocated.
: All three were freed, the retry succeeded, and the metadata count
: returned to its starting value.
:
: I configured QEMU with two virtual NUMA nodes, each with two vCPUs
: and 4 GiB of RAM. On each node, I forced the batched pre-split to
: fail. The trace showed the remap splitting the same PMD and completing
: a range flush before replacing its first PTE. Folios on each node
: shared a tail backing page distinct from the other node's. I drained
: each node's pool in turn and verified data in a live folio on the other.
:
: Finally, on the kernel without diagnostic hooks, I grew and drained
: both pools during active page-cache reclaim. Over 40 cycles all 640
: folios optimized and restored, and the OOM-kill count stayed flat.
:
: Tested-by: Andrew Morton <akpm@linux-foundation.org>


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

* Re: [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB
  2026-11-04  6:46 ` [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Andrew Morton
@ 2026-11-04  6:55   ` Muchun Song
  0 siblings, 0 replies; 26+ messages in thread
From: Muchun Song @ 2026-11-04  6:55 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Muchun Song, Oscar Salvador, Mike Rapoport, Qi Zheng, Lance Yang,
	James Houghton, David Hildenbrand, linux-mm, linux-kernel



> On Nov 4, 2026, at 14:46, Andrew Morton <akpm@linux-foundation.org> wrote:
> 
> On Wed,  4 Nov 2026 10:34:58 +0800 Muchun Song <songmuchun@bytedance.com> wrote:
> 
>> Hi,
>> 
>> Section-based vmemmap optimization is implemented in sparse-vmemmap.c,
>> while folio-based optimization currently lives in hugetlb_vmemmap.c.
>> The latter is tightly coupled to HugeTLB-specific state even though the
>> underlying page-table remapping operates on one folio at a time.
>> 
>> To make these interfaces easier to maintain, the folio-based remapping
>> mechanics first need a boundary that is independent of HugeTLB-specific
>> state and raw address ranges. This will allow the mechanics to be moved
>> into a common vmemmap optimization layer alongside the section-based
>> interface.
>> 
>> This series establishes that boundary through three folio-oriented
>> operations, each with a distinct role:
>> 
>>  - vmemmap_optimize_folio() optimizes the vmemmap backing of a folio;
>>  - vmemmap_restore_folio() restores the vmemmap backing of a folio; and
>>  - vmemmap_pteify_folio() converts the vmemmap backing of a folio to
>>    PTE mappings.
>> 
>> Together, these operations encapsulate the address-range calculation and
>> remapping mechanics for a folio. HugeTLB wrappers retain policy decisions
>> such as eligibility, optimized state, batching and TLB flush handling.
>> 
>> Patches 1-8 generalize the underlying vmemmap remapping infrastructure by
>> removing HugeTLB-specific assumptions and simplifying remap control and
>> resource handling. Patches 9-12 then establish folio-oriented operations
>> for optimization, restoration and PTE conversion.
>> 
>> The implementation remains in hugetlb_vmemmap.c for now. A follow-up can
>> move the folio-based operations into the common vmemmap optimization layer
>> once the interface boundary is in place.
>> 
> 
> Thanks.  I fed this into my evolving LLM-controlled testing setup and it
> said
> 
> : I tested all twelve patches together in an x86-64 Debian VM under
> : QEMU/KVM, with 4 KiB base pages and KASAN enabled.
> :
> : I grew the pool by 64 two-MiB huge pages and drained it again. With
> : vmemmap optimization enabled, the kernel released 448 vmemmap pages
> : on growth and restored all 448 on shrink, in each of three cycles.
> : With optimization disabled, the vmemmap count stayed constant. I also
> : faulted in a single surplus huge page. It released seven vmemmap pages
> : and restored all seven when I unmapped it.
> :
> : I tested both boot-reserved and runtime-allocated 1 GiB huge pages.
> : For the runtime case, I grew the pool from zero to one, drained it,
> : and allocated again. I mapped the page and wrote and verified a byte
> : in every 4 KiB base page. After unmapping it, I demoted it to 512
> : two-MiB huge pages and drained the resulting pool.
> :
> : All twelve programs in the HugeTLB selftest group passed, including
> : the cgroup reservation, fault-charge and reparenting tests. Both
> : soft-offline cases passed, as did all twelve poisoned-page read test
> : cases. I saw no HugeTLB warnings or KASAN reports.
> :
> : On a diagnostic build, I failed the head-page allocation and, in a
> : separate run, interrupted optimization after three PTEs had changed.
> : The trace showed rollback restoring the two tail entries to distinct
> : backing pages. The folio remained usable in both runs. I also failed
> : restoration after three of seven replacement pages had been allocated.
> : All three were freed, the retry succeeded, and the metadata count
> : returned to its starting value.
> :
> : I configured QEMU with two virtual NUMA nodes, each with two vCPUs
> : and 4 GiB of RAM. On each node, I forced the batched pre-split to
> : fail. The trace showed the remap splitting the same PMD and completing
> : a range flush before replacing its first PTE. Folios on each node
> : shared a tail backing page distinct from the other node's. I drained
> : each node's pool in turn and verified data in a live folio on the other.
> :
> : Finally, on the kernel without diagnostic hooks, I grew and drained
> : both pools during active page-cache reclaim. Over 40 cycles all 640
> : folios optimized and restored, and the OOM-kill count stayed flat.

Really a thorough set of tests.

> :
> : Tested-by: Andrew Morton <akpm@linux-foundation.org>

Thanks for testing.

Muchun


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

* Re: [PATCH v3 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros
  2026-11-04  2:34 ` [PATCH v3 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros Muchun Song
@ 2026-11-04  9:15   ` Oscar Salvador (SUSE)
  0 siblings, 0 replies; 26+ messages in thread
From: Oscar Salvador (SUSE) @ 2026-11-04  9:15 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Andrew Morton, Mike Rapoport, Qi Zheng,
	Lance Yang, James Houghton, David Hildenbrand, Muchun Song,
	linux-mm, linux-kernel

On Wed, Nov 04, 2026 at 10:34:59AM +0800, Muchun Song wrote:
> Vmemmap optimization is no longer HugeTLB-specific, so the remaining
> HugeTLB-local reserve macros are redundant.
> 
> Replace them with the generic definitions to remove duplication and keep
> the HugeTLB vmemmap code aligned with the common optimization macros.
> 
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

Reviewed-by: Oscar Salvador <osalvador@suse.de>

 

-- 
Oscar Salvador
SUSE Labs

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

* Re: [PATCH v3 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH
  2026-11-04  2:35 ` [PATCH v3 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH Muchun Song
@ 2026-11-04  9:22   ` Oscar Salvador (SUSE)
  2026-11-04 11:06   ` Mike Rapoport
  1 sibling, 0 replies; 26+ messages in thread
From: Oscar Salvador (SUSE) @ 2026-11-04  9:22 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Andrew Morton, Mike Rapoport, Qi Zheng,
	Lance Yang, James Houghton, David Hildenbrand, Muchun Song,
	linux-mm, linux-kernel

On Wed, Nov 04, 2026 at 10:35:00AM +0800, Muchun Song wrote:
> VMEMMAP_SPLIT_NO_TLB_FLUSH and VMEMMAP_REMAP_NO_TLB_FLUSH do not
> describe fully independent choices. A split-only walk can defer the
> PMD-split flush to a later global flush, and a remap-only walk can
> defer the final remap flush. However, when a PMD split is followed
> by PTE remapping, the split flush must happen before the PTEs are
> changed, because stale large-page translations must not overlap with
> new small-page translations that map different physical pages.
> 
> Replace the two flags with a single VMEMMAP_DEFER_TLB_FLUSH flag. The
> new name reflects that only safely batchable flushes are deferred. Keep
> split-only walks able to defer the flush, but force the split flush
> whenever a remap_pte callback is present. Callers that batch HVO work
> can still defer the final remap flush and issue their own global flush.
> 
> Current callers keep the same flushing behavior.
> 
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Reviewed-by: Qi Zheng <qi.zheng@linux.dev>

Reviewed-by: Oscar Salvador <osalvador@suse.de>

 

-- 
Oscar Salvador
SUSE Labs

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

* Re: [PATCH v3 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling
  2026-11-04  2:35 ` [PATCH v3 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling Muchun Song
@ 2026-11-04  9:40   ` Oscar Salvador (SUSE)
  0 siblings, 0 replies; 26+ messages in thread
From: Oscar Salvador (SUSE) @ 2026-11-04  9:40 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Andrew Morton, Mike Rapoport, Qi Zheng,
	Lance Yang, James Houghton, David Hildenbrand, Muchun Song,
	linux-mm, linux-kernel

On Wed, Nov 04, 2026 at 10:35:01AM +0800, Muchun Song wrote:
> The new head page is already tracked on the vmemmap page list before
> remapping starts.  Passing it separately gives the remap walker two
> ways to refer to the same page and unnecessarily complicates its
> interface.
> 
> Use the page list as the single handoff mechanism for the head page,
> removing the redundant state from the walker.
> 
> No functional change intended.
> 
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

Reviewed-by: Oscar Salvador <osalvador@suse.de>

 

-- 
Oscar Salvador
SUSE Labs

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

* Re: [PATCH v3 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation
  2026-11-04  2:35 ` [PATCH v3 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation Muchun Song
@ 2026-11-04  9:58   ` Oscar Salvador (SUSE)
  2026-11-04 11:14     ` Muchun Song
  2026-11-04 11:06   ` Mike Rapoport
  1 sibling, 1 reply; 26+ messages in thread
From: Oscar Salvador (SUSE) @ 2026-11-04  9:58 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Andrew Morton, Mike Rapoport, Qi Zheng,
	Lance Yang, James Houghton, David Hildenbrand, Muchun Song,
	linux-mm, linux-kernel

On Wed, Nov 04, 2026 at 10:35:02AM +0800, Muchun Song wrote:
> Vmemmap page allocation is currently tied to restoring a virtual range
> and assumes an empty destination list.  These constraints prevent it from
> being reused when pages are requested independently of a vmemmap range or
> appended to an existing list.
> 
> Generalize the allocator and give it an all-or-nothing list contract so
> the restore and optimization paths can share the same allocation policy.
> This also allows HVO head-page allocation to fail gracefully under memory
> pressure without triggering the OOM killer.
> 
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Reviewed-by: Qi Zheng <qi.zheng@linux.dev>

Reviewed-by: Oscar Salvador <osalvador@suse.de>

A small nit below:

> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
> index 0e5b2b182d1b..d3058e106795 100644
> --- a/mm/hugetlb_vmemmap.c
> +++ b/mm/hugetlb_vmemmap.c
...
> +static int alloc_vmemmap_page_list(struct list_head *head,
> +		unsigned int nr_pages, int nid)
>  {
> -	gfp_t gfp_mask = GFP_KERNEL | __GFP_RETRY_MAYFAIL;
> -	unsigned long nr_pages = (end - start) >> PAGE_SHIFT;
> -	int nid = page_to_nid((struct page *)start);
> +	LIST_HEAD(new_pages);
>  	struct page *page, *next;
> -	int i;
>  
> -	for (i = 0; i < nr_pages; i++) {
> -		page = alloc_pages_node(nid, gfp_mask, 0);
> +	for (unsigned int i = 0; i < nr_pages; i++) {
> +		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
>  		if (!page)
> -			goto out;
> -		list_add(&page->lru, list);
> +			goto free_pages;
> +		list_add(&page->lru, &new_pages);
>  	}
> +	list_splice(&new_pages, head);
>  	memmap_pages_add(nr_pages);
>  
>  	return 0;
> -out:
> -	list_for_each_entry_safe(page, next, list, lru)
> +free_pages:
> +	list_for_each_entry_safe(page, next, &new_pages, lru)
>  		__free_page(page);
>  	return -ENOMEM;
>  }
...
> @@ -527,15 +526,13 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
>  	 */
>  	folio_set_hugetlb_vmemmap_optimized(folio);
>  
> -	vmemmap_head = alloc_pages_node(nid, GFP_KERNEL, 0);
> -	if (!vmemmap_head) {
> +	if (alloc_vmemmap_page_list(vmemmap_pages, VMEMMAP_OPTIMIZATION_PAGES, nid)) {
>  		ret = -ENOMEM;
>  		goto out;

alloc_vmemmap_page_list() already gives you the error code (ENOMEM), so
it would look more natural to me to use that already and not "overwrite"
it.
e.g: 

 ret = alloc_vmemmap_page_list
 if (ret)
     goto out;

but as I said, just a nit :-)
 

-- 
Oscar Salvador
SUSE Labs

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

* Re: [PATCH v3 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation
  2026-11-04  2:35 ` [PATCH v3 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation Muchun Song
@ 2026-11-04 10:02   ` Oscar Salvador (SUSE)
  0 siblings, 0 replies; 26+ messages in thread
From: Oscar Salvador (SUSE) @ 2026-11-04 10:02 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Andrew Morton, Mike Rapoport, Qi Zheng,
	Lance Yang, James Houghton, David Hildenbrand, Muchun Song,
	linux-mm, linux-kernel

On Wed, Nov 04, 2026 at 10:35:03AM +0800, Muchun Song wrote:
> Installing a writable vmemmap page requires consuming a replacement page,
> copying the current contents into it and making the copy visible before
> publishing the new mapping.  HVO currently splits this sequence between
> the page-table walker and its caller depending on the remap path.
> 
> Centralize the sequence in the walker so all writable replacements follow
> the same copy-and-publish protocol.  This keeps page preparation with the
> mapping update and removes path-specific handling from higher-level HVO
> code.
> 
> No functional change intended.
> 
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

Reviewed-by: Oscar Salvador <osalvador@suse.de>

 

-- 
Oscar Salvador
SUSE Labs

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

* Re: [PATCH v3 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte()
  2026-11-04  2:35 ` [PATCH v3 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte() Muchun Song
@ 2026-11-04 10:04   ` Oscar Salvador (SUSE)
  0 siblings, 0 replies; 26+ messages in thread
From: Oscar Salvador (SUSE) @ 2026-11-04 10:04 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Andrew Morton, Mike Rapoport, Qi Zheng,
	Lance Yang, James Houghton, David Hildenbrand, Muchun Song,
	linux-mm, linux-kernel

On Wed, Nov 04, 2026 at 10:35:04AM +0800, Muchun Song wrote:
> Both optimization and restoration update vmemmap PTEs by remapping them,
> so "remap" does not identify the callback's role.  Use "optimize" for the
> optimization callback to match "restore" and make the two directions
> explicit and consistent.
> 
> No functional change intended.
> 
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

Reviewed-by: Oscar Salvador <osalvador@suse.de>

 

-- 
Oscar Salvador
SUSE Labs

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

* Re: [PATCH v3 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling
  2026-11-04  2:35 ` [PATCH v3 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling Muchun Song
@ 2026-11-04 10:31   ` Oscar Salvador (SUSE)
  0 siblings, 0 replies; 26+ messages in thread
From: Oscar Salvador (SUSE) @ 2026-11-04 10:31 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Andrew Morton, Mike Rapoport, Qi Zheng,
	Lance Yang, James Houghton, David Hildenbrand, Muchun Song,
	linux-mm, linux-kernel

On Wed, Nov 04, 2026 at 10:35:05AM +0800, Muchun Song wrote:
> The remap walker currently carries a shared tail page selected by its
> caller, even though the optimize and restore callbacks can derive the
> same information from the folio being processed.  This adds redundant
> state to the walker and couples its interface to caller-side preparation.
> 
> Derive the shared mapping state in the callbacks instead, keeping the
> mapping policy with the PTE being processed and simplifying the remap
> interface.
> 
> No functional change intended.
> 
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

Reviewed-by: Oscar Salvador <osalvador@suse.de>

 

-- 
Oscar Salvador
SUSE Labs

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

* Re: [PATCH v3 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper
  2026-11-04  2:35 ` [PATCH v3 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper Muchun Song
@ 2026-11-04 10:36   ` Oscar Salvador (SUSE)
  0 siblings, 0 replies; 26+ messages in thread
From: Oscar Salvador (SUSE) @ 2026-11-04 10:36 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Andrew Morton, Mike Rapoport, Qi Zheng,
	Lance Yang, James Houghton, David Hildenbrand, Muchun Song,
	linux-mm, linux-kernel

On Wed, Nov 04, 2026 at 10:35:06AM +0800, Muchun Song wrote:
> Keep alloc_vmemmap_page_list() together with
> free_vmemmap_page_list(). This also allows the folio-oriented
> optimization helper introduced in a subsequent change to reuse the
> allocator without a forward declaration.
> 
> No functional change intended.
> 
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
> Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

Reviewed-by: Oscar Salvador <osalvador@suse.de>

 

-- 
Oscar Salvador
SUSE Labs

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

* Re: [PATCH v3 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH
  2026-11-04  2:35 ` [PATCH v3 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH Muchun Song
  2026-11-04  9:22   ` Oscar Salvador (SUSE)
@ 2026-11-04 11:06   ` Mike Rapoport
  1 sibling, 0 replies; 26+ messages in thread
From: Mike Rapoport @ 2026-11-04 11:06 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Andrew Morton, Mike Rapoport, Qi Zheng,
	Lance Yang, James Houghton, David Hildenbrand, Muchun Song,
	linux-mm, linux-kernel

On Wed, 04 Nov 2026 10:35:00 +0800, Muchun Song <songmuchun@bytedance.com> wrote:
> VMEMMAP_SPLIT_NO_TLB_FLUSH and VMEMMAP_REMAP_NO_TLB_FLUSH do not
> describe fully independent choices. A split-only walk can defer the
> PMD-split flush to a later global flush, and a remap-only walk can
> defer the final remap flush. However, when a PMD split is followed
> by PTE remapping, the split flush must happen before the PTEs are
> changed, because stale large-page translations must not overlap with
> new small-page translations that map different physical pages.
> 
> [...]

Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

-- 
Sincerely yours,
Mike.


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

* Re: [PATCH v3 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation
  2026-11-04  2:35 ` [PATCH v3 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation Muchun Song
  2026-11-04  9:58   ` Oscar Salvador (SUSE)
@ 2026-11-04 11:06   ` Mike Rapoport
  1 sibling, 0 replies; 26+ messages in thread
From: Mike Rapoport @ 2026-11-04 11:06 UTC (permalink / raw)
  To: Muchun Song
  Cc: Oscar Salvador, Andrew Morton, Mike Rapoport, Qi Zheng,
	Lance Yang, James Houghton, David Hildenbrand, Muchun Song,
	linux-mm, linux-kernel

On Wed, 04 Nov 2026 10:35:02 +0800, Muchun Song <songmuchun@bytedance.com> wrote:
> Vmemmap page allocation is currently tied to restoring a virtual range
> and assumes an empty destination list.  These constraints prevent it from
> being reused when pages are requested independently of a vmemmap range or
> appended to an existing list.
> 
> Generalize the allocator and give it an all-or-nothing list contract so
> the restore and optimization paths can share the same allocation policy.
> This also allows HVO head-page allocation to fail gracefully under memory
> pressure without triggering the OOM killer.
> 
> [...]

Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>

-- 
Sincerely yours,
Mike.


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

* Re: [PATCH v3 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation
  2026-11-04  9:58   ` Oscar Salvador (SUSE)
@ 2026-11-04 11:14     ` Muchun Song
  0 siblings, 0 replies; 26+ messages in thread
From: Muchun Song @ 2026-11-04 11:14 UTC (permalink / raw)
  To: Oscar Salvador (SUSE)
  Cc: Muchun Song, Oscar Salvador, Andrew Morton, Mike Rapoport,
	Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	linux-mm, linux-kernel



> On Nov 4, 2026, at 17:58, Oscar Salvador (SUSE) <osalvador@kernel.org> wrote:
> 
> On Wed, Nov 04, 2026 at 10:35:02AM +0800, Muchun Song wrote:
>> Vmemmap page allocation is currently tied to restoring a virtual range
>> and assumes an empty destination list.  These constraints prevent it from
>> being reused when pages are requested independently of a vmemmap range or
>> appended to an existing list.
>> 
>> Generalize the allocator and give it an all-or-nothing list contract so
>> the restore and optimization paths can share the same allocation policy.
>> This also allows HVO head-page allocation to fail gracefully under memory
>> pressure without triggering the OOM killer.
>> 
>> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
>> Reviewed-by: Lance Yang <lance.yang@linux.dev>
>> Reviewed-by: Qi Zheng <qi.zheng@linux.dev>
> 
> Reviewed-by: Oscar Salvador <osalvador@suse.de>

Thanks for your review.

> 
> A small nit below:
> 
>> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
>> index 0e5b2b182d1b..d3058e106795 100644
>> --- a/mm/hugetlb_vmemmap.c
>> +++ b/mm/hugetlb_vmemmap.c
> ...
>> +static int alloc_vmemmap_page_list(struct list_head *head,
>> +		unsigned int nr_pages, int nid)
>> {
>> -	gfp_t gfp_mask = GFP_KERNEL | __GFP_RETRY_MAYFAIL;
>> -	unsigned long nr_pages = (end - start) >> PAGE_SHIFT;
>> -	int nid = page_to_nid((struct page *)start);
>> +	LIST_HEAD(new_pages);
>> 	struct page *page, *next;
>> -	int i;
>> 
>> -	for (i = 0; i < nr_pages; i++) {
>> -		page = alloc_pages_node(nid, gfp_mask, 0);
>> +	for (unsigned int i = 0; i < nr_pages; i++) {
>> +		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
>> 		if (!page)
>> -			goto out;
>> -		list_add(&page->lru, list);
>> +		goto free_pages;
>> +		list_add(&page->lru, &new_pages);
>> }
>> +	list_splice(&new_pages, head);
>> 	memmap_pages_add(nr_pages);
>> 
>> 	return 0;
>> -out:
>> -	list_for_each_entry_safe(page, next, list, lru)
>> +free_pages:
>> +	list_for_each_entry_safe(page, next, &new_pages, lru)
>> 		__free_page(page);
>> 	return -ENOMEM;
>> }
> ...
>> @@ -527,15 +526,13 @@ static int __hugetlb_vmemmap_optimize_folio(const struct hstate *h,
>>  */
>> folio_set_hugetlb_vmemmap_optimized(folio);
>> 
>> -	vmemmap_head = alloc_pages_node(nid, GFP_KERNEL, 0);
>> -	if (!vmemmap_head) {
>> +	if (alloc_vmemmap_page_list(vmemmap_pages, VMEMMAP_OPTIMIZATION_PAGES, nid)) {
>> 		ret = -ENOMEM;
>> 		goto out;
> 
> alloc_vmemmap_page_list() already gives you the error code (ENOMEM), so
> it would look more natural to me to use that already and not "overwrite"
> it.
> e.g: 
> 
> ret = alloc_vmemmap_page_list
> if (ret)
>     goto out;

Agreed, thanks — I'll just propagate the return value next version.

Muchun

> 
> but as I said, just a nit :-)
> 
> 
> -- 
> Oscar Salvador
> SUSE Labs



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

end of thread, other threads:[~2026-11-04 11:15 UTC | newest]

Thread overview: 26+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-04  2:34 [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
2026-11-04  2:34 ` [PATCH v3 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros Muchun Song
2026-11-04  9:15   ` Oscar Salvador (SUSE)
2026-11-04  2:35 ` [PATCH v3 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH Muchun Song
2026-11-04  9:22   ` Oscar Salvador (SUSE)
2026-11-04 11:06   ` Mike Rapoport
2026-11-04  2:35 ` [PATCH v3 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling Muchun Song
2026-11-04  9:40   ` Oscar Salvador (SUSE)
2026-11-04  2:35 ` [PATCH v3 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation Muchun Song
2026-11-04  9:58   ` Oscar Salvador (SUSE)
2026-11-04 11:14     ` Muchun Song
2026-11-04 11:06   ` Mike Rapoport
2026-11-04  2:35 ` [PATCH v3 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation Muchun Song
2026-11-04 10:02   ` Oscar Salvador (SUSE)
2026-11-04  2:35 ` [PATCH v3 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte() Muchun Song
2026-11-04 10:04   ` Oscar Salvador (SUSE)
2026-11-04  2:35 ` [PATCH v3 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling Muchun Song
2026-11-04 10:31   ` Oscar Salvador (SUSE)
2026-11-04  2:35 ` [PATCH v3 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper Muchun Song
2026-11-04 10:36   ` Oscar Salvador (SUSE)
2026-11-04  2:35 ` [PATCH v3 09/12] mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented Muchun Song
2026-11-04  2:35 ` [PATCH v3 10/12] mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented Muchun Song
2026-11-04  2:35 ` [PATCH v3 11/12] mm: hugetlb_vmemmap: unify vmemmap range restoration Muchun Song
2026-11-04  2:35 ` [PATCH v3 12/12] mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented Muchun Song
2026-11-04  6:46 ` [PATCH v3 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Andrew Morton
2026-11-04  6:55   ` Muchun Song

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®