Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB
@ 2026-11-02  3:25 Muchun Song
  2026-11-02  3:25 ` [PATCH v2 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros Muchun Song
                   ` (11 more replies)
  0 siblings, 12 replies; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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.

v2:
- 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                 | 373 ++++++++++++---------------
 mm/hugetlb_vmemmap.h                 |  10 +-
 mm/mm_init.c                         |   6 +-
 mm/sparse-vmemmap.c                  |   7 +-
 6 files changed, 182 insertions(+), 228 deletions(-)


base-commit: d42dfde37af766d1c272467352c0a56d3169ee58
-- 
2.54.0



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

* [PATCH v2 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH Muchun Song
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
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 ff9dbfbc7a96..7c26be15a741 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -3301,7 +3301,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);
@@ -3341,7 +3341,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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
  2026-11-02  3:25 ` [PATCH v2 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling Muchun Song
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
  2026-11-02  3:25 ` [PATCH v2 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros Muchun Song
  2026-11-02  3:25 ` [PATCH v2 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation Muchun Song
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (2 preceding siblings ...)
  2026-11-02  3:25 ` [PATCH v2 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation Muchun Song
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 39 ++++++++++++++++++---------------------
 1 file changed, 18 insertions(+), 21 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 0e5b2b182d1b..4b20a8efc067 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -334,28 +334,26 @@ 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);
-	struct page *page, *next;
-	int i;
+	LIST_HEAD(local);
 
-	for (i = 0; i < nr_pages; i++) {
-		page = alloc_pages_node(nid, gfp_mask, 0);
-		if (!page)
-			goto out;
-		list_add(&page->lru, list);
+	for (unsigned int i = 0; i < nr_pages; i++) {
+		struct page *page;
+
+		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
+		if (!page) {
+			memmap_pages_add(i);
+			free_vmemmap_page_list(&local);
+			return -ENOMEM;
+		}
+		list_add(&page->lru, &local);
 	}
+	list_splice(&local, head);
 	memmap_pages_add(nr_pages);
 
 	return 0;
-out:
-	list_for_each_entry_safe(page, next, list, 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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (3 preceding siblings ...)
  2026-11-02  3:25 ` [PATCH v2 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte() Muchun Song
                   ` (6 subsequent siblings)
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
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 4b20a8efc067..c9635679a36d 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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte()
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (4 preceding siblings ...)
  2026-11-02  3:25 ` [PATCH v2 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling Muchun Song
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
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 c9635679a36d..5e84004db4b8 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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (5 preceding siblings ...)
  2026-11-02  3:25 ` [PATCH v2 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte() Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02  3:52   ` sashiko-bot
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper Muchun Song
                   ` (4 subsequent siblings)
  11 siblings, 2 replies; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
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                 | 90 +++++++++++++---------------
 mm/mm_init.c                         |  6 +-
 mm/sparse-vmemmap.c                  |  7 ++-
 4 files changed, 51 insertions(+), 58 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 5e84004db4b8..d0af251aa533 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,12 @@ 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)
 {
 	int ret;
 	struct vmemmap_remap_walk walk = {
 		.remap_pte	= vmemmap_optimize_pte,
-		.vmemmap_tail	= vmemmap_tail,
 		.vmemmap_pages	= vmemmap_pages,
 		.flags		= flags,
 	};
@@ -321,7 +316,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 +490,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 +499,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 +527,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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (6 preceding siblings ...)
  2026-11-02  3:25 ` [PATCH v2 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 09/12] mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented Muchun Song
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
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 d0af251aa533..244cd8a0c78a 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(local);
+
+	for (unsigned int i = 0; i < nr_pages; i++) {
+		struct page *page;
+
+		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
+		if (!page) {
+			memmap_pages_add(i);
+			free_vmemmap_page_list(&local);
+			return -ENOMEM;
+		}
+		list_add(&page->lru, &local);
+	}
+	list_splice(&local, head);
+	memmap_pages_add(nr_pages);
+
+	return 0;
+}
+
 static struct page *vmemmap_dup_page_from_list(const struct page *src,
 					       struct list_head *vmemmap_pages)
 {
@@ -325,28 +347,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(local);
-
-	for (unsigned int i = 0; i < nr_pages; i++) {
-		struct page *page;
-
-		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
-		if (!page) {
-			memmap_pages_add(i);
-			free_vmemmap_page_list(&local);
-			return -ENOMEM;
-		}
-		list_add(&page->lru, &local);
-	}
-	list_splice(&local, head);
-	memmap_pages_add(nr_pages);
-
-	return 0;
-}
-
 /**
  * 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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 09/12] mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (7 preceding siblings ...)
  2026-11-02  3:25 ` [PATCH v2 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 10/12] mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented Muchun Song
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
v2:
- Collect Reviewed-by from Lance Yang and Qi Zheng
---
 mm/hugetlb_vmemmap.c | 67 ++++++++++++++++++++------------------------
 1 file changed, 31 insertions(+), 36 deletions(-)

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 244cd8a0c78a..14390ee59ba2 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,29 +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,
-			      struct list_head *vmemmap_pages,
-			      unsigned long flags)
+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;
@@ -489,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
@@ -512,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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 10/12] mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (8 preceding siblings ...)
  2026-11-02  3:25 ` [PATCH v2 09/12] mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 11/12] mm: hugetlb_vmemmap: unify vmemmap range restoration Muchun Song
  2026-11-02  3:25 ` [PATCH v2 12/12] mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented Muchun Song
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
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 14390ee59ba2..465a907620ab 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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 11/12] mm: hugetlb_vmemmap: unify vmemmap range restoration
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (9 preceding siblings ...)
  2026-11-02  3:25 ` [PATCH v2 10/12] mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02  3:25 ` [PATCH v2 12/12] mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented Muchun Song
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
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 465a907620ab..370e604c8056 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 related	[flat|nested] 33+ messages in thread

* [PATCH v2 12/12] mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented
  2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
                   ` (10 preceding siblings ...)
  2026-11-02  3:25 ` [PATCH v2 11/12] mm: hugetlb_vmemmap: unify vmemmap range restoration Muchun Song
@ 2026-11-02  3:25 ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  11 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  3:25 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>
---
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 370e604c8056..c04f53f708d2 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 related	[flat|nested] 33+ messages in thread

* Re: [PATCH v2 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling
  2026-11-02  3:25 ` [PATCH v2 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling Muchun Song
@ 2026-11-02  3:52   ` sashiko-bot
  2026-11-02  6:12     ` Muchun Song
  2026-11-02 10:18   ` Mike Rapoport
  1 sibling, 1 reply; 33+ messages in thread
From: sashiko-bot @ 2026-11-02  3:52 UTC (permalink / raw)
  To: Muchun Song, Oscar Salvador, Andrew Morton, Mike Rapoport
  Cc: Qi Zheng, Lance Yang, James Houghton, David Hildenbrand,
	Muchun Song, linux-mm

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] mm: hugetlb_vmemmap: uninitialized tail page read in vmemmap_optimize_pte()
--

commit 32511c4a4d8ec3d45e0f629f89666a78dc9372ee
Author: Muchun Song <muchun.song@linux.dev>

mm: hugetlb_vmemmap: simplify shared tail page handling

This commit removes the vmemmap_tail field from struct vmemmap_remap_walk and
updates vmemmap_optimize_pte() and vmemmap_restore_pte() to derive the shared
tail page directly from the folio being processed.

> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
> index 5e84004db4b87..d0af251aa533c 100644
> --- a/mm/hugetlb_vmemmap.c
> +++ b/mm/hugetlb_vmemmap.c

[ ... ]

> @@ -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);
>  	}

[Severity: High]
Can calling page_folio(page), folio_order(folio), and
folio_vmemmap_shared_tail_page(folio) on every PTE in vmemmap_optimize_pte()
read uninitialized tail struct pages for bootmem gigantic hugepages?

During early boot in alloc_bootmem_huge_page(), gigantic hugepages mark all
tail pages [pfn + 1, pfn + nr_pages) with memblock_reserved_mark_noinit(), so
memmap_init_reserved_pages() skips initializing their struct pages:

mm/hugetlb.c:alloc_bootmem_huge_page() {
    ...
	if (hugetlb_vmemmap_optimizable(h))
		hugetlb_vmemmap_optimize_bootmem_page(pfn, huge_page_order(h));
    ...
	memblock_reserved_mark_noinit(PFN_PHYS(pfn + 1),
				      (nr_pages - 1) * PAGE_SIZE);
}

When a bootmem gigantic hugepage is not aligned to PAGES_PER_SECTION (for
example, when nr_pages < PAGES_PER_SECTION, such as on 64-bit LoongArch with
16KB base pages where SECTION_SIZE_BITS is 29 and PMD_SHIFT is 25 with
CONFIG_ARCH_FORCE_MAX_ORDER=10), section_set_compound_order_range() returns
early without setting section->compound_page_order:

include/linux/vmemmap-optimization.h:section_set_compound_order_range() {
	unsigned long section_nr = pfn_to_section_nr(pfn);

	if (!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION))
		return;
    ...
}

In that case, sparse_init() allocates regular vmemmap pages via
memmap_alloc(), leaving them uninitialized (or filled with 0xff under
CONFIG_DEBUG_VM).

Later, gather_bootmem_prealloc_node() initializes only the first
VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES struct pages (covering only the first
vmemmap PTE), and prep_and_add_bootmem_folios() defers initializing the
remaining tail struct pages until after hugetlb_vmemmap_optimize_folios()
runs and fails:

mm/hugetlb.c:gather_bootmem_prealloc_node() {
    ...
		hugetlb_folio_init_vmemmap(folio, h,
					   VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES);
		init_new_hugetlb_folio(folio);

		if (vmemmap_optimizable_order(pfn_to_section_compound_order(pfn)))
			folio_set_hugetlb_vmemmap_optimized(folio);
    ...
}

mm/hugetlb.c:prep_and_add_bootmem_folios() {
	/* Send list for bulk vmemmap optimization processing */
	hugetlb_vmemmap_optimize_folios(h, folio_list);

	list_for_each_entry_safe(folio, tmp_f, folio_list, lru) {
		if (!folio_test_hugetlb_vmemmap_optimized(folio)) {
			hugetlb_folio_init_tail_vmemmap(folio, h,
					VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES,
					pages_per_huge_page(h));
		}
    ...
}

Previously, __hugetlb_vmemmap_optimize_folio() derived vmemmap_tail upfront
from the initialized head folio, and vmemmap_optimize_pte() checked
walk->nr_walked == 0 without inspecting tail struct pages.

With this change, on the second and subsequent PTEs (where addr >=
vmemmap_start + VMEMMAP_OPTIMIZATION_SIZE), page->compound_info in the tail
struct page at addr has not been initialized by prep_compound_tail() yet:

1. If the raw vmemmap backing pages are zeroed, page_folio(page) returns page
itself rather than the head folio, folio_order(folio) returns 0, and
vmemmap_optimizable_pfn() returns false. vmemmap_optimize_pte() then falls
back to vmemmap_dup_page_from_list(src, walk->vmemmap_pages) for every tail
PTE, rotating the displaced vmemmap pages across the tail PTEs instead of
mapping the shared tail page, while leaving HPG_vmemmap_optimized set so
prep_and_add_bootmem_folios() skips hugetlb_folio_init_tail_vmemmap().

2. If CONFIG_DEBUG_VM is enabled, page_init_poison() fills the uninitialized
tail struct pages with 0xff. _compound_head(page) then evaluates to the
poisoned tail page itself, and folio_order(folio) reads 0xff, leading to an
out-of-bounds shift in vmemmap_optimizable_pfn() and an out-of-bounds
node_zones access in folio_zone(folio).

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261102032525.3597743-1-songmuchun@bytedance.com?part=7


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

* Re: [PATCH v2 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling
  2026-11-02  3:52   ` sashiko-bot
@ 2026-11-02  6:12     ` Muchun Song
  2026-11-02 13:48       ` Huacai Chen
  0 siblings, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02  6:12 UTC (permalink / raw)
  To: sashiko-reviews
  Cc: Muchun Song, Oscar Salvador, Andrew Morton, Mike Rapoport,
	Qi Zheng, Lance Yang, James Houghton, David Hildenbrand, linux-mm,
	Huacai Chen, WANG Xuerui, loongarch



> On Nov 2, 2026, at 11:52, sashiko-bot@kernel.org wrote:
> 
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] mm: hugetlb_vmemmap: uninitialized tail page read in vmemmap_optimize_pte()
> --
> 
> commit 32511c4a4d8ec3d45e0f629f89666a78dc9372ee
> Author: Muchun Song <muchun.song@linux.dev>
> 
> mm: hugetlb_vmemmap: simplify shared tail page handling
> 
> This commit removes the vmemmap_tail field from struct vmemmap_remap_walk and
> updates vmemmap_optimize_pte() and vmemmap_restore_pte() to derive the shared
> tail page directly from the folio being processed.
> 
>> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
>> index 5e84004db4b87..d0af251aa533c 100644
>> --- a/mm/hugetlb_vmemmap.c
>> +++ b/mm/hugetlb_vmemmap.c
> 
> [ ... ]
> 
>> @@ -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);
>> 	}
> 
> [Severity: High]
> Can calling page_folio(page), folio_order(folio), and
> folio_vmemmap_shared_tail_page(folio) on every PTE in vmemmap_optimize_pte()
> read uninitialized tail struct pages for bootmem gigantic hugepages?
> 
> During early boot in alloc_bootmem_huge_page(), gigantic hugepages mark all
> tail pages [pfn + 1, pfn + nr_pages) with memblock_reserved_mark_noinit(), so
> memmap_init_reserved_pages() skips initializing their struct pages:
> 
> mm/hugetlb.c:alloc_bootmem_huge_page() {
>    ...
> 	if (hugetlb_vmemmap_optimizable(h))
> 		hugetlb_vmemmap_optimize_bootmem_page(pfn, huge_page_order(h));
>    ...
> 	memblock_reserved_mark_noinit(PFN_PHYS(pfn + 1),
> 				      (nr_pages - 1) * PAGE_SIZE);
> }
> 
> When a bootmem gigantic hugepage is not aligned to PAGES_PER_SECTION (for
> example, when nr_pages < PAGES_PER_SECTION, such as on 64-bit LoongArch with
> 16KB base pages where SECTION_SIZE_BITS is 29 and PMD_SHIFT is 25 with
> CONFIG_ARCH_FORCE_MAX_ORDER=10), section_set_compound_order_range() returns

This issue does not occur with the default LoongArch configuration. For
16KB base pages, LoongArch defaults ARCH_FORCE_MAX_ORDER to 11, which
matches HUGETLB_PAGE_ORDER.

The reported case requires manually overriding CONFIG_ARCH_FORCE_MAX_ORDER
to 10 while keeping HugeTLB enabled. This creates an **invalid configuration**
that Kconfig currently permits:

HUGETLB_PAGE_ORDER	= 11
MAX_FOLIO_ORDER		= 10

LoongArch does not select ARCH_HAS_GIGANTIC_PAGE, so the generic MM code
assumes that folios cannot exceed MAX_PAGE_ORDER. The forced configuration
violates that assumption and is already diagnosed by:

	WARN_ON(order > MAX_FOLIO_ORDER);

in hugetlb_add_hstate().

Therefore, this is not a regression in a supported LoongArch configuration,
and I don't think this series needs to handle it. The proper fix is a separate
LoongArch Kconfig change preventing ARCH_FORCE_MAX_ORDER from being set below
the HugeTLB order when HugeTLB is enabled.

Cc LoongArch maintainers.

Thanks,
Muchun

> early without setting section->compound_page_order:
> 
> include/linux/vmemmap-optimization.h:section_set_compound_order_range() {
> 	unsigned long section_nr = pfn_to_section_nr(pfn);
> 
> 	if (!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION))
> 		return;
>    ...
> }
> 
> In that case, sparse_init() allocates regular vmemmap pages via
> memmap_alloc(), leaving them uninitialized (or filled with 0xff under
> CONFIG_DEBUG_VM).
> 
> Later, gather_bootmem_prealloc_node() initializes only the first
> VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES struct pages (covering only the first
> vmemmap PTE), and prep_and_add_bootmem_folios() defers initializing the
> remaining tail struct pages until after hugetlb_vmemmap_optimize_folios()
> runs and fails:
> 
> mm/hugetlb.c:gather_bootmem_prealloc_node() {
>    ...
> 		hugetlb_folio_init_vmemmap(folio, h,
> 					   VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES);
> 		init_new_hugetlb_folio(folio);
> 
> 		if (vmemmap_optimizable_order(pfn_to_section_compound_order(pfn)))
> 			folio_set_hugetlb_vmemmap_optimized(folio);
>    ...
> }
> 
> mm/hugetlb.c:prep_and_add_bootmem_folios() {
> 	/* Send list for bulk vmemmap optimization processing */
> 	hugetlb_vmemmap_optimize_folios(h, folio_list);
> 
> 	list_for_each_entry_safe(folio, tmp_f, folio_list, lru) {
> 		if (!folio_test_hugetlb_vmemmap_optimized(folio)) {
> 			hugetlb_folio_init_tail_vmemmap(folio, h,
> 					VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES,
> 					pages_per_huge_page(h));
> 		}
>    ...
> }
> 
> Previously, __hugetlb_vmemmap_optimize_folio() derived vmemmap_tail upfront
> from the initialized head folio, and vmemmap_optimize_pte() checked
> walk->nr_walked == 0 without inspecting tail struct pages.
> 
> With this change, on the second and subsequent PTEs (where addr >=
> vmemmap_start + VMEMMAP_OPTIMIZATION_SIZE), page->compound_info in the tail
> struct page at addr has not been initialized by prep_compound_tail() yet:
> 
> 1. If the raw vmemmap backing pages are zeroed, page_folio(page) returns page
> itself rather than the head folio, folio_order(folio) returns 0, and
> vmemmap_optimizable_pfn() returns false. vmemmap_optimize_pte() then falls
> back to vmemmap_dup_page_from_list(src, walk->vmemmap_pages) for every tail
> PTE, rotating the displaced vmemmap pages across the tail PTEs instead of
> mapping the shared tail page, while leaving HPG_vmemmap_optimized set so
> prep_and_add_bootmem_folios() skips hugetlb_folio_init_tail_vmemmap().
> 
> 2. If CONFIG_DEBUG_VM is enabled, page_init_poison() fills the uninitialized
> tail struct pages with 0xff. _compound_head(page) then evaluates to the
> poisoned tail page itself, and folio_order(folio) reads 0xff, leading to an
> out-of-bounds shift in vmemmap_optimizable_pfn() and an out-of-bounds
> node_zones access in folio_zone(folio).
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20261102032525.3597743-1-songmuchun@bytedance.com?part=7



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

* Re: [PATCH v2 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros
  2026-11-02  3:25 ` [PATCH v2 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  0 siblings, 0 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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 Mon, 02 Nov 2026 11:25:14 +0800, Muchun Song <songmuchun@bytedance.com> 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.
> 
> 
> [...]

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

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH
  2026-11-02  3:25 ` [PATCH v2 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  2026-11-02 11:30     ` Muchun Song
  2026-11-02 12:28     ` Muchun Song
  0 siblings, 2 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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

Hi Muchun,

> 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 4b73d6ec8dcea..63e899cfaac3a 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.

Are we certain that *all* modern MMUs for *all* architecures that 
support hugetlb are fine with skipping TLB flush after a split?

AFAIU, x86::set_memory always flushes TLBs after a large leaf is split.

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling
  2026-11-02  3:25 ` [PATCH v2 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  0 siblings, 0 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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 Mon, 02 Nov 2026 11:25:16 +0800, Muchun Song <songmuchun@bytedance.com> 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.
> 
> [...]

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

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation
  2026-11-02  3:25 ` [PATCH v2 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  2026-11-02 12:08     ` Muchun Song
  2026-11-02 12:34     ` Muchun Song
  0 siblings, 2 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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

Hi Muchun,

> 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>
> ---
> v2:
> - Collect Reviewed-by from Lance Yang and Qi Zheng
> ---
>  mm/hugetlb_vmemmap.c | 39 ++++++++++++++++++---------------------
>  1 file changed, 18 insertions(+), 21 deletions(-)
>
> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
> index 0e5b2b182d1bf..4b20a8efc0678 100644
> --- a/mm/hugetlb_vmemmap.c
> +++ b/mm/hugetlb_vmemmap.c
> @@ -334,28 +334,26 @@ 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)

Please indent with two tabs, like David likes :)
Applies throughout the series.

>  {
> -	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);
> -	struct page *page, *next;
> -	int i;
> +	LIST_HEAD(local);

Can we use a better name here please? E.g new_pages?

>  
> -	for (i = 0; i < nr_pages; i++) {
> -		page = alloc_pages_node(nid, gfp_mask, 0);
> -		if (!page)
> -			goto out;
> -		list_add(&page->lru, list);
> +	for (unsigned int i = 0; i < nr_pages; i++) {
> +		struct page *page;
> +
> +		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
> +		if (!page) {

I think goto err_do_cleanup is cleaner than ad-hok cleanups

> +			memmap_pages_add(i);

This is confusing. Updating counters only to use
free_vmemmap_page_list() seems worse choice than walking the list and
__free_page().

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation
  2026-11-02  3:25 ` [PATCH v2 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  0 siblings, 0 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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 Mon, 02 Nov 2026 11:25:18 +0800, Muchun Song <songmuchun@bytedance.com> 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.
> 
> [...]

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

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte()
  2026-11-02  3:25 ` [PATCH v2 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte() Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  0 siblings, 0 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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 Mon, 02 Nov 2026 11:25:19 +0800, Muchun Song <songmuchun@bytedance.com> 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.
> 
> [...]

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

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling
  2026-11-02  3:25 ` [PATCH v2 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling Muchun Song
  2026-11-02  3:52   ` sashiko-bot
@ 2026-11-02 10:18   ` Mike Rapoport
  1 sibling, 0 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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

> 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>

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper
  2026-11-02  3:25 ` [PATCH v2 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  0 siblings, 0 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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

> 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>

With the comments for patch 4 addressed

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

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 09/12] mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented
  2026-11-02  3:25 ` [PATCH v2 09/12] mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  0 siblings, 0 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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 Mon, 02 Nov 2026 11:25:22 +0800, Muchun Song <songmuchun@bytedance.com> wrote:
> 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.
> 
> [...]

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

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 10/12] mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented
  2026-11-02  3:25 ` [PATCH v2 10/12] mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  0 siblings, 0 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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 Mon, 02 Nov 2026 11:25:23 +0800, Muchun Song <songmuchun@bytedance.com> wrote:
> 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.
> 
> [...]

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

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 11/12] mm: hugetlb_vmemmap: unify vmemmap range restoration
  2026-11-02  3:25 ` [PATCH v2 11/12] mm: hugetlb_vmemmap: unify vmemmap range restoration Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  0 siblings, 0 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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 Mon, 02 Nov 2026 11:25:24 +0800, Muchun Song <songmuchun@bytedance.com> wrote:
> 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.
> 
> [...]

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

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 12/12] mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented
  2026-11-02  3:25 ` [PATCH v2 12/12] mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented Muchun Song
@ 2026-11-02 10:18   ` Mike Rapoport
  0 siblings, 0 replies; 33+ messages in thread
From: Mike Rapoport @ 2026-11-02 10:18 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 Mon, 02 Nov 2026 11:25:25 +0800, Muchun Song <songmuchun@bytedance.com> wrote:
> 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.
> 
> [...]

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

-- 
Sincerely yours,
Mike.



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

* Re: [PATCH v2 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH
  2026-11-02 10:18   ` Mike Rapoport
@ 2026-11-02 11:30     ` Muchun Song
  2026-11-02 12:28     ` Muchun Song
  1 sibling, 0 replies; 33+ messages in thread
From: Muchun Song @ 2026-11-02 11:30 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Muchun Song, Oscar Salvador, Andrew Morton, Qi Zheng, Lance Yang,
	James Houghton, David Hildenbrand, linux-mm, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3268 bytes --]



> On Nov 2, 2026, at 18:18, Mike Rapoport <rppt@kernel.org> wrote:
> 
> Hi Muchun,

Hi Mike,

> 
>> 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 4b73d6ec8dcea..63e899cfaac3a 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.
> 
> Are we certain that *all* modern MMUs for *all* architecures that 
> support hugetlb are fine with skipping TLB flush after a split?

I think this is the case on architectures that currently enable
ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP. Of course, the point I want to
further clarify is that the "skip" action here is not really a skip,
but rather a "defer"—the TLB is subsequently flushed in a single batch.

Thanks for your review.

Muchun.

> 
> AFAIU, x86::set_memory always flushes TLBs after a large leaf is split.
> 
> -- 
> Sincerely yours,
> Mike.


[-- Attachment #2: Type: text/html, Size: 12375 bytes --]

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

* Re: [PATCH v2 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation
  2026-11-02 10:18   ` Mike Rapoport
@ 2026-11-02 12:08     ` Muchun Song
  2026-11-02 12:34     ` Muchun Song
  1 sibling, 0 replies; 33+ messages in thread
From: Muchun Song @ 2026-11-02 12:08 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Muchun Song, Oscar Salvador, Andrew Morton, Qi Zheng, Lance Yang,
	James Houghton, David Hildenbrand, linux-mm, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 3741 bytes --]



> On Nov 2, 2026, at 18:18, Mike Rapoport <rppt@kernel.org> wrote:
> 
> Hi Muchun,

Hi,

> 
>> 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>
>> ---
>> v2:
>> - Collect Reviewed-by from Lance Yang and Qi Zheng
>> ---
>> mm/hugetlb_vmemmap.c | 39 ++++++++++++++++++---------------------
>> 1 file changed, 18 insertions(+), 21 deletions(-)
>> 
>> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
>> index 0e5b2b182d1bf..4b20a8efc0678 100644
>> --- a/mm/hugetlb_vmemmap.c
>> +++ b/mm/hugetlb_vmemmap.c
>> @@ -334,28 +334,26 @@ 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)
> 
> Please indent with two tabs, like David likes :)
> Applies throughout the series.

Sure. Should we update scripts/checkpatch.pl in the future for MM,
since this script seems to favor the alignment approach?

> 
>> {
>> -	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);
>> -	struct page *page, *next;
>> -	int i;
>> +	LIST_HEAD(local);
> 
> Can we use a better name here please? E.g new_pages?

Yeah, make sense.

> 
>> 
>> -	for (i = 0; i < nr_pages; i++) {
>> -		page = alloc_pages_node(nid, gfp_mask, 0);
>> -		if (!page)
>> -			goto out;
>> -		list_add(&page->lru, list);
>> +	for (unsigned int i = 0; i < nr_pages; i++) {
>> +		struct page *page;
>> +
>> +		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
>> +		if (!page) {
> 
> I think goto err_do_cleanup is cleaner than ad-hok cleanups

I’ll use goto style. Either is OK for me.

> 
>> +			memmap_pages_add(i);
> 
> This is confusing. Updating counters only to use
> free_vmemmap_page_list() seems worse choice than walking the list and
> __free_page().

I think the memmap accounting should be done right after each successful
allocation — that way, using free_vmemmap_page_list() becomes more natural
and simple.

Somthing like:

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index c04f53f708d2..cab9ec14873d 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -207,14 +207,13 @@ static int alloc_vmemmap_page_list(struct list_head *head, unsigned int nr_pages
 
                page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
                if (!page) {
-                       memmap_pages_add(i);
                        free_vmemmap_page_list(&local);
                        return -ENOMEM;
                }
+               memmap_pages_add(1);
                list_add(&page->lru, &local);
        }
        list_splice(&local, head);
-       memmap_pages_add(nr_pages);
 
        return 0;
 }

Thanks for your review, Mike.

Muchun


> 
> -- 
> Sincerely yours,
> Mike.


[-- Attachment #2: Type: text/html, Size: 20467 bytes --]

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

* Re: [PATCH v2 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02 11:30     ` Muchun Song
@ 2026-11-02 12:28     ` Muchun Song
  1 sibling, 0 replies; 33+ messages in thread
From: Muchun Song @ 2026-11-02 12:28 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Muchun Song, Oscar Salvador, Andrew Morton, Qi Zheng, Lance Yang,
	James Houghton, David Hildenbrand, linux-mm, linux-kernel



> On Nov 2, 2026, at 18:18, Mike Rapoport <rppt@kernel.org> wrote:
> 
> Hi Muchun,

Hi,

I didn't realized that I'm not using a plain text in my last mail.
It’s my email client's fault. Let me email it again.

> 
>> 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 4b73d6ec8dcea..63e899cfaac3a 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.
> 
> Are we certain that *all* modern MMUs for *all* architecures that 
> support hugetlb are fine with skipping TLB flush after a split?

I think this is the case on architectures that currently enable
ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP. Of course, the point I want to
further clarify is that the "skip" action here is not really a skip,
but rather a "defer"—the TLB is subsequently flushed in a single batch.

Thanks for your review.

Muchun.

> 
> AFAIU, x86::set_memory always flushes TLBs after a large leaf is split.
> 
> -- 
> Sincerely yours,
> Mike.




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

* Re: [PATCH v2 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation
  2026-11-02 10:18   ` Mike Rapoport
  2026-11-02 12:08     ` Muchun Song
@ 2026-11-02 12:34     ` Muchun Song
  2026-11-02 17:57       ` Mike Rapoport
  1 sibling, 1 reply; 33+ messages in thread
From: Muchun Song @ 2026-11-02 12:34 UTC (permalink / raw)
  To: Mike Rapoport
  Cc: Muchun Song, Oscar Salvador, Andrew Morton, Qi Zheng, Lance Yang,
	James Houghton, David Hildenbrand, linux-mm, linux-kernel



> On Nov 2, 2026, at 18:18, Mike Rapoport <rppt@kernel.org> wrote:
> 
> Hi Muchun,

Hi,

It's my email client's fault. Let me email it again.

> 
>> 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>
>> ---
>> v2:
>> - Collect Reviewed-by from Lance Yang and Qi Zheng
>> ---
>> mm/hugetlb_vmemmap.c | 39 ++++++++++++++++++---------------------
>> 1 file changed, 18 insertions(+), 21 deletions(-)
>> 
>> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
>> index 0e5b2b182d1bf..4b20a8efc0678 100644
>> --- a/mm/hugetlb_vmemmap.c
>> +++ b/mm/hugetlb_vmemmap.c
>> @@ -334,28 +334,26 @@ 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)
> 
> Please indent with two tabs, like David likes :)
> Applies throughout the series.

Sure. Should we update scripts/checkpatch.pl in the future for MM,
since this script seems to favor the alignment approach?

> 
>> {
>> -	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);
>> -	struct page *page, *next;
>> -	int i;
>> +	LIST_HEAD(local);
> 
> Can we use a better name here please? E.g new_pages?

Yahs, make sense.

> 
>> 
>> -	for (i = 0; i < nr_pages; i++) {
>> -		page = alloc_pages_node(nid, gfp_mask, 0);
>> -		if (!page)
>> -			goto out;
>> -		list_add(&page->lru, list);
>> +	for (unsigned int i = 0; i < nr_pages; i++) {
>> +		struct page *page;
>> +
>> +		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
>> +		if (!page) {
> 
> I think goto err_do_cleanup is cleaner than ad-hok cleanups

I’ll use goto style, either is OK for me.

> 
>> +			memmap_pages_add(i);
> 
> This is confusing. Updating counters only to use
> free_vmemmap_page_list() seems worse choice than walking the list and
> __free_page().

I think the memmap accounting should be done right after each successful
allocation — that way, using free_vmemmap_page_list() becomes more natural
and simple.

Somthing like:

diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index c04f53f708d2..cab9ec14873d 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -207,14 +207,13 @@ static int alloc_vmemmap_page_list(struct list_head *head, unsigned int nr_pages
 
                page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
                if (!page) {
-                       memmap_pages_add(i);
                        free_vmemmap_page_list(&local);
                        return -ENOMEM;
                }
+               memmap_pages_add(1);
                list_add(&page->lru, &local);
        }
        list_splice(&local, head);
-       memmap_pages_add(nr_pages);
 
        return 0;
 }

Thanks for your review, Mike.

Muchun

> 
> -- 
> Sincerely yours,
> Mike.




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

* Re: [PATCH v2 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling
  2026-11-02  6:12     ` Muchun Song
@ 2026-11-02 13:48       ` Huacai Chen
  0 siblings, 0 replies; 33+ messages in thread
From: Huacai Chen @ 2026-11-02 13:48 UTC (permalink / raw)
  To: Muchun Song
  Cc: sashiko-reviews, Muchun Song, Oscar Salvador, Andrew Morton,
	Mike Rapoport, Qi Zheng, Lance Yang, James Houghton,
	David Hildenbrand, linux-mm, WANG Xuerui, loongarch

On Mon, Nov 2, 2026 at 2:12 PM Muchun Song <muchun.song@linux.dev> wrote:
>
>
>
> > On Nov 2, 2026, at 11:52, sashiko-bot@kernel.org wrote:
> >
> > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> > - [High] mm: hugetlb_vmemmap: uninitialized tail page read in vmemmap_optimize_pte()
> > --
> >
> > commit 32511c4a4d8ec3d45e0f629f89666a78dc9372ee
> > Author: Muchun Song <muchun.song@linux.dev>
> >
> > mm: hugetlb_vmemmap: simplify shared tail page handling
> >
> > This commit removes the vmemmap_tail field from struct vmemmap_remap_walk and
> > updates vmemmap_optimize_pte() and vmemmap_restore_pte() to derive the shared
> > tail page directly from the folio being processed.
> >
> >> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
> >> index 5e84004db4b87..d0af251aa533c 100644
> >> --- a/mm/hugetlb_vmemmap.c
> >> +++ b/mm/hugetlb_vmemmap.c
> >
> > [ ... ]
> >
> >> @@ -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);
> >>      }
> >
> > [Severity: High]
> > Can calling page_folio(page), folio_order(folio), and
> > folio_vmemmap_shared_tail_page(folio) on every PTE in vmemmap_optimize_pte()
> > read uninitialized tail struct pages for bootmem gigantic hugepages?
> >
> > During early boot in alloc_bootmem_huge_page(), gigantic hugepages mark all
> > tail pages [pfn + 1, pfn + nr_pages) with memblock_reserved_mark_noinit(), so
> > memmap_init_reserved_pages() skips initializing their struct pages:
> >
> > mm/hugetlb.c:alloc_bootmem_huge_page() {
> >    ...
> >       if (hugetlb_vmemmap_optimizable(h))
> >               hugetlb_vmemmap_optimize_bootmem_page(pfn, huge_page_order(h));
> >    ...
> >       memblock_reserved_mark_noinit(PFN_PHYS(pfn + 1),
> >                                     (nr_pages - 1) * PAGE_SIZE);
> > }
> >
> > When a bootmem gigantic hugepage is not aligned to PAGES_PER_SECTION (for
> > example, when nr_pages < PAGES_PER_SECTION, such as on 64-bit LoongArch with
> > 16KB base pages where SECTION_SIZE_BITS is 29 and PMD_SHIFT is 25 with
> > CONFIG_ARCH_FORCE_MAX_ORDER=10), section_set_compound_order_range() returns
>
> This issue does not occur with the default LoongArch configuration. For
> 16KB base pages, LoongArch defaults ARCH_FORCE_MAX_ORDER to 11, which
> matches HUGETLB_PAGE_ORDER.
>
> The reported case requires manually overriding CONFIG_ARCH_FORCE_MAX_ORDER
> to 10 while keeping HugeTLB enabled. This creates an **invalid configuration**
> that Kconfig currently permits:
>
> HUGETLB_PAGE_ORDER      = 11
> MAX_FOLIO_ORDER         = 10
>
> LoongArch does not select ARCH_HAS_GIGANTIC_PAGE, so the generic MM code
> assumes that folios cannot exceed MAX_PAGE_ORDER. The forced configuration
> violates that assumption and is already diagnosed by:
>
>         WARN_ON(order > MAX_FOLIO_ORDER);
>
> in hugetlb_add_hstate().
>
> Therefore, this is not a regression in a supported LoongArch configuration,
> and I don't think this series needs to handle it. The proper fix is a separate
> LoongArch Kconfig change preventing ARCH_FORCE_MAX_ORDER from being set below
> the HugeTLB order when HugeTLB is enabled.
Yes, this isn't a regression, the problem existed before this series.

I suggest change BUILD_BUG_ON_INVALID(HUGETLB_PAGE_ORDER >
MAX_FOLIO_ORDER) to MAYBE_BUILD_BUG_ON(HUGETLB_PAGE_ORDER >
MAX_FOLIO_ORDER) in mm/hugetlb.c, which cause a build error
immediately for invalid ARCH_FORCE_MAX_ORDER configurations.

Huacai

>
> Cc LoongArch maintainers.
>
> Thanks,
> Muchun
>
> > early without setting section->compound_page_order:
> >
> > include/linux/vmemmap-optimization.h:section_set_compound_order_range() {
> >       unsigned long section_nr = pfn_to_section_nr(pfn);
> >
> >       if (!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION))
> >               return;
> >    ...
> > }
> >
> > In that case, sparse_init() allocates regular vmemmap pages via
> > memmap_alloc(), leaving them uninitialized (or filled with 0xff under
> > CONFIG_DEBUG_VM).
> >
> > Later, gather_bootmem_prealloc_node() initializes only the first
> > VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES struct pages (covering only the first
> > vmemmap PTE), and prep_and_add_bootmem_folios() defers initializing the
> > remaining tail struct pages until after hugetlb_vmemmap_optimize_folios()
> > runs and fails:
> >
> > mm/hugetlb.c:gather_bootmem_prealloc_node() {
> >    ...
> >               hugetlb_folio_init_vmemmap(folio, h,
> >                                          VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES);
> >               init_new_hugetlb_folio(folio);
> >
> >               if (vmemmap_optimizable_order(pfn_to_section_compound_order(pfn)))
> >                       folio_set_hugetlb_vmemmap_optimized(folio);
> >    ...
> > }
> >
> > mm/hugetlb.c:prep_and_add_bootmem_folios() {
> >       /* Send list for bulk vmemmap optimization processing */
> >       hugetlb_vmemmap_optimize_folios(h, folio_list);
> >
> >       list_for_each_entry_safe(folio, tmp_f, folio_list, lru) {
> >               if (!folio_test_hugetlb_vmemmap_optimized(folio)) {
> >                       hugetlb_folio_init_tail_vmemmap(folio, h,
> >                                       VMEMMAP_OPTIMIZATION_NR_STRUCT_PAGES,
> >                                       pages_per_huge_page(h));
> >               }
> >    ...
> > }
> >
> > Previously, __hugetlb_vmemmap_optimize_folio() derived vmemmap_tail upfront
> > from the initialized head folio, and vmemmap_optimize_pte() checked
> > walk->nr_walked == 0 without inspecting tail struct pages.
> >
> > With this change, on the second and subsequent PTEs (where addr >=
> > vmemmap_start + VMEMMAP_OPTIMIZATION_SIZE), page->compound_info in the tail
> > struct page at addr has not been initialized by prep_compound_tail() yet:
> >
> > 1. If the raw vmemmap backing pages are zeroed, page_folio(page) returns page
> > itself rather than the head folio, folio_order(folio) returns 0, and
> > vmemmap_optimizable_pfn() returns false. vmemmap_optimize_pte() then falls
> > back to vmemmap_dup_page_from_list(src, walk->vmemmap_pages) for every tail
> > PTE, rotating the displaced vmemmap pages across the tail PTEs instead of
> > mapping the shared tail page, while leaving HPG_vmemmap_optimized set so
> > prep_and_add_bootmem_folios() skips hugetlb_folio_init_tail_vmemmap().
> >
> > 2. If CONFIG_DEBUG_VM is enabled, page_init_poison() fills the uninitialized
> > tail struct pages with 0xff. _compound_head(page) then evaluates to the
> > poisoned tail page itself, and folio_order(folio) reads 0xff, leading to an
> > out-of-bounds shift in vmemmap_optimizable_pfn() and an out-of-bounds
> > node_zones access in folio_zone(folio).
> >
> > --
> > Sashiko AI review · https://sashiko.dev/#/patchset/20261102032525.3597743-1-songmuchun@bytedance.com?part=7
>


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

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

On Mon, Nov 02, 2026 at 08:34:03PM +0800, Muchun Song wrote:
> > On Nov 2, 2026, at 18:18, Mike Rapoport <rppt@kernel.org> wrote:
> > 
> > Hi Muchun,
> 
> Hi,
> 
> It's my email client's fault. Let me email it again.

Things happen :)

> >> 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>
> >> ---
> >> v2:
> >> - Collect Reviewed-by from Lance Yang and Qi Zheng
> >> ---
> >> mm/hugetlb_vmemmap.c | 39 ++++++++++++++++++---------------------
> >> 1 file changed, 18 insertions(+), 21 deletions(-)
> >> 
> >> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
> >> index 0e5b2b182d1bf..4b20a8efc0678 100644
> >> --- a/mm/hugetlb_vmemmap.c
> >> +++ b/mm/hugetlb_vmemmap.c
> >> @@ -334,28 +334,26 @@ 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)
> > 
> > Please indent with two tabs, like David likes :)
> > Applies throughout the series.
> 
> Sure. Should we update scripts/checkpatch.pl in the future for MM,
> since this script seems to favor the alignment approach?

Yes, makes sense to me. 
 
> >> -	for (i = 0; i < nr_pages; i++) {
> >> -		page = alloc_pages_node(nid, gfp_mask, 0);
> >> -		if (!page)
> >> -			goto out;
> >> -		list_add(&page->lru, list);
> >> +	for (unsigned int i = 0; i < nr_pages; i++) {
> >> +		struct page *page;
> >> +
> >> +		page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
> >> +		if (!page) {
> > 
> > I think goto err_do_cleanup is cleaner than ad-hok cleanups
> 
> I’ll use goto style, either is OK for me.
> 
> > 
> >> +			memmap_pages_add(i);
> > 
> > This is confusing. Updating counters only to use
> > free_vmemmap_page_list() seems worse choice than walking the list and
> > __free_page().
> 
> I think the memmap accounting should be done right after each successful
> allocation — that way, using free_vmemmap_page_list() becomes more natural
> and simple.

Why are you against using a loop with a plain __free_page()?

We know for sure it's not a reserved page and there is no need in
free_vmemmap_page().

And with memmap_pages_add() per page do a bunch of atomic_long_add()s
instead of one.

BTW, it would have been nice to avoid per-page counters update in
free_vmemmap_page_list() too, but that's not related to this series.
 
> Somthing like:
> 
> diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
> index c04f53f708d2..cab9ec14873d 100644
> --- a/mm/hugetlb_vmemmap.c
> +++ b/mm/hugetlb_vmemmap.c
> @@ -207,14 +207,13 @@ static int alloc_vmemmap_page_list(struct list_head *head, unsigned int nr_pages
>  
>                 page = alloc_pages_node(nid, GFP_KERNEL | __GFP_RETRY_MAYFAIL, 0);
>                 if (!page) {
> -                       memmap_pages_add(i);
>                         free_vmemmap_page_list(&local);
>                         return -ENOMEM;
>                 }
> +               memmap_pages_add(1);
>                 list_add(&page->lru, &local);
>         }
>         list_splice(&local, head);
> -       memmap_pages_add(nr_pages);
>  
>         return 0;
>  }
> 
> Thanks for your review, Mike.
> 
> Muchun
> 
> > 
> > -- 
> > Sincerely yours,
> > Mike.
> 
> 

-- 
Sincerely yours,
Mike.


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

end of thread, other threads:[~2026-11-02 17:57 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02  3:25 [PATCH v2 00/12] mm: Decouple folio-based vmemmap optimization from HugeTLB Muchun Song
2026-11-02  3:25 ` [PATCH v2 01/12] mm: hugetlb_vmemmap: remove HugeTLB-specific vmemmap optimization macros Muchun Song
2026-11-02 10:18   ` Mike Rapoport
2026-11-02  3:25 ` [PATCH v2 02/12] mm: hugetlb_vmemmap: unify VMEMMAP_{SPLIT,REMAP}_NO_TLB_FLUSH Muchun Song
2026-11-02 10:18   ` Mike Rapoport
2026-11-02 11:30     ` Muchun Song
2026-11-02 12:28     ` Muchun Song
2026-11-02  3:25 ` [PATCH v2 03/12] mm: hugetlb_vmemmap: simplify vmemmap head page handling Muchun Song
2026-11-02 10:18   ` Mike Rapoport
2026-11-02  3:25 ` [PATCH v2 04/12] mm: hugetlb_vmemmap: generalize vmemmap page allocation Muchun Song
2026-11-02 10:18   ` Mike Rapoport
2026-11-02 12:08     ` Muchun Song
2026-11-02 12:34     ` Muchun Song
2026-11-02 17:57       ` Mike Rapoport
2026-11-02  3:25 ` [PATCH v2 05/12] mm: hugetlb_vmemmap: unify writable vmemmap page preparation Muchun Song
2026-11-02 10:18   ` Mike Rapoport
2026-11-02  3:25 ` [PATCH v2 06/12] mm: hugetlb_vmemmap: rename vmemmap_remap_pte() to vmemmap_optimize_pte() Muchun Song
2026-11-02 10:18   ` Mike Rapoport
2026-11-02  3:25 ` [PATCH v2 07/12] mm: hugetlb_vmemmap: simplify shared tail page handling Muchun Song
2026-11-02  3:52   ` sashiko-bot
2026-11-02  6:12     ` Muchun Song
2026-11-02 13:48       ` Huacai Chen
2026-11-02 10:18   ` Mike Rapoport
2026-11-02  3:25 ` [PATCH v2 08/12] mm: hugetlb_vmemmap: move alloc_vmemmap_page_list() next to its free helper Muchun Song
2026-11-02 10:18   ` Mike Rapoport
2026-11-02  3:25 ` [PATCH v2 09/12] mm: hugetlb_vmemmap: make vmemmap optimization folio-oriented Muchun Song
2026-11-02 10:18   ` Mike Rapoport
2026-11-02  3:25 ` [PATCH v2 10/12] mm: hugetlb_vmemmap: make vmemmap restoration folio-oriented Muchun Song
2026-11-02 10:18   ` Mike Rapoport
2026-11-02  3:25 ` [PATCH v2 11/12] mm: hugetlb_vmemmap: unify vmemmap range restoration Muchun Song
2026-11-02 10:18   ` Mike Rapoport
2026-11-02  3:25 ` [PATCH v2 12/12] mm: hugetlb_vmemmap: make vmemmap PTE conversion folio-oriented Muchun Song
2026-11-02 10:18   ` Mike Rapoport

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