| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/04 19:38 | flow | patch-triage |
4mArgs:null Results: {
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE",
"USERFAULTFD"
],
"FocusSymbols": [
"madvise_collapse"
],
"Reasoning": "The patch refactors PTE checking logic for THP collapse into a new helper function collapse_check_pte(). It also modifies the behavior when a non-anonymous folio is encountered in an anonymous VMA, changing a VM_BUG_ON_FOLIO to a VM_WARN_ON_FOLIO and returning an error. Fuzzing madvise(MADV_COLLAPSE) and khugepaged can help verify that this refactoring doesn't introduce subtle regressions in PTE scanning or uninitialized variable usage.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/04 19:38 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 578a4da6083c5552c26f2a70fed8ae9b9a349f40\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Aug 4 19:38:57 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/admin-guide/mm/transhuge.rst b/Documentation/admin-guide/mm/transhuge.rst\nindex 16f37135ed80d..b187d618452f4 100644\n--- a/Documentation/admin-guide/mm/transhuge.rst\n+++ b/Documentation/admin-guide/mm/transhuge.rst\n@@ -224,7 +224,7 @@ khugepaged will be automatically started when any THP size is enabled\n (either of the per-size anon control or the top-level control are set\n to \"always\" or \"madvise\"), and it'll be automatically shutdown when\n all THP sizes are disabled (when both the per-size anon control and the\n-top-level control are \"never\")\n+top-level control are \"never\").\n \n process THP controls\n --------------------\n@@ -301,7 +301,9 @@ being replaced by a PMD mapping, or (2) physical pages replaced by one\n hugepage of various sizes (PMD-sized or mTHP). Each may happen independently,\n or together, depending on the type of memory and the failures that occur.\n As such, this value should be interpreted roughly as a sign of progress,\n-and counters in /proc/vmstat consulted for more accurate accounting)::\n+and counters in /proc/vmstat consulted for more accurate accounting.\n+Per-order mTHP collapse statistics are also available under\n+/sys/kernel/mm/transparent_hugepage/hugepages-\u003csize\u003ekB/stats/)::\n \n \t/sys/kernel/mm/transparent_hugepage/khugepaged/pages_collapsed\n \ndiff --git a/mm/khugepaged.c b/mm/khugepaged.c\nindex b237f6e7662a5..3338e9dc11dd0 100644\n--- a/mm/khugepaged.c\n+++ b/mm/khugepaged.c\n@@ -65,6 +65,12 @@ enum scan_result {\n \tSCAN_PAGE_DIRTY_OR_WRITEBACK,\n };\n \n+enum pte_check_result {\n+\tPTE_CHECK_SUCCEED,\n+\tPTE_CHECK_FAIL,\n+\tPTE_CHECK_CONTINUE,\n+};\n+\n #define CREATE_TRACE_POINTS\n #include \u003ctrace/events/huge_memory.h\u003e\n \n@@ -119,6 +125,20 @@ struct collapse_control {\n \tDECLARE_BITMAP(mthp_present_ptes, MAX_PTRS_PER_PTE);\n };\n \n+struct pte_check_context {\n+\tstruct collapse_control *cc;\n+\tstruct vm_area_struct *vma;\n+\tunsigned int order;\n+\tstruct folio *folio;\n+\tint none_or_zero;\n+\tint shared;\n+\tint unmapped;\n+\tenum scan_result result;\n+\tunsigned int max_ptes_none;\n+\tunsigned int max_ptes_swap;\n+\tunsigned int max_ptes_shared;\n+};\n+\n /**\n * struct khugepaged_scan - cursor for scanning\n * @mm_head: the head of the mm list to scan\n@@ -620,7 +640,7 @@ void __khugepaged_exit(struct mm_struct *mm)\n \t\t/*\n \t\t * This is required to serialize against\n \t\t * collapse_test_exit() (which is guaranteed to run\n-\t\t * under mmap sem read mode). Stop here (after we return all\n+\t\t * under mmap_lock read mode). Stop here (after we return all\n \t\t * pagetables will be destroyed) until khugepaged has finished\n \t\t * working on the pagetables under the mmap_lock.\n \t\t */\n@@ -629,6 +649,13 @@ void __khugepaged_exit(struct mm_struct *mm)\n \t}\n }\n \n+static void collapse_control_init_scan(struct collapse_control *cc)\n+{\n+\tmemset(cc-\u003enode_load, 0, sizeof(cc-\u003enode_load));\n+\tnodes_clear(cc-\u003ealloc_nmask);\n+\tbitmap_zero(cc-\u003emthp_present_ptes, MAX_PTRS_PER_PTE);\n+}\n+\n static void release_pte_folio(struct folio *folio)\n {\n \tnode_stat_mod_folio(folio,\n@@ -665,76 +692,156 @@ static void release_pte_pages(pte_t *pte, pte_t *_pte,\n \t}\n }\n \n+/*\n+ * pte_is_referenced() - Check for enough referenced PTEs to justify collapsing\n+ *\n+ * If collapse was initiated by khugepaged, check that the page has been\n+ * recently accessed (young pte) to justify collapsing the page.\n+ *\n+ * Return: true if the page has been recently accessed.\n+ */\n+static inline bool pte_is_referenced(struct collapse_control *cc, pte_t pteval,\n+\tstruct folio *folio, struct vm_area_struct *vma, unsigned long addr)\n+{\n+\treturn cc-\u003eis_khugepaged \u0026\u0026\n+\t (pte_young(pteval) || folio_test_young(folio) ||\n+\t\tfolio_test_referenced(folio) ||\n+\t\tmmu_notifier_test_young(vma-\u003evm_mm, addr));\n+}\n+\n+static void count_collapse_event(unsigned int order, enum vm_event_item vm_event,\n+\t\tenum mthp_stat_item mthp_event)\n+{\n+\tif (is_pmd_order(order))\n+\t\tcount_vm_event(vm_event);\n+\tcount_mthp_stat(order, mthp_event);\n+}\n+\n+/*\n+ * pte_check_fail() - A simple helper to set the pte_check_context result and\n+ * return PTE_CHECK_FAIL.\n+ */\n+static enum pte_check_result pte_check_fail(struct pte_check_context *ctx,\n+\t\tenum scan_result result)\n+{\n+\tctx-\u003eresult = result;\n+\treturn PTE_CHECK_FAIL;\n+}\n+\n+/*\n+ * collapse_check_pte() - Check if a PTE is suitable for collapse\n+ *\n+ * Check if a PTE is suitable for collapse based on the following criteria:\n+ * - max_pte_* values are not exceeded\n+ * - uffd is not active\n+ * - lazyfree properties are not present\n+ * - only anonymous pages are present\n+ *\n+ * a helper struct pte_check_context is used to pass and store relevant\n+ * information between the collapse_check_pte() function and the caller.\n+ *\n+ * Return: PTE_CHECK_SUCCEED if the PTE is suitable for collapse,\n+ * PTE_CHECK_FAIL if the PTE is not suitable for collapse,\n+ * PTE_CHECK_CONTINUE if the scan should continue to check the next PTE.\n+ */\n+static enum pte_check_result collapse_check_pte(pte_t pteval,\n+\t\tunsigned long addr, struct pte_check_context *ctx)\n+{\n+\tif (pte_none_or_zero(pteval)) {\n+\t\tif (++ctx-\u003enone_or_zero \u003e ctx-\u003emax_ptes_none) {\n+\t\t\tcount_collapse_event(ctx-\u003eorder, THP_SCAN_EXCEED_NONE_PTE,\n+\t\t\t\t\t MTHP_STAT_COLLAPSE_EXCEED_NONE);\n+\t\t\treturn pte_check_fail(ctx, SCAN_EXCEED_NONE_PTE);\n+\t\t}\n+\t\treturn PTE_CHECK_CONTINUE;\n+\t}\n+\tif (!pte_present(pteval)) {\n+\t\tif (ctx-\u003eunmapped == -1)\n+\t\t\treturn pte_check_fail(ctx, SCAN_PTE_NON_PRESENT);\n+\t\tif (++ctx-\u003eunmapped \u003e ctx-\u003emax_ptes_swap) {\n+\t\t\tcount_collapse_event(ctx-\u003eorder, THP_SCAN_EXCEED_SWAP_PTE,\n+\t\t\t\t\t MTHP_STAT_COLLAPSE_EXCEED_SWAP);\n+\t\t\treturn pte_check_fail(ctx, SCAN_EXCEED_SWAP_PTE);\n+\t\t}\n+\t\tif (pte_swp_uffd_any(pteval))\n+\t\t\treturn pte_check_fail(ctx, SCAN_PTE_UFFD);\n+\t\treturn PTE_CHECK_CONTINUE;\n+\t}\n+\t/*\n+\t * Don't collapse if any of the small PTEs are armed with uffd\n+\t * write protection. Marking the new huge pmd as write protected\n+\t * could bring userfault messages that fall outside of the\n+\t * registered range.\n+\t */\n+\tif (pte_uffd(pteval))\n+\t\treturn pte_check_fail(ctx, SCAN_PTE_UFFD);\n+\n+\tctx-\u003efolio = vm_normal_folio(ctx-\u003evma, addr, pteval);\n+\tif (unlikely(!ctx-\u003efolio) || unlikely(folio_is_zone_device(ctx-\u003efolio)))\n+\t\treturn pte_check_fail(ctx, SCAN_PAGE_NULL);\n+\n+\t/*\n+\t * If the vma has the VM_DROPPABLE flag, the collapse will\n+\t * preserve the lazyfree property without needing to skip.\n+\t */\n+\tif (ctx-\u003ecc-\u003eis_khugepaged \u0026\u0026 !(ctx-\u003evma-\u003evm_flags \u0026 VM_DROPPABLE) \u0026\u0026\n+\t folio_test_lazyfree(ctx-\u003efolio) \u0026\u0026 !pte_dirty(pteval))\n+\t\treturn pte_check_fail(ctx, SCAN_PAGE_LAZYFREE);\n+\n+\tif (!folio_test_anon(ctx-\u003efolio)) {\n+\t\tVM_WARN_ON_FOLIO(!folio_test_anon(ctx-\u003efolio), ctx-\u003efolio);\n+\t\treturn pte_check_fail(ctx, SCAN_PAGE_ANON);\n+\t}\n+\n+\tif (folio_maybe_mapped_shared(ctx-\u003efolio)) {\n+\t\t/*\n+\t\t * TODO: Support shared pages without leading to further\n+\t\t * mTHP collapses. Currently bringing in new pages via\n+\t\t * shared may cause a future higher order collapse on a\n+\t\t * rescan of the same range.\n+\t\t */\n+\t\tif (++ctx-\u003eshared \u003e ctx-\u003emax_ptes_shared) {\n+\t\t\tcount_collapse_event(ctx-\u003eorder, THP_SCAN_EXCEED_SHARED_PTE,\n+\t\t\t\t\t MTHP_STAT_COLLAPSE_EXCEED_SHARED);\n+\t\t\treturn pte_check_fail(ctx, SCAN_EXCEED_SHARED_PTE);\n+\t\t}\n+\t}\n+\n+\treturn PTE_CHECK_SUCCEED;\n+}\n+\n static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\n \t\tunsigned long start_addr, pte_t *pte, struct collapse_control *cc,\n \t\tunsigned int order, struct list_head *compound_pagelist)\n {\n-\tconst unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, order);\n-\tconst unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, order);\n \tconst unsigned long nr_pages = 1UL \u003c\u003c order;\n-\tstruct page *page = NULL;\n \tstruct folio *folio = NULL;\n \tunsigned long addr = start_addr;\n-\tpte_t *_pte;\n-\tint none_or_zero = 0, shared = 0, referenced = 0;\n+\tpte_t *_pte, pteval;\n+\tint referenced = 0;\n \tenum scan_result result = SCAN_FAIL;\n+\tenum pte_check_result pte_check;\n+\tstruct pte_check_context ctx = {\n+\t\t.cc = cc,\n+\t\t.vma = vma,\n+\t\t.order = order,\n+\t\t.unmapped = -1, /* don't check swap PTEs */\n+\t\t.max_ptes_none = collapse_max_ptes_none(cc, vma, order),\n+\t\t.max_ptes_shared = collapse_max_ptes_shared(cc, order),\n+\t};\n \n \tfor (_pte = pte; _pte \u003c pte + nr_pages;\n \t _pte++, addr += PAGE_SIZE) {\n-\t\tpte_t pteval = ptep_get(_pte);\n-\t\tif (pte_none_or_zero(pteval)) {\n-\t\t\tif (++none_or_zero \u003e max_ptes_none) {\n-\t\t\t\tresult = SCAN_EXCEED_NONE_PTE;\n-\t\t\t\tif (is_pmd_order(order))\n-\t\t\t\t\tcount_vm_event(THP_SCAN_EXCEED_NONE_PTE);\n-\t\t\t\tcount_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_NONE);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (!pte_present(pteval)) {\n-\t\t\tresult = SCAN_PTE_NON_PRESENT;\n-\t\t\tgoto out;\n-\t\t}\n-\t\tif (pte_uffd(pteval)) {\n-\t\t\tresult = SCAN_PTE_UFFD;\n-\t\t\tgoto out;\n-\t\t}\n-\t\tpage = vm_normal_page(vma, addr, pteval);\n-\t\tif (unlikely(!page) || unlikely(is_zone_device_page(page))) {\n-\t\t\tresult = SCAN_PAGE_NULL;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tfolio = page_folio(page);\n-\t\tVM_BUG_ON_FOLIO(!folio_test_anon(folio), folio);\n-\n-\t\t/*\n-\t\t * If the vma has the VM_DROPPABLE flag, the collapse will\n-\t\t * preserve the lazyfree property without needing to skip.\n-\t\t */\n-\t\tif (cc-\u003eis_khugepaged \u0026\u0026 !(vma-\u003evm_flags \u0026 VM_DROPPABLE) \u0026\u0026\n-\t\t folio_test_lazyfree(folio) \u0026\u0026 !pte_dirty(pteval)) {\n-\t\t\tresult = SCAN_PAGE_LAZYFREE;\n+\t\tpteval = ptep_get(_pte);\n+\t\tpte_check = collapse_check_pte(pteval, addr, \u0026ctx);\n+\t\tfolio = ctx.folio;\n+\t\tif (pte_check == PTE_CHECK_FAIL) {\n+\t\t\tresult = ctx.result;\n \t\t\tgoto out;\n \t\t}\n+\t\tif (pte_check == PTE_CHECK_CONTINUE)\n+\t\t\tcontinue;\n \n-\t\t/* See collapse_scan_pmd(). */\n-\t\tif (folio_maybe_mapped_shared(folio)) {\n-\t\t\t/*\n-\t\t\t * TODO: Support shared pages without leading to further\n-\t\t\t * mTHP collapses. Currently bringing in new pages via\n-\t\t\t * shared may cause a future higher order collapse on a\n-\t\t\t * rescan of the same range.\n-\t\t\t */\n-\t\t\tif (++shared \u003e max_ptes_shared) {\n-\t\t\t\tresult = SCAN_EXCEED_SHARED_PTE;\n-\t\t\t\tif (is_pmd_order(order))\n-\t\t\t\t\tcount_vm_event(THP_SCAN_EXCEED_SHARED_PTE);\n-\t\t\t\tcount_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_SHARED);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\t\t}\n \t\t/*\n \t\t * TODO: In some cases of partially-mapped folios, we'd actually\n \t\t * want to collapse.\n@@ -759,8 +866,8 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\n \n \t\t/*\n \t\t * We can do it before folio_isolate_lru because the\n-\t\t * folio can't be freed from under us. NOTE: PG_lock\n-\t\t * is needed to serialize against split_huge_page\n+\t\t * folio can't be freed from under us. NOTE: folio lock\n+\t\t * is needed to serialize against split_huge_page()\n \t\t * when invoked from the VM.\n \t\t */\n \t\tif (!folio_trylock(folio)) {\n@@ -786,7 +893,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\n \t\t}\n \n \t\t/*\n-\t\t * Isolate the page to avoid collapsing an hugepage\n+\t\t * Isolate the folio to avoid collapsing a hugepage\n \t\t * currently in use by the VM.\n \t\t */\n \t\tif (!folio_isolate_lru(folio)) {\n@@ -803,14 +910,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\n \t\tif (folio_test_large(folio))\n \t\t\tlist_add_tail(\u0026folio-\u003elru, compound_pagelist);\n next:\n-\t\t/*\n-\t\t * If collapse was initiated by khugepaged, check that there is\n-\t\t * enough young pte to justify collapsing the page\n-\t\t */\n-\t\tif (cc-\u003eis_khugepaged \u0026\u0026\n-\t\t (pte_young(pteval) || folio_test_young(folio) ||\n-\t\t folio_test_referenced(folio) ||\n-\t\t mmu_notifier_test_young(vma-\u003evm_mm, addr)))\n+\t\tif (pte_is_referenced(cc, pteval, folio, vma, addr))\n \t\t\treferenced++;\n \t}\n \n@@ -818,13 +918,13 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\n \t\tresult = SCAN_LACK_REFERENCED_PAGE;\n \t} else {\n \t\tresult = SCAN_SUCCEED;\n-\t\ttrace_mm_collapse_huge_page_isolate(folio, none_or_zero,\n+\t\ttrace_mm_collapse_huge_page_isolate(folio, ctx.none_or_zero,\n \t\t\t\t\t\t referenced, result, order);\n \t\treturn result;\n \t}\n out:\n \trelease_pte_pages(pte, _pte, compound_pagelist);\n-\ttrace_mm_collapse_huge_page_isolate(folio, none_or_zero,\n+\ttrace_mm_collapse_huge_page_isolate(folio, ctx.none_or_zero,\n \t\t\t\t\t referenced, result, order);\n \treturn result;\n }\n@@ -904,7 +1004,7 @@ static void __collapse_huge_page_copy_failed(pte_t *pte,\n \t * Re-establish the PMD to point to the original page table\n \t * entry. Restoring PMD needs to be done prior to releasing\n \t * pages. Since pages are still isolated and locked here,\n-\t * acquiring anon_vma_lock_write is unnecessary.\n+\t * acquiring anon_vma_lock_write() is unnecessary.\n \t */\n \tpmd_ptl = pmd_lock(vma-\u003evm_mm, pmd);\n \tpmd_populate(vma-\u003evm_mm, pmd, pmd_pgtable(orig_pmd));\n@@ -1078,9 +1178,9 @@ static enum scan_result hugepage_vma_revalidate(struct mm_struct *mm, unsigned l\n \t\treturn SCAN_VMA_CHECK;\n \t/*\n \t * Anon VMA expected, the address may be unmapped then\n-\t * remapped to file after khugepaged reaquired the mmap_lock.\n+\t * remapped to file after khugepaged reacquired the mmap_lock.\n \t *\n-\t * thp_vma_allowable_orders may return true for qualified file\n+\t * thp_vma_allowable_orders() may return true for qualified file\n \t * vmas.\n \t */\n \tif (expect_anon \u0026\u0026 (!(*vmap)-\u003eanon_vma || !vma_is_anonymous(*vmap)))\n@@ -1136,7 +1236,7 @@ static enum scan_result check_pmd_still_valid(struct mm_struct *mm,\n \n /*\n * Bring missing pages in from swap, to complete THP collapse.\n- * Only done if khugepaged_scan_pmd believes it is worthwhile.\n+ * Only done if collapse_scan_pmd() believes it is worthwhile.\n *\n * For mTHP orders the function bails on the first swap entry, because\n * faulting pages back in during collapse could re-populate PTEs that\n@@ -1204,7 +1304,7 @@ static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,\n \t\tpte = NULL;\n \n \t\t/*\n-\t\t * do_swap_page returns VM_FAULT_RETRY with released mmap_lock.\n+\t\t * do_swap_page() returns VM_FAULT_RETRY with released mmap_lock.\n \t\t * Note we treat VM_FAULT_RETRY as VM_FAULT_ERROR here because\n \t\t * we do not retry here and swap entry will remain in pagetable\n \t\t * resulting in later failure.\n@@ -1247,15 +1347,12 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru\n \tfolio = __folio_alloc(gfp, order, node, \u0026cc-\u003ealloc_nmask);\n \tif (!folio) {\n \t\t*foliop = NULL;\n-\t\tif (is_pmd_order(order))\n-\t\t\tcount_vm_event(THP_COLLAPSE_ALLOC_FAILED);\n-\t\tcount_mthp_stat(order, MTHP_STAT_COLLAPSE_ALLOC_FAILED);\n+\t\tcount_collapse_event(order, THP_COLLAPSE_ALLOC_FAILED,\n+\t\t\t\t MTHP_STAT_COLLAPSE_ALLOC_FAILED);\n \t\treturn SCAN_ALLOC_HUGE_PAGE_FAIL;\n \t}\n \n-\tif (is_pmd_order(order))\n-\t\tcount_vm_event(THP_COLLAPSE_ALLOC);\n-\tcount_mthp_stat(order, MTHP_STAT_COLLAPSE_ALLOC);\n+\tcount_collapse_event(order, THP_COLLAPSE_ALLOC, MTHP_STAT_COLLAPSE_ALLOC);\n \n \tif (unlikely(mem_cgroup_charge(folio, mm, gfp))) {\n \t\tfolio_put(folio);\n@@ -1271,7 +1368,7 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru\n }\n \n /*\n- * collapse_huge_page expects the mmap_lock to be unlocked before entering and\n+ * collapse_huge_page() expects the mmap_lock to be unlocked before entering and\n * will always return with the lock unlocked, to avoid holding the mmap_lock\n * while allocating a THP, as that could trigger direct reclaim/compaction.\n * Note that the VMA must be rechecked after grabbing the mmap_lock again.\n@@ -1318,7 +1415,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s\n \n \tif (unmapped) {\n \t\t/*\n-\t\t * __collapse_huge_page_swapin will return with mmap_lock\n+\t\t * __collapse_huge_page_swapin() will return with mmap_lock\n \t\t * released when it fails. So we jump out_nolock directly in\n \t\t * that case. Continuing to collapse causes inconsistency.\n \t\t */\n@@ -1331,8 +1428,8 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s\n \tmmap_read_unlock(mm);\n \t/*\n \t * Prevent all access to pagetables with the exception of\n-\t * gup_fast later handled by the ptep_clear_flush and the VM\n-\t * handled by the anon_vma lock + PG_lock.\n+\t * gup_fast later handled by the pmdp_collapse_flush() and the VM\n+\t * handled by the anon_vma lock + folio lock.\n \t *\n \t * UFFDIO_MOVE is prevented to race as well thanks to the\n \t * mmap_lock.\n@@ -1389,9 +1486,9 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s\n \t\tspin_lock(pmd_ptl);\n \t\tVM_WARN_ON_ONCE(!pmd_none(*pmd));\n \t\t/*\n-\t\t * We can only use set_pmd_at when establishing\n+\t\t * We can only use set_pmd_at() when establishing\n \t\t * hugepmds and never for establishing regular pmds that\n-\t\t * points to regular pagetables. Use pmd_populate for that\n+\t\t * points to regular pagetables. Use pmd_populate() for that\n \t\t */\n \t\tpmd_populate(mm, pmd, pmd_pgtable(_pmd));\n \t\tspin_unlock(pmd_ptl);\n@@ -1439,7 +1536,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s\n \t\t\tspin_lock_nested(pte_ptl, SINGLE_DEPTH_NESTING);\n \t\tpmd_populate(mm, pmd, pmd_pgtable(_pmd));\n \t\tmap_anon_folio_pte_nopf(folio, pte, vma, start_addr,\n-\t\t\t\t\t /*uffd_wp=*/ false);\n+\t\t\t\t\t /*uffd=*/ false);\n \t\tif (pte_ptl != pmd_ptl)\n \t\t\tspin_unlock(pte_ptl);\n \t}\n@@ -1449,10 +1546,10 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s\n \n \tresult = SCAN_SUCCEED;\n out_up_write:\n-\tif (anon_vma_locked)\n-\t\tanon_vma_unlock_write(vma-\u003eanon_vma);\n \tif (pte)\n \t\tpte_unmap(pte);\n+\tif (anon_vma_locked)\n+\t\tanon_vma_unlock_write(vma-\u003eanon_vma);\n \tmmap_write_unlock(mm);\n out_nolock:\n \tif (folio)\n@@ -1593,42 +1690,47 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n \t\tstruct vm_area_struct *vma, unsigned long start_addr,\n \t\tbool *lock_dropped, struct collapse_control *cc)\n {\n-\tconst unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER);\n-\tconst unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);\n-\tunsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);\n \tenum tva_type tva_flags = cc-\u003eis_khugepaged ? TVA_KHUGEPAGED : TVA_FORCED_COLLAPSE;\n \tpmd_t *pmd;\n \tpte_t *pte, *_pte, pteval;\n \tint i;\n-\tint none_or_zero = 0, shared = 0, referenced = 0;\n-\tenum scan_result result = SCAN_FAIL;\n-\tstruct page *page = NULL;\n \tstruct folio *folio = NULL;\n+\tint referenced = 0;\n+\tenum scan_result result = SCAN_FAIL;\n \tunsigned long addr;\n \tunsigned long enabled_orders;\n \tspinlock_t *ptl;\n-\tint node = NUMA_NO_NODE, unmapped = 0;\n+\tint node = NUMA_NO_NODE;\n+\tenum pte_check_result pte_check;\n \n \tVM_BUG_ON(start_addr \u0026 ~HPAGE_PMD_MASK);\n \n+\tstruct pte_check_context ctx = {\n+\t\t.cc = cc,\n+\t\t.vma = vma,\n+\t\t.order = HPAGE_PMD_ORDER,\n+\t\t.max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER),\n+\t\t.max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER),\n+\t\t.max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER),\n+\t};\n+\n \tresult = find_pmd_or_thp_or_none(mm, start_addr, \u0026pmd);\n \tif (result != SCAN_SUCCEED) {\n \t\tcc-\u003eprogress++;\n \t\tgoto out;\n \t}\n \n-\tbitmap_zero(cc-\u003emthp_present_ptes, MAX_PTRS_PER_PTE);\n-\tmemset(cc-\u003enode_load, 0, sizeof(cc-\u003enode_load));\n-\tnodes_clear(cc-\u003ealloc_nmask);\n+\tcollapse_control_init_scan(cc);\n \n \tenabled_orders = collapse_possible_orders(vma, vma-\u003evm_flags, tva_flags);\n \n \t/*\n \t * If PMD is the only enabled order, enforce max_ptes_none, otherwise\n-\t * scan all pages to populate the bitmap for mTHP collapse.\n+\t * scan all pages to populate the bitmap for mTHP collapse. The bitmap\n+\t * is then checked again in mthp_collapse() for each attempted order.\n \t */\n \tif (enabled_orders != BIT(HPAGE_PMD_ORDER))\n-\t\tmax_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;\n+\t\tctx.max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;\n \n \tpte = pte_offset_map_lock(mm, pmd, start_addr, \u0026ptl);\n \tif (!pte) {\n@@ -1644,84 +1746,14 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n \n \t\tcc-\u003eprogress++;\n \n-\t\tif (pte_none_or_zero(pteval)) {\n-\t\t\tif (++none_or_zero \u003e max_ptes_none) {\n-\t\t\t\tresult = SCAN_EXCEED_NONE_PTE;\n-\t\t\t\tcount_vm_event(THP_SCAN_EXCEED_NONE_PTE);\n-\t\t\t\tcount_mthp_stat(HPAGE_PMD_ORDER,\n-\t\t\t\t\t\tMTHP_STAT_COLLAPSE_EXCEED_NONE);\n-\t\t\t\tgoto out_unmap;\n-\t\t\t}\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (!pte_present(pteval)) {\n-\t\t\tif (++unmapped \u003e max_ptes_swap) {\n-\t\t\t\tresult = SCAN_EXCEED_SWAP_PTE;\n-\t\t\t\tcount_vm_event(THP_SCAN_EXCEED_SWAP_PTE);\n-\t\t\t\tcount_mthp_stat(HPAGE_PMD_ORDER,\n-\t\t\t\t\t\tMTHP_STAT_COLLAPSE_EXCEED_SWAP);\n-\t\t\t\tgoto out_unmap;\n-\t\t\t}\n-\t\t\t/*\n-\t\t\t * Always be strict with uffd-wp\n-\t\t\t * enabled swap entries. Please see\n-\t\t\t * comment below for pte_uffd().\n-\t\t\t */\n-\t\t\tif (pte_swp_uffd_any(pteval)) {\n-\t\t\t\tresult = SCAN_PTE_UFFD;\n-\t\t\t\tgoto out_unmap;\n-\t\t\t}\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (pte_uffd(pteval)) {\n-\t\t\t/*\n-\t\t\t * Don't collapse the page if any of the small\n-\t\t\t * PTEs are armed with uffd write protection.\n-\t\t\t * Here we can also mark the new huge pmd as\n-\t\t\t * write protected if any of the small ones is\n-\t\t\t * marked but that could bring unknown\n-\t\t\t * userfault messages that falls outside of\n-\t\t\t * the registered range. So, just be simple.\n-\t\t\t */\n-\t\t\tresult = SCAN_PTE_UFFD;\n-\t\t\tgoto out_unmap;\n-\t\t}\n-\n-\t\tpage = vm_normal_page(vma, addr, pteval);\n-\t\tif (unlikely(!page) || unlikely(is_zone_device_page(page))) {\n-\t\t\tresult = SCAN_PAGE_NULL;\n-\t\t\tgoto out_unmap;\n-\t\t}\n-\t\tfolio = page_folio(page);\n-\n-\t\t/*\n-\t\t * If the vma has the VM_DROPPABLE flag, the collapse will\n-\t\t * preserve the lazyfree property without needing to skip.\n-\t\t */\n-\t\tif (cc-\u003eis_khugepaged \u0026\u0026 !(vma-\u003evm_flags \u0026 VM_DROPPABLE) \u0026\u0026\n-\t\t folio_test_lazyfree(folio) \u0026\u0026 !pte_dirty(pteval)) {\n-\t\t\tresult = SCAN_PAGE_LAZYFREE;\n+\t\tpte_check = collapse_check_pte(pteval, addr, \u0026ctx);\n+\t\tfolio = ctx.folio;\n+\t\tif (pte_check == PTE_CHECK_FAIL) {\n+\t\t\tresult = ctx.result;\n \t\t\tgoto out_unmap;\n \t\t}\n-\n-\t\tif (!folio_test_anon(folio)) {\n-\t\t\tresult = SCAN_PAGE_ANON;\n-\t\t\tgoto out_unmap;\n-\t\t}\n-\n-\t\t/*\n-\t\t * We treat a single page as shared if any part of the THP\n-\t\t * is shared.\n-\t\t */\n-\t\tif (folio_maybe_mapped_shared(folio)) {\n-\t\t\tif (++shared \u003e max_ptes_shared) {\n-\t\t\t\tresult = SCAN_EXCEED_SHARED_PTE;\n-\t\t\t\tcount_vm_event(THP_SCAN_EXCEED_SHARED_PTE);\n-\t\t\t\tcount_mthp_stat(HPAGE_PMD_ORDER,\n-\t\t\t\t\t\tMTHP_STAT_COLLAPSE_EXCEED_SHARED);\n-\t\t\t\tgoto out_unmap;\n-\t\t\t}\n-\t\t}\n+\t\tif (pte_check == PTE_CHECK_CONTINUE)\n+\t\t\tcontinue;\n \n \t\t/* Set bit for occupied pages */\n \t\t__set_bit(i, cc-\u003emthp_present_ptes);\n@@ -1749,31 +1781,21 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n \t\t/*\n \t\t * Check if the page has any GUP (or other external) pins.\n \t\t *\n-\t\t * Here the check may be racy:\n-\t\t * it may see folio_mapcount() \u003e folio_ref_count().\n-\t\t * But such case is ephemeral we could always retry collapse\n-\t\t * later. However it may report false positive if the page\n-\t\t * has excessive GUP pins (i.e. 512). Anyway the same check\n-\t\t * will be done again later the risk seems low.\n+\t\t * Here the check is racy, but such case is ephemeral and\n+\t\t * we could always retry collapse later. Anyway the same\n+\t\t * check will be done again later the risk seems low.\n \t\t */\n \t\tif (folio_expected_ref_count(folio) != folio_ref_count(folio)) {\n \t\t\tresult = SCAN_PAGE_COUNT;\n \t\t\tgoto out_unmap;\n \t\t}\n \n-\t\t/*\n-\t\t * If collapse was initiated by khugepaged, check that there is\n-\t\t * enough young pte to justify collapsing the page\n-\t\t */\n-\t\tif (cc-\u003eis_khugepaged \u0026\u0026\n-\t\t (pte_young(pteval) || folio_test_young(folio) ||\n-\t\t folio_test_referenced(folio) ||\n-\t\t mmu_notifier_test_young(vma-\u003evm_mm, addr)))\n+\t\tif (pte_is_referenced(cc, pteval, folio, vma, addr))\n \t\t\treferenced++;\n \t}\n \tif (cc-\u003eis_khugepaged \u0026\u0026\n \t\t (!referenced ||\n-\t\t (unmapped \u0026\u0026 referenced \u003c HPAGE_PMD_NR / 2))) {\n+\t\t (ctx.unmapped \u0026\u0026 referenced \u003c HPAGE_PMD_NR / 2))) {\n \t\tresult = SCAN_LACK_REFERENCED_PAGE;\n \t} else {\n \t\tresult = SCAN_SUCCEED;\n@@ -1781,16 +1803,16 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n out_unmap:\n \tpte_unmap_unlock(pte, ptl);\n \tif (result == SCAN_SUCCEED) {\n-\t\t/* collapse_huge_page expects the lock to be dropped before calling */\n+\t\t/* collapse_huge_page() expects the lock to be dropped before calling */\n \t\tmmap_read_unlock(mm);\n \t\tresult = mthp_collapse(mm, start_addr, referenced,\n-\t\t\t\t unmapped, cc, enabled_orders);\n+\t\t\t\t ctx.unmapped, cc, enabled_orders);\n \t\t/* mmap_lock was released above, set lock_dropped */\n \t\t*lock_dropped = true;\n \t}\n out:\n \ttrace_mm_khugepaged_scan_pmd(mm, folio, referenced,\n-\t\t\t\t none_or_zero, result, unmapped);\n+\t\t\t\t ctx.none_or_zero, result, ctx.unmapped);\n \treturn result;\n }\n \n@@ -2691,8 +2713,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,\n \n \tpresent = 0;\n \tswap = 0;\n-\tmemset(cc-\u003enode_load, 0, sizeof(cc-\u003enode_load));\n-\tnodes_clear(cc-\u003ealloc_nmask);\n+\tcollapse_control_init_scan(cc);\n \trcu_read_lock();\n \txas_for_each(\u0026xas, folio, start + HPAGE_PMD_NR - 1) {\n \t\tif (xas_retry(\u0026xas, folio))\n"
} |
| 2/1 | 2026/08/04 19:39 | agent | patch-evaluator |
4mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE",
"USERFAULTFD"
],
"FocusSymbols": [
"madvise_collapse"
],
"Reasoning": "The patch refactors PTE checking logic for THP collapse into a new helper function collapse_check_pte(). It also modifies the behavior when a non-anonymous folio is encountered in an anonymous VMA, changing a VM_BUG_ON_FOLIO to a VM_WARN_ON_FOLIO and returning an error. Fuzzing madvise(MADV_COLLAPSE) and khugepaged can help verify that this refactoring doesn't introduce subtle regressions in PTE scanning or uninitialized variable usage.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 578a4da6083c5552c26f2a70fed8ae9b9a349f40
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 4 19:38:57 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/admin-guide/mm/transhuge.rst b/Documentation/admin-guide/mm/transhuge.rst
index 16f37135ed80d..b187d618452f4 100644
--- a/Documentation/admin-guide/mm/transhuge.rst
+++ b/Documentation/admin-guide/mm/transhuge.rst
@@ -224,7 +224,7 @@ khugepaged will be automatically started when any THP size is enabled
(either of the per-size anon control or the top-level control are set
to "always" or "madvise"), and it'll be automatically shutdown when
all THP sizes are disabled (when both the per-size anon control and the
-top-level control are "never")
+top-level control are "never").
process THP controls
--------------------
@@ -301,7 +301,9 @@ being replaced by a PMD mapping, or (2) physical pages replaced by one
hugepage of various sizes (PMD-sized or mTHP). Each may happen independently,
or together, depending on the type of memory and the failures that occur.
As such, this value should be interpreted roughly as a sign of progress,
-and counters in /proc/vmstat consulted for more accurate accounting)::
+and counters in /proc/vmstat consulted for more accurate accounting.
+Per-order mTHP collapse statistics are also available under
+/sys/kernel/mm/transparent_hugepage/hugepages-<size>kB/stats/)::
/sys/kernel/mm/transparent_hugepage/khugepaged/pages_collapsed
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index b237f6e7662a5..3338e9dc11dd0 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -65,6 +65,12 @@ enum scan_result {
SCAN_PAGE_DIRTY_OR_WRITEBACK,
};
+enum pte_check_result {
+ PTE_CHECK_SUCCEED,
+ PTE_CHECK_FAIL,
+ PTE_CHECK_CONTINUE,
+};
+
#define CREATE_TRACE_POINTS
#include <trace/events/huge_memory.h>
@@ -119,6 +125,20 @@ struct collapse_control {
DECLARE_BITMAP(mthp_present_ptes, MAX_PTRS_PER_PTE);
};
+struct pte_check_context {
+ struct collapse_control *cc;
+ struct vm_area_struct *vma;
+ unsigned int order;
+ struct folio *folio;
+ int none_or_zero;
+ int shared;
+ int unmapped;
+ enum scan_result result;
+ unsigned int max_ptes_none;
+ unsigned int max_ptes_swap;
+ unsigned int max_ptes_shared;
+};
+
/**
* struct khugepaged_scan - cursor for scanning
* @mm_head: the head of the mm list to scan
@@ -620,7 +640,7 @@ void __khugepaged_exit(struct mm_struct *mm)
/*
* This is required to serialize against
* collapse_test_exit() (which is guaranteed to run
- * under mmap sem read mode). Stop here (after we return all
+ * under mmap_lock read mode). Stop here (after we return all
* pagetables will be destroyed) until khugepaged has finished
* working on the pagetables under the mmap_lock.
*/
@@ -629,6 +649,13 @@ void __khugepaged_exit(struct mm_struct *mm)
}
}
+static void collapse_control_init_scan(struct collapse_control *cc)
+{
+ memset(cc->node_load, 0, sizeof(cc->node_load));
+ nodes_clear(cc->alloc_nmask);
+ bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE);
+}
+
static void release_pte_folio(struct folio *folio)
{
node_stat_mod_folio(folio,
@@ -665,76 +692,156 @@ static void release_pte_pages(pte_t *pte, pte_t *_pte,
}
}
+/*
+ * pte_is_referenced() - Check for enough referenced PTEs to justify collapsing
+ *
+ * If collapse was initiated by khugepaged, check that the page has been
+ * recently accessed (young pte) to justify collapsing the page.
+ *
+ * Return: true if the page has been recently accessed.
+ */
+static inline bool pte_is_referenced(struct collapse_control *cc, pte_t pteval,
+ struct folio *folio, struct vm_area_struct *vma, unsigned long addr)
+{
+ return cc->is_khugepaged &&
+ (pte_young(pteval) || folio_test_young(folio) ||
+ folio_test_referenced(folio) ||
+ mmu_notifier_test_young(vma->vm_mm, addr));
+}
+
+static void count_collapse_event(unsigned int order, enum vm_event_item vm_event,
+ enum mthp_stat_item mthp_event)
+{
+ if (is_pmd_order(order))
+ count_vm_event(vm_event);
+ count_mthp_stat(order, mthp_event);
+}
+
+/*
+ * pte_check_fail() - A simple helper to set the pte_check_context result and
+ * return PTE_CHECK_FAIL.
+ */
+static enum pte_check_result pte_check_fail(struct pte_check_context *ctx,
+ enum scan_result result)
+{
+ ctx->result = result;
+ return PTE_CHECK_FAIL;
+}
+
+/*
+ * collapse_check_pte() - Check if a PTE is suitable for collapse
+ *
+ * Check if a PTE is suitable for collapse based on the following criteria:
+ * - max_pte_* values are not exceeded
+ * - uffd is not active
+ * - lazyfree properties are not present
+ * - only anonymous pages are present
+ *
+ * a helper struct pte_check_context is used to pass and store relevant
+ * information between the collapse_check_pte() function and the caller.
+ *
+ * Return: PTE_CHECK_SUCCEED if the PTE is suitable for collapse,
+ * PTE_CHECK_FAIL if the PTE is not suitable for collapse,
+ * PTE_CHECK_CONTINUE if the scan should continue to check the next PTE.
+ */
+static enum pte_check_result collapse_check_pte(pte_t pteval,
+ unsigned long addr, struct pte_check_context *ctx)
+{
+ if (pte_none_or_zero(pteval)) {
+ if (++ctx->none_or_zero > ctx->max_ptes_none) {
+ count_collapse_event(ctx->order, THP_SCAN_EXCEED_NONE_PTE,
+ MTHP_STAT_COLLAPSE_EXCEED_NONE);
+ return pte_check_fail(ctx, SCAN_EXCEED_NONE_PTE);
+ }
+ return PTE_CHECK_CONTINUE;
+ }
+ if (!pte_present(pteval)) {
+ if (ctx->unmapped == -1)
+ return pte_check_fail(ctx, SCAN_PTE_NON_PRESENT);
+ if (++ctx->unmapped > ctx->max_ptes_swap) {
+ count_collapse_event(ctx->order, THP_SCAN_EXCEED_SWAP_PTE,
+ MTHP_STAT_COLLAPSE_EXCEED_SWAP);
+ return pte_check_fail(ctx, SCAN_EXCEED_SWAP_PTE);
+ }
+ if (pte_swp_uffd_any(pteval))
+ return pte_check_fail(ctx, SCAN_PTE_UFFD);
+ return PTE_CHECK_CONTINUE;
+ }
+ /*
+ * Don't collapse if any of the small PTEs are armed with uffd
+ * write protection. Marking the new huge pmd as write protected
+ * could bring userfault messages that fall outside of the
+ * registered range.
+ */
+ if (pte_uffd(pteval))
+ return pte_check_fail(ctx, SCAN_PTE_UFFD);
+
+ ctx->folio = vm_normal_folio(ctx->vma, addr, pteval);
+ if (unlikely(!ctx->folio) || unlikely(folio_is_zone_device(ctx->folio)))
+ return pte_check_fail(ctx, SCAN_PAGE_NULL);
+
+ /*
+ * If the vma has the VM_DROPPABLE flag, the collapse will
+ * preserve the lazyfree property without needing to skip.
+ */
+ if (ctx->cc->is_khugepaged && !(ctx->vma->vm_flags & VM_DROPPABLE) &&
+ folio_test_lazyfree(ctx->folio) && !pte_dirty(pteval))
+ return pte_check_fail(ctx, SCAN_PAGE_LAZYFREE);
+
+ if (!folio_test_anon(ctx->folio)) {
+ VM_WARN_ON_FOLIO(!folio_test_anon(ctx->folio), ctx->folio);
+ return pte_check_fail(ctx, SCAN_PAGE_ANON);
+ }
+
+ if (folio_maybe_mapped_shared(ctx->folio)) {
+ /*
+ * TODO: Support shared pages without leading to further
+ * mTHP collapses. Currently bringing in new pages via
+ * shared may cause a future higher order collapse on a
+ * rescan of the same range.
+ */
+ if (++ctx->shared > ctx->max_ptes_shared) {
+ count_collapse_event(ctx->order, THP_SCAN_EXCEED_SHARED_PTE,
+ MTHP_STAT_COLLAPSE_EXCEED_SHARED);
+ return pte_check_fail(ctx, SCAN_EXCEED_SHARED_PTE);
+ }
+ }
+
+ return PTE_CHECK_SUCCEED;
+}
+
static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
unsigned long start_addr, pte_t *pte, struct collapse_control *cc,
unsigned int order, struct list_head *compound_pagelist)
{
- const unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, order);
- const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, order);
const unsigned long nr_pages = 1UL << order;
- struct page *page = NULL;
struct folio *folio = NULL;
unsigned long addr = start_addr;
- pte_t *_pte;
- int none_or_zero = 0, shared = 0, referenced = 0;
+ pte_t *_pte, pteval;
+ int referenced = 0;
enum scan_result result = SCAN_FAIL;
+ enum pte_check_result pte_check;
+ struct pte_check_context ctx = {
+ .cc = cc,
+ .vma = vma,
+ .order = order,
+ .unmapped = -1, /* don't check swap PTEs */
+ .max_ptes_none = collapse_max_ptes_none(cc, vma, order),
+ .max_ptes_shared = collapse_max_ptes_shared(cc, order),
+ };
for (_pte = pte; _pte < pte + nr_pages;
_pte++, addr += PAGE_SIZE) {
- pte_t pteval = ptep_get(_pte);
- if (pte_none_or_zero(pteval)) {
- if (++none_or_zero > max_ptes_none) {
- result = SCAN_EXCEED_NONE_PTE;
- if (is_pmd_order(order))
- count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
- count_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_NONE);
- goto out;
- }
- continue;
- }
- if (!pte_present(pteval)) {
- result = SCAN_PTE_NON_PRESENT;
- goto out;
- }
- if (pte_uffd(pteval)) {
- result = SCAN_PTE_UFFD;
- goto out;
- }
- page = vm_normal_page(vma, addr, pteval);
- if (unlikely(!page) || unlikely(is_zone_device_page(page))) {
- result = SCAN_PAGE_NULL;
- goto out;
- }
-
- folio = page_folio(page);
- VM_BUG_ON_FOLIO(!folio_test_anon(folio), folio);
-
- /*
- * If the vma has the VM_DROPPABLE flag, the collapse will
- * preserve the lazyfree property without needing to skip.
- */
- if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
- folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
- result = SCAN_PAGE_LAZYFREE;
+ pteval = ptep_get(_pte);
+ pte_check = collapse_check_pte(pteval, addr, &ctx);
+ folio = ctx.folio;
+ if (pte_check == PTE_CHECK_FAIL) {
+ result = ctx.result;
goto out;
}
+ if (pte_check == PTE_CHECK_CONTINUE)
+ continue;
- /* See collapse_scan_pmd(). */
- if (folio_maybe_mapped_shared(folio)) {
- /*
- * TODO: Support shared pages without leading to further
- * mTHP collapses. Currently bringing in new pages via
- * shared may cause a future higher order collapse on a
- * rescan of the same range.
- */
- if (++shared > max_ptes_shared) {
- result = SCAN_EXCEED_SHARED_PTE;
- if (is_pmd_order(order))
- count_vm_event(THP_SCAN_EXCEED_SHARED_PTE);
- count_mthp_stat(order, MTHP_STAT_COLLAPSE_EXCEED_SHARED);
- goto out;
- }
- }
/*
* TODO: In some cases of partially-mapped folios, we'd actually
* want to collapse.
@@ -759,8 +866,8 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
/*
* We can do it before folio_isolate_lru because the
- * folio can't be freed from under us. NOTE: PG_lock
- * is needed to serialize against split_huge_page
+ * folio can't be freed from under us. NOTE: folio lock
+ * is needed to serialize against split_huge_page()
* when invoked from the VM.
*/
if (!folio_trylock(folio)) {
@@ -786,7 +893,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
}
/*
- * Isolate the page to avoid collapsing an hugepage
+ * Isolate the folio to avoid collapsing a hugepage
* currently in use by the VM.
*/
if (!folio_isolate_lru(folio)) {
@@ -803,14 +910,7 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
if (folio_test_large(folio))
list_add_tail(&folio->lru, compound_pagelist);
next:
- /*
- * If collapse was initiated by khugepaged, check that there is
- * enough young pte to justify collapsing the page
- */
- if (cc->is_khugepaged &&
- (pte_young(pteval) || folio_test_young(folio) ||
- folio_test_referenced(folio) ||
- mmu_notifier_test_young(vma->vm_mm, addr)))
+ if (pte_is_referenced(cc, pteval, folio, vma, addr))
referenced++;
}
@@ -818,13 +918,13 @@ static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,
result = SCAN_LACK_REFERENCED_PAGE;
} else {
result = SCAN_SUCCEED;
- trace_mm_collapse_huge_page_isolate(folio, none_or_zero,
+ trace_mm_collapse_huge_page_isolate(folio, ctx.none_or_zero,
referenced, result, order);
return result;
}
out:
release_pte_pages(pte, _pte, compound_pagelist);
- trace_mm_collapse_huge_page_isolate(folio, none_or_zero,
+ trace_mm_collapse_huge_page_isolate(folio, ctx.none_or_zero,
referenced, result, order);
return result;
}
@@ -904,7 +1004,7 @@ static void __collapse_huge_page_copy_failed(pte_t *pte,
* Re-establish the PMD to point to the original page table
* entry. Restoring PMD needs to be done prior to releasing
* pages. Since pages are still isolated and locked here,
- * acquiring anon_vma_lock_write is unnecessary.
+ * acquiring anon_vma_lock_write() is unnecessary.
*/
pmd_ptl = pmd_lock(vma->vm_mm, pmd);
pmd_populate(vma->vm_mm, pmd, pmd_pgtable(orig_pmd));
@@ -1078,9 +1178,9 @@ static enum scan_result hugepage_vma_revalidate(struct mm_struct *mm, unsigned l
return SCAN_VMA_CHECK;
/*
* Anon VMA expected, the address may be unmapped then
- * remapped to file after khugepaged reaquired the mmap_lock.
+ * remapped to file after khugepaged reacquired the mmap_lock.
*
- * thp_vma_allowable_orders may return true for qualified file
+ * thp_vma_allowable_orders() may return true for qualified file
* vmas.
*/
if (expect_anon && (!(*vmap)->anon_vma || !vma_is_anonymous(*vmap)))
@@ -1136,7 +1236,7 @@ static enum scan_result check_pmd_still_valid(struct mm_struct *mm,
/*
* Bring missing pages in from swap, to complete THP collapse.
- * Only done if khugepaged_scan_pmd believes it is worthwhile.
+ * Only done if collapse_scan_pmd() believes it is worthwhile.
*
* For mTHP orders the function bails on the first swap entry, because
* faulting pages back in during collapse could re-populate PTEs that
@@ -1204,7 +1304,7 @@ static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,
pte = NULL;
/*
- * do_swap_page returns VM_FAULT_RETRY with released mmap_lock.
+ * do_swap_page() returns VM_FAULT_RETRY with released mmap_lock.
* Note we treat VM_FAULT_RETRY as VM_FAULT_ERROR here because
* we do not retry here and swap entry will remain in pagetable
* resulting in later failure.
@@ -1247,15 +1347,12 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru
folio = __folio_alloc(gfp, order, node, &cc->alloc_nmask);
if (!folio) {
*foliop = NULL;
- if (is_pmd_order(order))
- count_vm_event(THP_COLLAPSE_ALLOC_FAILED);
- count_mthp_stat(order, MTHP_STAT_COLLAPSE_ALLOC_FAILED);
+ count_collapse_event(order, THP_COLLAPSE_ALLOC_FAILED,
+ MTHP_STAT_COLLAPSE_ALLOC_FAILED);
return SCAN_ALLOC_HUGE_PAGE_FAIL;
}
- if (is_pmd_order(order))
- count_vm_event(THP_COLLAPSE_ALLOC);
- count_mthp_stat(order, MTHP_STAT_COLLAPSE_ALLOC);
+ count_collapse_event(order, THP_COLLAPSE_ALLOC, MTHP_STAT_COLLAPSE_ALLOC);
if (unlikely(mem_cgroup_charge(folio, mm, gfp))) {
folio_put(folio);
@@ -1271,7 +1368,7 @@ static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_stru
}
/*
- * collapse_huge_page expects the mmap_lock to be unlocked before entering and
+ * collapse_huge_page() expects the mmap_lock to be unlocked before entering and
* will always return with the lock unlocked, to avoid holding the mmap_lock
* while allocating a THP, as that could trigger direct reclaim/compaction.
* Note that the VMA must be rechecked after grabbing the mmap_lock again.
@@ -1318,7 +1415,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
if (unmapped) {
/*
- * __collapse_huge_page_swapin will return with mmap_lock
+ * __collapse_huge_page_swapin() will return with mmap_lock
* released when it fails. So we jump out_nolock directly in
* that case. Continuing to collapse causes inconsistency.
*/
@@ -1331,8 +1428,8 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
mmap_read_unlock(mm);
/*
* Prevent all access to pagetables with the exception of
- * gup_fast later handled by the ptep_clear_flush and the VM
- * handled by the anon_vma lock + PG_lock.
+ * gup_fast later handled by the pmdp_collapse_flush() and the VM
+ * handled by the anon_vma lock + folio lock.
*
* UFFDIO_MOVE is prevented to race as well thanks to the
* mmap_lock.
@@ -1389,9 +1486,9 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
spin_lock(pmd_ptl);
VM_WARN_ON_ONCE(!pmd_none(*pmd));
/*
- * We can only use set_pmd_at when establishing
+ * We can only use set_pmd_at() when establishing
* hugepmds and never for establishing regular pmds that
- * points to regular pagetables. Use pmd_populate for that
+ * points to regular pagetables. Use pmd_populate() for that
*/
pmd_populate(mm, pmd, pmd_pgtable(_pmd));
spin_unlock(pmd_ptl);
@@ -1439,7 +1536,7 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
spin_lock_nested(pte_ptl, SINGLE_DEPTH_NESTING);
pmd_populate(mm, pmd, pmd_pgtable(_pmd));
map_anon_folio_pte_nopf(folio, pte, vma, start_addr,
- /*uffd_wp=*/ false);
+ /*uffd=*/ false);
if (pte_ptl != pmd_ptl)
spin_unlock(pte_ptl);
}
@@ -1449,10 +1546,10 @@ static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long s
result = SCAN_SUCCEED;
out_up_write:
- if (anon_vma_locked)
- anon_vma_unlock_write(vma->anon_vma);
if (pte)
pte_unmap(pte);
+ if (anon_vma_locked)
+ anon_vma_unlock_write(vma->anon_vma);
mmap_write_unlock(mm);
out_nolock:
if (folio)
@@ -1593,42 +1690,47 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
struct vm_area_struct *vma, unsigned long start_addr,
bool *lock_dropped, struct collapse_control *cc)
{
- const unsigned int max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER);
- const unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);
- unsigned int max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER);
enum tva_type tva_flags = cc->is_khugepaged ? TVA_KHUGEPAGED : TVA_FORCED_COLLAPSE;
pmd_t *pmd;
pte_t *pte, *_pte, pteval;
int i;
- int none_or_zero = 0, shared = 0, referenced = 0;
- enum scan_result result = SCAN_FAIL;
- struct page *page = NULL;
struct folio *folio = NULL;
+ int referenced = 0;
+ enum scan_result result = SCAN_FAIL;
unsigned long addr;
unsigned long enabled_orders;
spinlock_t *ptl;
- int node = NUMA_NO_NODE, unmapped = 0;
+ int node = NUMA_NO_NODE;
+ enum pte_check_result pte_check;
VM_BUG_ON(start_addr & ~HPAGE_PMD_MASK);
+ struct pte_check_context ctx = {
+ .cc = cc,
+ .vma = vma,
+ .order = HPAGE_PMD_ORDER,
+ .max_ptes_none = collapse_max_ptes_none(cc, vma, HPAGE_PMD_ORDER),
+ .max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER),
+ .max_ptes_shared = collapse_max_ptes_shared(cc, HPAGE_PMD_ORDER),
+ };
+
result = find_pmd_or_thp_or_none(mm, start_addr, &pmd);
if (result != SCAN_SUCCEED) {
cc->progress++;
goto out;
}
- bitmap_zero(cc->mthp_present_ptes, MAX_PTRS_PER_PTE);
- memset(cc->node_load, 0, sizeof(cc->node_load));
- nodes_clear(cc->alloc_nmask);
+ collapse_control_init_scan(cc);
enabled_orders = collapse_possible_orders(vma, vma->vm_flags, tva_flags);
/*
* If PMD is the only enabled order, enforce max_ptes_none, otherwise
- * scan all pages to populate the bitmap for mTHP collapse.
+ * scan all pages to populate the bitmap for mTHP collapse. The bitmap
+ * is then checked again in mthp_collapse() for each attempted order.
*/
if (enabled_orders != BIT(HPAGE_PMD_ORDER))
- max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
+ ctx.max_ptes_none = KHUGEPAGED_MAX_PTES_LIMIT;
pte = pte_offset_map_lock(mm, pmd, start_addr, &ptl);
if (!pte) {
@@ -1644,84 +1746,14 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
cc->progress++;
- if (pte_none_or_zero(pteval)) {
- if (++none_or_zero > max_ptes_none) {
- result = SCAN_EXCEED_NONE_PTE;
- count_vm_event(THP_SCAN_EXCEED_NONE_PTE);
- count_mthp_stat(HPAGE_PMD_ORDER,
- MTHP_STAT_COLLAPSE_EXCEED_NONE);
- goto out_unmap;
- }
- continue;
- }
- if (!pte_present(pteval)) {
- if (++unmapped > max_ptes_swap) {
- result = SCAN_EXCEED_SWAP_PTE;
- count_vm_event(THP_SCAN_EXCEED_SWAP_PTE);
- count_mthp_stat(HPAGE_PMD_ORDER,
- MTHP_STAT_COLLAPSE_EXCEED_SWAP);
- goto out_unmap;
- }
- /*
- * Always be strict with uffd-wp
- * enabled swap entries. Please see
- * comment below for pte_uffd().
- */
- if (pte_swp_uffd_any(pteval)) {
- result = SCAN_PTE_UFFD;
- goto out_unmap;
- }
- continue;
- }
- if (pte_uffd(pteval)) {
- /*
- * Don't collapse the page if any of the small
- * PTEs are armed with uffd write protection.
- * Here we can also mark the new huge pmd as
- * write protected if any of the small ones is
- * marked but that could bring unknown
- * userfault messages that falls outside of
- * the registered range. So, just be simple.
- */
- result = SCAN_PTE_UFFD;
- goto out_unmap;
- }
-
- page = vm_normal_page(vma, addr, pteval);
- if (unlikely(!page) || unlikely(is_zone_device_page(page))) {
- result = SCAN_PAGE_NULL;
- goto out_unmap;
- }
- folio = page_folio(page);
-
- /*
- * If the vma has the VM_DROPPABLE flag, the collapse will
- * preserve the lazyfree property without needing to skip.
- */
- if (cc->is_khugepaged && !(vma->vm_flags & VM_DROPPABLE) &&
- folio_test_lazyfree(folio) && !pte_dirty(pteval)) {
- result = SCAN_PAGE_LAZYFREE;
+ pte_check = collapse_check_pte(pteval, addr, &ctx);
+ folio = ctx.folio;
+ if (pte_check == PTE_CHECK_FAIL) {
+ result = ctx.result;
goto out_unmap;
}
-
- if (!folio_test_anon(folio)) {
- result = SCAN_PAGE_ANON;
- goto out_unmap;
- }
-
- /*
- * We treat a single page as shared if any part of the THP
- * is shared.
- */
- if (folio_maybe_mapped_shared(folio)) {
- if (++shared > max_ptes_shared) {
- result = SCAN_EXCEED_SHARED_PTE;
- count_vm_event(THP_SCAN_EXCEED_SHARED_PTE);
- count_mthp_stat(HPAGE_PMD_ORDER,
- MTHP_STAT_COLLAPSE_EXCEED_SHARED);
- goto out_unmap;
- }
- }
+ if (pte_check == PTE_CHECK_CONTINUE)
+ continue;
/* Set bit for occupied pages */
__set_bit(i, cc->mthp_present_ptes);
@@ -1749,31 +1781,21 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
/*
* Check if the page has any GUP (or other external) pins.
*
- * Here the check may be racy:
- * it may see folio_mapcount() > folio_ref_count().
- * But such case is ephemeral we could always retry collapse
- * later. However it may report false positive if the page
- * has excessive GUP pins (i.e. 512). Anyway the same check
- * will be done again later the risk seems low.
+ * Here the check is racy, but such case is ephemeral and
+ * we could always retry collapse later. Anyway the same
+ * check will be done again later the risk seems low.
*/
if (folio_expected_ref_count(folio) != folio_ref_count(folio)) {
result = SCAN_PAGE_COUNT;
goto out_unmap;
}
- /*
- * If collapse was initiated by khugepaged, check that there is
- * enough young pte to justify collapsing the page
- */
- if (cc->is_khugepaged &&
- (pte_young(pteval) || folio_test_young(folio) ||
- folio_test_referenced(folio) ||
- mmu_notifier_test_young(vma->vm_mm, addr)))
+ if (pte_is_referenced(cc, pteval, folio, vma, addr))
referenced++;
}
if (cc->is_khugepaged &&
(!referenced ||
- (unmapped && referenced < HPAGE_PMD_NR / 2))) {
+ (ctx.unmapped && referenced < HPAGE_PMD_NR / 2))) {
result = SCAN_LACK_REFERENCED_PAGE;
} else {
result = SCAN_SUCCEED;
@@ -1781,16 +1803,16 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
out_unmap:
pte_unmap_unlock(pte, ptl);
if (result == SCAN_SUCCEED) {
- /* collapse_huge_page expects the lock to be dropped before calling */
+ /* collapse_huge_page() expects the lock to be dropped before calling */
mmap_read_unlock(mm);
result = mthp_collapse(mm, start_addr, referenced,
- unmapped, cc, enabled_orders);
+ ctx.unmapped, cc, enabled_orders);
/* mmap_lock was released above, set lock_dropped */
*lock_dropped = true;
}
out:
trace_mm_khugepaged_scan_pmd(mm, folio, referenced,
- none_or_zero, result, unmapped);
+ ctx.none_or_zero, result, ctx.unmapped);
return result;
}
@@ -2691,8 +2713,7 @@ static enum scan_result collapse_scan_file(struct mm_struct *mm,
present = 0;
swap = 0;
- memset(cc->node_load, 0, sizeof(cc->node_load));
- nodes_clear(cc->alloc_nmask);
+ collapse_control_init_scan(cc);
rcu_read_lock();
xas_for_each(&xas, folio, start + HPAGE_PMD_NR - 1) {
if (xas_retry(&xas, folio))
|
| 3/2 | 2026/08/04 19:39 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11236 output: 20 thoughts: 2822 Thoughts: **Analyzing the Khugepaged Collapse Logic Refactor** Okay, so I'm looking at this patch that seems to be a refactoring of the khugepaged collapse logic, specifically the PTE scanning part, modifying `mm/khugepaged.c` and `Documentation/admin-guide/mm/transhuge.rst`. The main changes appear to be the introduction of a new `pte_check_context` struct and a new function `collapse_check_pte()` to centralize PTE checking. Several other related functions like `pte_is_referenced()`, `count_collapse_event()`, and `collapse_control_init_scan()` are added or modified as well. My initial impression is that this is a clean-up, but I need to be sure. Looking closely at the diff in `collapse_scan_pmd()`, I see that a series of checks have been moved into the `collapse_check_pte()` function. My first thought is "ok, this seems like just a logical grouping and simplification." The original code did inline checks with `pte_none_or_zero()`, `pte_present()`, and some folio tests. Now, these are all done within `collapse_check_pte()`. However, as I dive deeper, I notice subtle details. In `collapse_check_pte()`, the context struct is initialized. Comparing this to the older version, I want to verify if there is any functional change. Looking closer, there is a `ctx->unmapped` which is initialized to `-1` in `__collapse_huge_page_isolate()`. This is important, as it handles a different case than in `collapse_scan_pmd()`. In `collapse_scan_pmd()`, the initialization is implicit, but I need to verify that all the old and new contexts are comparable. The old code had `int unmapped = 0;` and so on, so now I need to check the default values. So the default values are also 0. `none_or_zero` and `shared` are also 0. So no functional change here. Then, there is the functional equivalence of `vm_normal_folio()` vs the old `vm_normal_page()` + `page_folio()` combination. Although they are functionally equivalent for most cases, `vm_normal_folio()` might be slightly different. So this part is also refactoring. The same goes for the addition of `count_collapse_event()`, it is a simple refactoring. Wait, looking at `__collapse_huge_page_isolate()`, I observe a critical change: `VM_BUG_ON_FOLIO` is being replaced by `VM_WARN_ON_FOLIO`. This change is also added in `collapse_scan_pmd()`! This is potentially significant! In the `__collapse_huge_page_isolate()` call site, the old code had a `VM_BUG_ON_FOLIO` check, but it is replaced with `VM_WARN_ON_FOLIO` now. In the other call site, `collapse_scan_pmd()`, it used to simply return `SCAN_PAGE_ANON` when it found a non-anonymous page. Now, it will trigger a `VM_WARN_ON_FOLIO` if it's not anon! This is a downgrade for one call site, but an introduction of a new warning for the other. So the question becomes: can `collapse_scan_pmd()` ever encounter a non-anonymous folio? I'll need to verify the call paths of `collapse_scan_pmd()`, which is called from `khugepaged_scan_pmd()`, which is called from `khugepaged_scan_mm_slot()`. It scans anonymous VMAs, meaning the non-anon folio must come from a source that khugepaged interacts with. I'm thinking the zero page is handled by `pte_none_or_zero()`. It looks like this new warning could be triggered by something like a VMA being anonymous, but the underlying page not actually being anonymous. I need to make sure this doesn't introduce any new instability due to new warnings. I'll need to check the VMA to verify whether `khugepaged` can actually interact with this case or not, and whether that folio can actually be non-anon. I'm now going to use `default_api:grepper` to search for `collapse_scan_pmd` to verify my findings. |
| 4/2 | 2026/08/04 19:39 | tool | grepper |
0mArgs:{
"Expression": "collapse_scan_pmd"
}Results: {
"Output": "mm/khugepaged.c=1224=static enum scan_result check_pmd_still_valid(struct mm_struct *mm,\n--\nmm/khugepaged.c-1238- * Bring missing pages in from swap, to complete THP collapse.\nmm/khugepaged.c:1239: * Only done if collapse_scan_pmd() believes it is worthwhile.\nmm/khugepaged.c-1240- *\n--\nmm/khugepaged.c=1562=static unsigned int max_order_from_offset(unsigned int offset)\n--\nmm/khugepaged.c-1571- * mthp_collapse() consumes the bitmap that is generated during\nmm/khugepaged.c:1572: * collapse_scan_pmd() to determine what regions and mTHP orders fit best.\nmm/khugepaged.c-1573- *\n--\nmm/khugepaged.c=1587=static enum scan_result mthp_collapse(struct mm_struct *mm,\n--\nmm/khugepaged.c-1688-\nmm/khugepaged.c:1689:static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\nmm/khugepaged.c-1690-\t\tstruct vm_area_struct *vma, unsigned long start_addr,\n--\nmm/khugepaged.c=2811=static enum scan_result collapse_single_pmd(unsigned long addr,\n--\nmm/khugepaged.c-2823-\tif (vma_is_anonymous(vma)) {\nmm/khugepaged.c:2824:\t\tresult = collapse_scan_pmd(mm, vma, addr, lock_dropped, cc);\nmm/khugepaged.c-2825-\t\tgoto end;\n"
} |
| 5/2 | 2026/08/04 19:39 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15520 output: 20 thoughts: 808 Thoughts: **Analyzing a Potential Kernel Bug in `collapse_scan_pmd()`** Okay, so I'm looking at `collapse_scan_pmd()` and it's called when the VMA is anonymous, specifically when `vma_is_anonymous(vma)` is true. My first thought is: can an anonymous VMA *actually* contain a non-anonymous folio? I immediately know that a `MAP_PRIVATE` mapping of a file would return false for `vma_is_anonymous(vma)`. But what about `MAP_ANONYMOUS | MAP_SHARED`? That's a shmem VMA, again, `vma_is_anonymous(vma)` is false. A standard `mmap(MAP_ANONYMOUS | MAP_PRIVATE)` creates an *anonymous* VMA, but could it have a non-anon folio? I consider KSM pages, but they're anon, so no luck there. Uprobes? Still anon. Ptrace inserted pages? Still anon. Aha! What if a device driver uses `vm_insert_page()` to map pages into an anonymous VMA? If a driver does do a `vm_insert_page()` into an anonymous VMA, that page *might* not be anonymous. Good. Let's see... `vm_normal_folio()` will return the folio. If it's not anon, `folio_test_anon()` will return false. And, the crucial part, `collapse_check_pte()` will trigger a `VM_WARN_ON_FOLIO(!folio_test_anon(ctx->folio), ctx->folio);`. Wait, is this a bug in the patch? It didn't used to have that warning! Let me check the original code... Okay, I see it. The original code just returned `SCAN_PAGE_ANON`. The new code now adds a `VM_WARN_ON_FOLIO`, which means the current patch introduced a functional change. That’s something that can be fuzzed! Can we even trigger this warning? How could we get a non-anon page into an anon VMA? The best way I think would be for syzkaller to map a non-anon page into an anonymous VMA and then trigger khugepaged to scan it, which should trigger the warning. I start running through possible avenues: `mmap(MAP_ANONYMOUS | MAP_PRIVATE)` won't work to map a non-anon page. What about `mremap`? No, it just moves the whole VMA. What about `vmsplice`? What about `userfaultfd`? With `UFFDIO_COPY`, it copies into an anon page. But what about `UFFDIO_CONTINUE`? That's for shmem/hugetlb. `madvise(MADV_MERGEABLE)`? KSM pages are anon, again. `O_DIRECT` read into an anon VMA? Pages are anon. What about `get_user_pages`? Wait, what if we use `mmap` to map a file, then `mprotect`? No. What about `remap_file_pages`? It was deprecated. Okay, let's recap. It is possible to trigger this case. To map a non-anon folio into anon VMA we should search through possible ways, for example: a device driver uses `vm_insert_page()` to map pages into an anonymous VMA. This is definitely a bug in the patch! Let's search for `SCAN_PAGE_ANON` in the kernel source to see if this is a known issue. |
| 6/2 | 2026/08/04 19:39 | tool | grepper |
0mArgs:{
"Expression": "SCAN_PAGE_ANON"
}Results: {
"Output": "include/trace/events/huge_memory.h-26-\tEM( SCAN_PAGE_LOCK,\t\t\"page_locked\")\t\t\t\\\ninclude/trace/events/huge_memory.h:27:\tEM( SCAN_PAGE_ANON,\t\t\"page_not_anon\")\t\t\\\ninclude/trace/events/huge_memory.h-28-\tEM( SCAN_PAGE_LAZYFREE,\t\t\"page_lazyfree\")\t\t\\\n--\nmm/khugepaged.c=33=enum scan_result {\n--\nmm/khugepaged.c-49-\tSCAN_PAGE_LOCK,\nmm/khugepaged.c:50:\tSCAN_PAGE_ANON,\nmm/khugepaged.c-51-\tSCAN_PAGE_LAZYFREE,\n--\nmm/khugepaged.c=747=static enum pte_check_result collapse_check_pte(pte_t pteval,\n--\nmm/khugepaged.c-792-\t\tVM_WARN_ON_FOLIO(!folio_test_anon(ctx-\u003efolio), ctx-\u003efolio);\nmm/khugepaged.c:793:\t\treturn pte_check_fail(ctx, SCAN_PAGE_ANON);\nmm/khugepaged.c-794-\t}\n--\nmm/khugepaged.c=1153=static enum scan_result hugepage_vma_revalidate(struct mm_struct *mm, unsigned long address,\n--\nmm/khugepaged.c-1186-\tif (expect_anon \u0026\u0026 (!(*vmap)-\u003eanon_vma || !vma_is_anonymous(*vmap)))\nmm/khugepaged.c:1187:\t\treturn SCAN_PAGE_ANON;\nmm/khugepaged.c-1188-\treturn SCAN_SUCCEED;\n"
} |
| 7/2 | 2026/08/04 19:39 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17595 output: 19 thoughts: 664 Thoughts: **Analyzing the Khugepaged Anonymous Page Collapse Logic** Okay, so I'm digging into this `hugepage_vma_revalidate()` function and I see it returns `SCAN_PAGE_ANON` when `expect_anon` is true, but the VMA isn't anonymous anymore. My immediate thought is, "Where are we checking this?" Then I remember that `collapse_check_pte()` is called when scanning the PTEs. Now, here's where it gets interesting. I'm looking at `__collapse_huge_page_isolate()` and realize the old code already had `VM_BUG_ON_FOLIO(!folio_test_anon(folio), folio);`. This ensures that we are collapsing anon pages and throws a VM bug if not. The patch replaces this with `collapse_check_pte()`, which has `VM_WARN_ON_FOLIO(!folio_test_anon(ctx->folio), ctx->folio);`. So, the patch actually *downgrades* a `VM_BUG_ON_FOLIO` to a `VM_WARN_ON_FOLIO` in `__collapse_huge_page_isolate()`! At the same time it *upgrades* a silent return to `VM_WARN_ON_FOLIO` in `collapse_scan_pmd()`. Hold on, I need to trace the call flow. `__collapse_huge_page_isolate()` is called from `collapse_huge_page()`, which is called *after* `collapse_scan_pmd()` has succeeded. So `collapse_scan_pmd()` scans the PMD and if it finds a non-anonymous folio, it *used* to silently return `SCAN_PAGE_ANON`. If it *succeeded*, all the folios were anonymous. Then, `collapse_huge_page()` is called, which calls `__collapse_huge_page_isolate()`. In `__collapse_huge_page_isolate()`, it used to have the `VM_BUG_ON_FOLIO`, because it *assumed* that since `collapse_scan_pmd()` already checked, it must be anonymous. But wait a minute… the `mmap_lock` is dropped *between* `collapse_scan_pmd()` and `collapse_huge_page()`! `collapse_scan_pmd()` drops the lock before calling `mthp_collapse()`. Now, is `mthp_collapse()` called *instead* of `collapse_huge_page()`? Let's check `mthp_collapse()`... |
| 8/2 | 2026/08/04 19:39 | tool | grepper |
0mArgs:{
"Expression": "mthp_collapse"
}Results: {
"Output": "mm/khugepaged.c=1562=static unsigned int max_order_from_offset(unsigned int offset)\n--\nmm/khugepaged.c-1570-/*\nmm/khugepaged.c:1571: * mthp_collapse() consumes the bitmap that is generated during\nmm/khugepaged.c-1572- * collapse_scan_pmd() to determine what regions and mTHP orders fit best.\n--\nmm/khugepaged.c-1586- */\nmm/khugepaged.c:1587:static enum scan_result mthp_collapse(struct mm_struct *mm,\nmm/khugepaged.c-1588-\t\tunsigned long address, int referenced, int unmapped,\n--\nmm/khugepaged.c=1689=static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n--\nmm/khugepaged.c-1729-\t * scan all pages to populate the bitmap for mTHP collapse. The bitmap\nmm/khugepaged.c:1730:\t * is then checked again in mthp_collapse() for each attempted order.\nmm/khugepaged.c-1731-\t */\n--\nmm/khugepaged.c-1807-\t\tmmap_read_unlock(mm);\nmm/khugepaged.c:1808:\t\tresult = mthp_collapse(mm, start_addr, referenced,\nmm/khugepaged.c-1809-\t\t\t\t ctx.unmapped, cc, enabled_orders);\n"
} |
| 9/2 | 2026/08/04 19:39 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19244 output: 37 thoughts: 12 Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person: **Analyzing the Function Call: `mthp_collapse()`** Alright, let's dive into this. The mental trigger here is seeing that function call: `mthp_collapse()`. My initial thought is to immediately start parsing what that *means*. Based on the naming convention (assuming this is my own codebase or one I'm very familiar with), the "mthp" strongly suggests this is related to something like "Multi-threaded Hierarchical Processing," or maybe a similar structure that utilizes collapse operation for parallelism. The name "collapse" is critical here. It hints at some kind of reduction or aggregation process applied to a hierarchical or parallel data structure. I'm already anticipating key elements of it might be related to either data reduction (a 'collapse' into aggregate data) or thread synchronization and waiting. I need to understand the hierarchical aspect and threading relationship of that specific context to understand what type of information and data is passed. The goal would likely be to reduce the complexity of the data by merging or grouping data in an orderly fashion. |
| 10/2 | 2026/08/04 19:40 | tool | read-file |
0mArgs:{
"File": "mm/khugepaged.c",
"FirstLine": 1587,
"LineCount": 50
}Results: {
"Contents": "1587:\tstatic enum scan_result mthp_collapse(struct mm_struct *mm,\n1588:\t\t\tunsigned long address, int referenced, int unmapped,\n1589:\t\t\tstruct collapse_control *cc, unsigned long enabled_orders)\n1590:\t{\n1591:\t\tunsigned int nr_occupied_ptes, nr_ptes, max_ptes_none;\n1592:\t\tenum scan_result last_result = SCAN_FAIL;\n1593:\t\tint collapsed = 0;\n1594:\t\tbool alloc_failed = false;\n1595:\t\tunsigned long collapse_address;\n1596:\t\tunsigned int offset = 0;\n1597:\t\tunsigned int order = HPAGE_PMD_ORDER;\n1598:\t\n1599:\t\twhile (offset \u003c HPAGE_PMD_NR) {\n1600:\t\t\tnr_ptes = 1UL \u003c\u003c order;\n1601:\t\n1602:\t\t\tif (!test_bit(order, \u0026enabled_orders))\n1603:\t\t\t\tgoto next_order;\n1604:\t\n1605:\t\t\tmax_ptes_none = collapse_max_ptes_none(cc, NULL, order);\n1606:\t\t\tnr_occupied_ptes = bitmap_weight_from(cc-\u003emthp_present_ptes, offset,\n1607:\t\t\t\t\t\t\t offset + nr_ptes);\n1608:\t\n1609:\t\t\t/*\n1610:\t\t\t * Swap PTEs accepted during the scan are counted in @unmapped,\n1611:\t\t\t * not in the present-PTE bitmap. Account them for the PMD-order\n1612:\t\t\t * candidate.\n1613:\t\t\t */\n1614:\t\t\tif (is_pmd_order(order))\n1615:\t\t\t\tnr_occupied_ptes += unmapped;\n1616:\t\n1617:\t\t\tif (nr_occupied_ptes \u003e= nr_ptes - max_ptes_none) {\n1618:\t\t\t\tenum scan_result ret;\n1619:\t\n1620:\t\t\t\tcollapse_address = address + offset * PAGE_SIZE;\n1621:\t\t\t\tret = collapse_huge_page(mm, collapse_address, referenced,\n1622:\t\t\t\t\t\t\t unmapped, cc, order);\n1623:\t\n1624:\t\t\t\tswitch (ret) {\n1625:\t\t\t\t/* Cases where we continue to next collapse candidate */\n1626:\t\t\t\tcase SCAN_SUCCEED:\n1627:\t\t\t\t\tcollapsed += nr_ptes;\n1628:\t\t\t\t\tfallthrough;\n1629:\t\t\t\tcase SCAN_PTE_MAPPED_HUGEPAGE:\n1630:\t\t\t\t\tgoto next_offset;\n1631:\t\t\t\t/* Cases where lower orders might still succeed */\n1632:\t\t\t\tcase SCAN_ALLOC_HUGE_PAGE_FAIL:\n1633:\t\t\t\t\talloc_failed = true;\n1634:\t\t\t\t\tfallthrough;\n1635:\t\t\t\tcase SCAN_LACK_REFERENCED_PAGE:\n1636:\t\t\t\tcase SCAN_EXCEED_NONE_PTE:\n"
} |
| 11/2 | 2026/08/04 19:40 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20335 output: 19 thoughts: 2489 Thoughts: **Analysis of Huge Page Collapse Code Changes** Okay, let's break this down. My initial thought process focused on the flow of `mthp_collapse()` calling `collapse_huge_page()`. The key concern was the potential race condition between dropping and reacquiring the mmap_lock and the modification of PTEs. The revalidation through `hugepage_vma_revalidate()` seemed like a potential safeguard. My mind then jumped to the critical point: the handling of non-anonymous pages within an anonymous VMA. Specifically, I considered scenarios like zero pages, KSM pages, and pages from files. The `vm_insert_page()` function's ability to map non-anon pages into any VMA raised a red flag. This brought the `VM_WARN_ON_FOLIO(!folio_test_anon(ctx->folio), ctx->folio);` check, which replaced a `VM_BUG_ON_FOLIO` from the older code. So the change resulted in a downgrade from a BUG to a WARN. And also the code now WARNS in `collapse_scan_pmd()` where it used to just return. I went into investigating what happens inside `collapse_check_pte()`, where it's called from both `collapse_scan_pmd()` and `__collapse_huge_page_isolate()`. The addition of the WARN in `collapse_check_pte()` is a functional change, as it introduces a warning where previously, there was none. I wondered if this adds a warning. I checked `pte_is_referenced()`, comparing the old and new inline code. It's unchanged, which is good. Then, I looked at the handling of shared pages and unmapped pages within both `collapse_scan_pmd()` and `__collapse_huge_page_isolate()`, and these sections are also the same. The UFFD (userfaultfd) handling is also consistent across both old and new code. Finally, the change to `collapse_control_init_scan()` adds a new function that initializes the scan, however the code also has a `bitmap_zero()` call that was *not* in `collapse_scan_file()` before. This function is called in the `collapse_scan_pmd()` and `collapse_scan_file()`. |
| 12/2 | 2026/08/04 19:40 | tool | grepper |
0mArgs:{
"Expression": "collapse_scan_file"
}Results: {
"Output": "mm/khugepaged.c=2269=static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,\n--\nmm/khugepaged.c-2700-\nmm/khugepaged.c:2701:static enum scan_result collapse_scan_file(struct mm_struct *mm,\nmm/khugepaged.c-2702-\t\tunsigned long addr, struct file *file, pgoff_t start,\n--\nmm/khugepaged.c=2811=static enum scan_result collapse_single_pmd(unsigned long addr,\n--\nmm/khugepaged.c-2833-retry:\nmm/khugepaged.c:2834:\tresult = collapse_scan_file(mm, addr, file, pgoff, cc);\nmm/khugepaged.c-2835-\n--\nmm/mremap.c=197=static int move_ptes(struct pagetable_move_control *pmc,\n--\nmm/mremap.c-246-\t/*\nmm/mremap.c:247:\t * Now new_pte is none, so collapse_scan_file() path can not find\nmm/mremap.c-248-\t * this by traversing file-\u003ef_mapping, so there is no concurrency with\n"
} |
| 13/2 | 2026/08/04 19:40 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 23681 output: 37 thoughts: 30 Thoughts: Okay, let's dive into this. Here's how I'd approach this task, as if these were my internal thoughts: **Analyzing `collapse_scan_file()` for `cc->mthp_present_ptes` Usage** Right, so I need to understand how `cc->mthp_present_ptes` is being used. The most logical place to start is within the `collapse_scan_file()` function. I want to see if this particular member of the `cc` (presumably some kind of control context or cache structure) is actually accessed and manipulated within that function. I'll need to carefully examine the code within `collapse_scan_file()` to confirm its usage. Knowing where this variable is used is critical for understanding its purpose and impact. I'll need to check the function, line by line, to see if the variable is directly used or if another variable which uses it is used. |
| 14/2 | 2026/08/04 19:40 | tool | read-file |
0mArgs:{
"File": "mm/khugepaged.c",
"FirstLine": 2701,
"LineCount": 50
}Results: {
"Contents": "2701:\tstatic enum scan_result collapse_scan_file(struct mm_struct *mm,\n2702:\t\t\tunsigned long addr, struct file *file, pgoff_t start,\n2703:\t\t\tstruct collapse_control *cc)\n2704:\t{\n2705:\t\tconst unsigned int max_ptes_none = collapse_max_ptes_none(cc, NULL, HPAGE_PMD_ORDER);\n2706:\t\tconst unsigned int max_ptes_swap = collapse_max_ptes_swap(cc, HPAGE_PMD_ORDER);\n2707:\t\tstruct folio *folio = NULL;\n2708:\t\tstruct address_space *mapping = file-\u003ef_mapping;\n2709:\t\tXA_STATE(xas, \u0026mapping-\u003ei_pages, start);\n2710:\t\tint present, swap;\n2711:\t\tint node = NUMA_NO_NODE;\n2712:\t\tenum scan_result result = SCAN_SUCCEED;\n2713:\t\n2714:\t\tpresent = 0;\n2715:\t\tswap = 0;\n2716:\t\tcollapse_control_init_scan(cc);\n2717:\t\trcu_read_lock();\n2718:\t\txas_for_each(\u0026xas, folio, start + HPAGE_PMD_NR - 1) {\n2719:\t\t\tif (xas_retry(\u0026xas, folio))\n2720:\t\t\t\tcontinue;\n2721:\t\n2722:\t\t\tif (xa_is_value(folio)) {\n2723:\t\t\t\tswap += 1 \u003c\u003c xas_get_order(\u0026xas);\n2724:\t\t\t\tif (swap \u003e max_ptes_swap) {\n2725:\t\t\t\t\tresult = SCAN_EXCEED_SWAP_PTE;\n2726:\t\t\t\t\tcount_vm_event(THP_SCAN_EXCEED_SWAP_PTE);\n2727:\t\t\t\t\tbreak;\n2728:\t\t\t\t}\n2729:\t\t\t\tcontinue;\n2730:\t\t\t}\n2731:\t\n2732:\t\t\tif (!folio_try_get(folio)) {\n2733:\t\t\t\txas_reset(\u0026xas);\n2734:\t\t\t\tcontinue;\n2735:\t\t\t}\n2736:\t\n2737:\t\t\tif (unlikely(folio != xas_reload(\u0026xas))) {\n2738:\t\t\t\tfolio_put(folio);\n2739:\t\t\t\txas_reset(\u0026xas);\n2740:\t\t\t\tcontinue;\n2741:\t\t\t}\n2742:\t\n2743:\t\t\tif (is_pmd_order(folio_order(folio))) {\n2744:\t\t\t\tresult = SCAN_PTE_MAPPED_HUGEPAGE;\n2745:\t\t\t\t/*\n2746:\t\t\t\t * PMD-sized THP implies that we can only try\n2747:\t\t\t\t * retracting the PTE table.\n2748:\t\t\t\t */\n2749:\t\t\t\tfolio_put(folio);\n2750:\t\t\t\tbreak;\n"
} |
| 15/2 | 2026/08/04 19:40 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 24720 output: 19 thoughts: 2627 Thoughts: **Analysis of Collapse Code Changes** Okay, let's break this down. My initial assessment was focused on confirming that certain optimizations in `collapse_scan_file()` wouldn't inadvertently impact the overall collapse logic. Specifically, the zeroing of `cc->mthp_present_ptes` in that specific function appeared safe, as it's not directly utilized within the function's scope. However, this triggered a need to more rigorously investigate the surrounding code. The first area of concern was around the lazyfree handling, specifically within `__collapse_huge_page_isolate()` and `collapse_check_pte()`. It's pretty straightforward: we want to preserve lazyfree pages if the VMA has the `VM_DROPPABLE` flag set, and the page is actually lazyfree and not dirty. The check in the new code using the context structure (`ctx`) appears functionally identical to the older implementation. We are just using the context to pass data, as is proper. Next, I looked at the "young" page check, which determines if a PTE is "young" enough to justify collapsing. The logic within `__collapse_huge_page_isolate()` and `collapse_scan_pmd()` appears to be identical as well. The old `pte_young()` and folio checks, as well as the MMU notifier check, have been consolidated into `pte_is_referenced()`, but the behavior is, from what I can tell, exactly the same. The logic in `collapse_scan_pmd()` that determines if a collapse should be skipped due to a lack of referenced pages also appears to be identical. The most interesting area to look at was related to unmapped PTEs. In the older code, a simple integer variable `unmapped` was used to track the number of unmapped PTEs. The new code, however, leverages a context structure `ctx`, and the equivalent of the old `unmapped` variable is now `ctx.unmapped`. I had to confirm that `ctx.unmapped` was initialized to the correct value, and also the correct behavior of the logic that uses the `unmapped` values and counts, based on the `max_ptes_swap` parameter. Based on C's structure initialization rules, as it is *not* explicitly initialized in the struct, it would default to zero. The increment logic is consistent as well. So it behaves identically. The checks for `ctx.none_or_zero`, `ctx.shared`, and the assignment of `ctx.folio` and `ctx.result` were then confirmed to be handled correctly, confirming that the context struct behaved like the old variables. Lastly, the `count_collapse_event()` function was compared, to ensure no logic change was made to the stats. The logic is identical. This appears to be correct, and that `count_vm_event()` is called with `THP_SCAN_EXCEED_NONE_PTE` is correct. After reviewing these areas, the conclusion is that there are *no functional changes*. The code uses a context struct and inlines some functions, but the behavior is the same. |
| 16/2 | 2026/08/04 19:40 | tool | grepper |
0mArgs:{
"Expression": "vm_normal_folio"
}Results: {
"Output": "fs/proc/task_mmu.c=1690=static inline bool pte_is_pinned(struct vm_area_struct *vma, unsigned long addr, pte_t pte)\n--\nfs/proc/task_mmu.c-1699-\t\treturn false;\nfs/proc/task_mmu.c:1700:\tfolio = vm_normal_folio(vma, addr, pte);\nfs/proc/task_mmu.c-1701-\tif (!folio)\n--\nfs/proc/task_mmu.c=1770=static int clear_refs_pte_range(pmd_t *pmd, unsigned long addr,\n--\nfs/proc/task_mmu.c-1815-\nfs/proc/task_mmu.c:1816:\t\tfolio = vm_normal_folio(vma, addr, ptent);\nfs/proc/task_mmu.c-1817-\t\tif (!folio)\n--\ninclude/linux/mm.h=3143=extern void user_shm_unlock(size_t, struct ucounts *);\ninclude/linux/mm.h-3144-\ninclude/linux/mm.h:3145:struct folio *vm_normal_folio(struct vm_area_struct *vma, unsigned long addr,\ninclude/linux/mm.h-3146-\t\t\t pte_t pte);\ninclude/linux/mm.h=3147=struct page *vm_normal_page(struct vm_area_struct *vma, unsigned long addr,\ninclude/linux/mm.h-3148-\t\t\t pte_t pte);\ninclude/linux/mm.h:3149:struct folio *vm_normal_folio_pmd(struct vm_area_struct *vma,\ninclude/linux/mm.h-3150-\t\t\t\t unsigned long addr, pmd_t pmd);\n--\nmm/damon/vaddr.c=389=static int damon_young_pmd_entry(pmd_t *pmd, unsigned long addr,\n--\nmm/damon/vaddr.c-404-\t\t\tgoto huge_out;\nmm/damon/vaddr.c:405:\t\tfolio = vm_normal_folio_pmd(walk-\u003evma, addr, pmde);\nmm/damon/vaddr.c-406-\t\tif (!folio)\n--\nmm/damon/vaddr.c-423-\t\tgoto out;\nmm/damon/vaddr.c:424:\tfolio = vm_normal_folio(walk-\u003evma, addr, ptent);\nmm/damon/vaddr.c-425-\tif (!folio)\n--\nmm/damon/vaddr.c=635=static int damos_va_migrate_pmd_entry(pmd_t *pmd, unsigned long addr,\n--\nmm/damon/vaddr.c-653-\t\t\tgoto huge_out;\nmm/damon/vaddr.c:654:\t\tfolio = vm_normal_folio_pmd(walk-\u003evma, addr, pmde);\nmm/damon/vaddr.c-655-\t\tif (!folio)\n--\nmm/damon/vaddr.c-676-\t\t\tcontinue;\nmm/damon/vaddr.c:677:\t\tfolio = vm_normal_folio(walk-\u003evma, addr, ptent);\nmm/damon/vaddr.c-678-\t\tif (!folio)\n--\nmm/damon/vaddr.c=795=static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr,\n--\nmm/damon/vaddr.c-814-\nmm/damon/vaddr.c:815:\t\tfolio = vm_normal_folio_pmd(vma, addr, pmde);\nmm/damon/vaddr.c-816-\n--\nmm/damon/vaddr.c-839-\nmm/damon/vaddr.c:840:\t\tfolio = vm_normal_folio(vma, addr, ptent);\nmm/damon/vaddr.c-841-\n--\nmm/huge_memory.c=2278=vm_fault_t do_huge_pmd_numa_page(struct vm_fault *vmf)\n--\nmm/huge_memory.c-2307-\nmm/huge_memory.c:2308:\tfolio = vm_normal_folio_pmd(vma, haddr, pmd);\nmm/huge_memory.c-2309-\tif (!folio)\n--\nmm/huge_memory.c=2462=static struct folio *normal_or_softleaf_folio_pmd(struct vm_area_struct *vma,\n--\nmm/huge_memory.c-2465-\tif (is_present)\nmm/huge_memory.c:2466:\t\treturn vm_normal_folio_pmd(vma, addr, pmdval);\nmm/huge_memory.c-2467-\n--\nmm/khugepaged.c=747=static enum pte_check_result collapse_check_pte(pte_t pteval,\n--\nmm/khugepaged.c-778-\nmm/khugepaged.c:779:\tctx-\u003efolio = vm_normal_folio(ctx-\u003evma, addr, pteval);\nmm/khugepaged.c-780-\tif (unlikely(!ctx-\u003efolio) || unlikely(folio_is_zone_device(ctx-\u003efolio)))\n--\nmm/ksm.c=616=static int break_ksm_pmd_entry(pmd_t *pmdp, unsigned long addr, unsigned long end,\n--\nmm/ksm.c-638-\t\tif (pte_present(pte)) {\nmm/ksm.c:639:\t\t\tfolio = vm_normal_folio(walk-\u003evma, addr, pte);\nmm/ksm.c-640-\t\t} else if (!pte_none(pte)) {\n--\nmm/madvise.c=353=static int madvise_cold_or_pageout_pte_range(pmd_t *pmd,\n--\nmm/madvise.c-475-\nmm/madvise.c:476:\t\tfolio = vm_normal_folio(vma, addr, ptent);\nmm/madvise.c-477-\t\tif (!folio || folio_is_zone_device(folio))\n--\nmm/madvise.c=652=static int madvise_free_pte_range(pmd_t *pmd, unsigned long addr,\n--\nmm/madvise.c-704-\nmm/madvise.c:705:\t\tfolio = vm_normal_folio(vma, addr, ptent);\nmm/madvise.c-706-\t\tif (!folio || folio_is_zone_device(folio))\n--\nmm/memory.c=819=struct page *vm_normal_page(struct vm_area_struct *vma, unsigned long addr,\n--\nmm/memory.c-828-/**\nmm/memory.c:829: * vm_normal_folio() - Get the \"struct folio\" associated with a PTE\nmm/memory.c-830- * @vma: The VMA mapping the @pte.\n--\nmm/memory.c-839- */\nmm/memory.c:840:struct folio *vm_normal_folio(struct vm_area_struct *vma, unsigned long addr,\nmm/memory.c-841-\t\t\t pte_t pte)\n--\nmm/memory.c=863=struct page *vm_normal_page_pmd(struct vm_area_struct *vma, unsigned long addr,\n--\nmm/memory.c-872-/**\nmm/memory.c:873: * vm_normal_folio_pmd() - Get the \"struct folio\" associated with a PMD\nmm/memory.c-874- * @vma: The VMA mapping the @pmd.\n--\nmm/memory.c-883- */\nmm/memory.c:884:struct folio *vm_normal_folio_pmd(struct vm_area_struct *vma,\nmm/memory.c-885-\t\t\t\t unsigned long addr, pmd_t pmd)\n--\nmm/memory.c=6284=static vm_fault_t do_numa_page(struct vm_fault *vmf)\n--\nmm/memory.c-6319-\nmm/memory.c:6320:\tfolio = vm_normal_folio(vma, vmf-\u003eaddress, pte);\nmm/memory.c-6321-\tif (!folio || folio_is_zone_device(folio))\n--\nmm/mempolicy.c=687=static int queue_folios_pte_range(pmd_t *pmd, unsigned long addr,\n--\nmm/mempolicy.c-723-\t\t}\nmm/mempolicy.c:724:\t\tfolio = vm_normal_folio(vma, addr, ptent);\nmm/mempolicy.c-725-\t\tif (!folio || folio_is_zone_device(folio))\n--\nmm/mempolicy.c-729-\t\t/*\nmm/mempolicy.c:730:\t\t * vm_normal_folio() filters out zero pages, but there might\nmm/mempolicy.c-731-\t\t * still be reserved folios to skip, perhaps in a VDSO.\n--\nmm/mlock.c=350=static int mlock_pte_range(pmd_t *pmd, unsigned long addr,\n--\nmm/mlock.c-387-\t\t\tcontinue;\nmm/mlock.c:388:\t\tfolio = vm_normal_folio(vma, addr, ptent);\nmm/mlock.c-389-\t\tif (!folio || folio_is_zone_device(folio))\n--\nmm/mremap.c=178=static int mremap_folio_pte_batch(struct vm_area_struct *vma, unsigned long addr,\n--\nmm/mremap.c-189-\nmm/mremap.c:190:\tfolio = vm_normal_folio(vma, addr, pte);\nmm/mremap.c-191-\tif (!folio || !folio_test_large(folio))\n--\nmm/userfaultfd.c=1280=static struct folio *check_ptes_for_batched_move(struct vm_area_struct *src_vma,\n--\nmm/userfaultfd.c-1294-\nmm/userfaultfd.c:1295:\tfolio = vm_normal_folio(src_vma, src_addr, orig_src_pte);\nmm/userfaultfd.c-1296-\tif (!folio || !folio_trylock(folio))\n--\nmm/userfaultfd.c=1503=static long move_pages_ptes(struct mm_struct *mm, pmd_t *dst_pmd, pmd_t *src_pmd,\n--\nmm/userfaultfd.c-1619-\nmm/userfaultfd.c:1620:\t\t\tfolio = vm_normal_folio(src_vma, src_addr, orig_src_pte);\nmm/userfaultfd.c-1621-\t\t\tif (!folio || !PageAnonExclusive(\u0026folio-\u003epage)) {\n"
} |
| 17/2 | 2026/08/04 19:40 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 30892 output: 33 thoughts: 41 Thoughts: Okay, here's my take on those thoughts, framed from my perspective as someone well-versed in memory management: **Digging into `vm_normal_folio()`** Right, so `vm_normal_folio()` is essentially a convenience function, a wrapper, isn't it? It appears to be nothing more than a simplified way to obtain a folio based on a normal page, specifically by calling `page_folio(vm_normal_page())`. That's the core of its functionality. The implication is that it helps to reduce repetition. If a lot of code needs to operate on the folio of a normal page, this function just provides a shorthand method. The details are abstracted, which means that the caller does not need to repeatedly call `page_folio(vm_normal_page())`. Let's delve deeper and confirm that by examining the implementation of `vm_normal_folio()` itself. We can confirm whether it is actually just a simple wrapper function. |
| 18/2 | 2026/08/04 19:40 | tool | read-file |
0mArgs:{
"File": "mm/memory.c",
"FirstLine": 839,
"LineCount": 20
}Results: {
"Contents": " 839:\t */\n 840:\tstruct folio *vm_normal_folio(struct vm_area_struct *vma, unsigned long addr,\n 841:\t\t\t\t pte_t pte)\n 842:\t{\n 843:\t\tstruct page *page = vm_normal_page(vma, addr, pte);\n 844:\t\n 845:\t\tif (page)\n 846:\t\t\treturn page_folio(page);\n 847:\t\treturn NULL;\n 848:\t}\n 849:\t\n 850:\t#ifdef CONFIG_PGTABLE_HAS_HUGE_LEAVES\n 851:\t/**\n 852:\t * vm_normal_page_pmd() - Get the \"struct page\" associated with a PMD\n 853:\t * @vma: The VMA mapping the @pmd.\n 854:\t * @addr: The address where the @pmd is mapped.\n 855:\t * @pmd: The PMD.\n 856:\t *\n 857:\t * Get the \"struct page\" associated with a PTE. See __vm_normal_page()\n 858:\t * for details on \"normal\" and \"special\" mappings.\n"
} |
| 19/2 | 2026/08/04 19:40 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 31499 output: 23 thoughts: 789 Thoughts: **Analyzing a Potential Khugepaged Warning Condition** Okay, so I've been looking at this code and it looks like a subtle change has been introduced: that in `collapse_scan_pmd()`, there's a new `VM_WARN_ON_FOLIO` call. Previously, it simply returned `SCAN_PAGE_ANON`. The question is, can we trigger this warning? My initial thought is that it's related to khugepaged attempting to scan a non-anonymous page within what it *thinks* is an anonymous VMA. The code path includes a check `!folio_test_anon(ctx->folio)`. So, the crux of the problem lies in the possibility of mapping a non-anonymous page *into* an anonymous VMA. My first intuition is that `vm_insert_page()` is the key. Drivers often use this, but typically within *their* specific VMAs, which aren't anonymous. Could a user somehow leverage `vm_insert_page()` on a `MAP_ANONYMOUS`-created VMA? Drivers typically handle `vm_insert_page()` in their `mmap` handler, where the VMA's already set up. So, this wouldn't normally happen. I'm ruling out a few potential avenues: `O_DIRECT` reads into anonymous memory, but the pages themselves are still anonymous. `vmsplice` maps user memory to a pipe but doesn't change anonymity. `mremap` can move a VMA, but it doesn't touch the anon status. `userfaultfd`'s `UFFDIO_COPY` uses anon pages, and `UFFDIO_CONTINUE` is for shmem/hugetlb. KSM pages are always anonymous, as are zero pages. Then I paused to think about `remap_pfn_range` – I'm pretty sure it maps into PFNMAP regions, and thus `vm_normal_folio()` will return NULL. Also `vm_insert_page` could set mixed map, but the page itself is still the correct one. So the challenge is to find a way to get a driver to call `vm_insert_page()` on a VMA created via `mmap(MAP_ANONYMOUS)`. What can trigger this? `uprobe` inserts a breakpoint page, is that anon? And then there's `xol_add_vma()` creates a special VMA. Ptrace uses `get_user_pages()`. `madvise(MADV_FREE)` keeps it anon, `MADV_HWPOISON` poisons. I consider O_DIRECT with GUP. Then the thought to consider if khugepaged scans a file VMA. Wait, the key is the scan path! I realized `khugepaged` scans file VMAs via `collapse_scan_file()`, not `collapse_scan_pmd()`. So the problem I was thinking about might not be possible. However, is that statement correct? Time to go back to the code and check `khugepaged_scan_pmd()`. |
| 20/2 | 2026/08/04 19:41 | tool | grepper |
0mArgs:{
"Expression": "khugepaged_scan_pmd"
}Results: {
"Output": "include/trace/events/huge_memory.h=49=SCAN_STATUS\n--\ninclude/trace/events/huge_memory.h-55-\ninclude/trace/events/huge_memory.h:56:TRACE_EVENT(mm_khugepaged_scan_pmd,\ninclude/trace/events/huge_memory.h-57-\n--\nkernel/bpf/btf.c=6656=static const struct bpf_raw_tp_null_args raw_tp_null_args[] = {\n--\nkernel/bpf/btf.c-6705-\t/* huge_memory */\nkernel/bpf/btf.c:6706:\t{ \"mm_khugepaged_scan_pmd\", 0x10 },\nkernel/bpf/btf.c-6707-\t{ \"mm_collapse_huge_page_isolate\", 0x1 },\n--\nmm/khugepaged.c=1689=static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n--\nmm/khugepaged.c-1813-out:\nmm/khugepaged.c:1814:\ttrace_mm_khugepaged_scan_pmd(mm, folio, referenced,\nmm/khugepaged.c-1815-\t\t\t\t ctx.none_or_zero, result, ctx.unmapped);\n"
} |
| 21/2 | 2026/08/04 19:41 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33335 output: 23 thoughts: 46 Thoughts: Okay, let's see... **Identifying a Code Change** Wait a minute, *khugepaged_scan_pmd()* has been renamed to *collapse_scan_pmd()*? That's a significant change, I need to understand that. Let's trace this back to the original code for a bit. Need to check what *khugepaged_scan_pmd* used to do. |
| 22/2 | 2026/08/04 19:41 | tool | grepper |
0mArgs:{
"Expression": "khugepaged_scan_pmd"
}Results: {
"Output": "include/trace/events/huge_memory.h=49=SCAN_STATUS\n--\ninclude/trace/events/huge_memory.h-55-\ninclude/trace/events/huge_memory.h:56:TRACE_EVENT(mm_khugepaged_scan_pmd,\ninclude/trace/events/huge_memory.h-57-\n--\nkernel/bpf/btf.c=6656=static const struct bpf_raw_tp_null_args raw_tp_null_args[] = {\n--\nkernel/bpf/btf.c-6705-\t/* huge_memory */\nkernel/bpf/btf.c:6706:\t{ \"mm_khugepaged_scan_pmd\", 0x10 },\nkernel/bpf/btf.c-6707-\t{ \"mm_collapse_huge_page_isolate\", 0x1 },\n--\nmm/khugepaged.c=1689=static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n--\nmm/khugepaged.c-1813-out:\nmm/khugepaged.c:1814:\ttrace_mm_khugepaged_scan_pmd(mm, folio, referenced,\nmm/khugepaged.c-1815-\t\t\t\t ctx.none_or_zero, result, ctx.unmapped);\n"
} |
| 23/2 | 2026/08/04 19:41 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33850 output: 20 thoughts: 20 Thoughts: Okay, here's my attempt at summarizing those thoughts from a first-person, expert perspective, aiming for a fulsome rendering: **Investigating `collapse_scan_pmd()`** Right, so I need to understand what's happening with this particular function, `collapse_scan_pmd()`. That's the key. My immediate thought is, where is this function actually *used*? That's the most crucial piece of information to understand its role within the broader system. I need to trace its call sites, see the context in which it's being invoked. Knowing the calling functions and their associated data structures will give me the crucial context I need to figure out what `collapse_scan_pmd()` is supposed to achieve. It’s the usual debugging process: understand the where before the why. I know where to start though. Time to hunt down those calls! |
| 24/2 | 2026/08/04 19:41 | tool | grepper |
0mArgs:{
"Expression": "collapse_scan_pmd"
}Results: {
"Output": "mm/khugepaged.c=1224=static enum scan_result check_pmd_still_valid(struct mm_struct *mm,\n--\nmm/khugepaged.c-1238- * Bring missing pages in from swap, to complete THP collapse.\nmm/khugepaged.c:1239: * Only done if collapse_scan_pmd() believes it is worthwhile.\nmm/khugepaged.c-1240- *\n--\nmm/khugepaged.c=1562=static unsigned int max_order_from_offset(unsigned int offset)\n--\nmm/khugepaged.c-1571- * mthp_collapse() consumes the bitmap that is generated during\nmm/khugepaged.c:1572: * collapse_scan_pmd() to determine what regions and mTHP orders fit best.\nmm/khugepaged.c-1573- *\n--\nmm/khugepaged.c=1587=static enum scan_result mthp_collapse(struct mm_struct *mm,\n--\nmm/khugepaged.c-1688-\nmm/khugepaged.c:1689:static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\nmm/khugepaged.c-1690-\t\tstruct vm_area_struct *vma, unsigned long start_addr,\n--\nmm/khugepaged.c=2811=static enum scan_result collapse_single_pmd(unsigned long addr,\n--\nmm/khugepaged.c-2823-\tif (vma_is_anonymous(vma)) {\nmm/khugepaged.c:2824:\t\tresult = collapse_scan_pmd(mm, vma, addr, lock_dropped, cc);\nmm/khugepaged.c-2825-\t\tgoto end;\n"
} |
| 25/2 | 2026/08/04 19:41 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 34556 output: 20 thoughts: 14 Thoughts: Okay, here's my attempt at summarizing the thought process, as if I were thinking it: **Focusing on `collapse_single_pmd()`** Right, so I'm looking at this code, and the context is that the function in question here is `collapse_single_pmd()`. That's the key. I need to understand what this function is doing and where it's called from. That's my starting point. I need to figure out the significance of this specific function. Is it a core component? Is it used frequently? What are the inputs? What are the outputs? Gotta drill down into the details of *this* particular function. |
| 26/2 | 2026/08/04 19:41 | tool | grepper |
0mArgs:{
"Expression": "collapse_single_pmd"
}Results: {
"Output": "mm/khugepaged.c=2701=static enum scan_result collapse_scan_file(struct mm_struct *mm,\n--\nmm/khugepaged.c-2810- */\nmm/khugepaged.c:2811:static enum scan_result collapse_single_pmd(unsigned long addr,\nmm/khugepaged.c-2812-\t\tstruct vm_area_struct *vma, bool *lock_dropped,\n--\nmm/khugepaged.c=2868=static void collapse_scan_mm_slot(unsigned int progress_max,\n--\nmm/khugepaged.c-2938-\nmm/khugepaged.c:2939:\t\t\t*result = collapse_single_pmd(khugepaged_scan.address,\nmm/khugepaged.c-2940-\t\t\t\t\t\t vma, \u0026lock_dropped, cc);\n--\nmm/khugepaged.c=3209=int madvise_collapse(struct vm_area_struct *vma, unsigned long start,\n--\nmm/khugepaged.c-3257-\nmm/khugepaged.c:3258:\t\tresult = collapse_single_pmd(addr, vma, \u0026mmap_unlocked, cc);\nmm/khugepaged.c-3259-\n"
} |
| 27/2 | 2026/08/04 19:41 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 35079 output: 20 thoughts: 5304 Thoughts: **Reflecting on the Changes to Anonymous VMA Handling** Okay, so I'm diving into this patch that seems to be about refactoring some code related to collapsing huge pages, specifically in the context of anonymous VMAs. Initially, it looked like a simple clean-up, but the more I dig, the more I realize it has functional implications. The core of the patch revolves around this `collapse_check_pte()` function. It's essentially extracting the logic for checking page table entries that was previously duplicated in `collapse_scan_pmd()` and `__collapse_huge_page_isolate()`. The primary focus appears to be on confirming if pages within the virtual memory area are actually anonymous, which is critical for proper collapse behavior. Initially, I pondered: Is it even *possible* to have a non-anonymous page within an anonymous VMA? I started running through the ways a user might try to sneak a non-anon page in. `mmap(MAP_ANONYMOUS)` followed by `vmsplice`, `mremap`, `userfaultfd`, all kinds of madvise calls, `io_uring`, `bpf`... it seemed unlikely, bordering on impossible, without a rather serious kernel bug. It seems that `/dev/zero` is equivalent to using `MAP_ANONYMOUS`, so even that doesn't change anything. However, the change from `VM_BUG_ON_FOLIO` to `VM_WARN_ON_FOLIO` when a non-anonymous folio is encountered is the key here. In the old code, this would have triggered a kernel panic. With the patch, the system now *warns* and continues. This is the critical change. And that warning applies to `collapse_scan_pmd()` as well. The interesting question is then: *Can* this warning ever be triggered? The code appears to be designed to prevent this situation. It's a defensive check, an extra layer of sanity. Yet, the patch is from syz-cluster, which might mean that this condition is, in fact, reachable in some weird corner case. The vulnerability comes from the potential race condition. The mmap_lock is dropped between the scan and the collapse attempt. Another thread might change the mappings. However, the revalidation step in `collapse_huge_page()` seems to handle that by calling `hugepage_vma_revalidate()`. So, it seems that if a user *could* somehow map a non-anonymous page into an anonymous VMA (perhaps through a complex race condition, a device driver with a dodgy ioctl, or a subtle interaction with a new kernel feature), the old code would have panicked. This patch replaces that with a warning and a graceful fallback. This now offers syzkaller a way to test for those conditions and potentially expose a subtle bug, even if I'm not entirely convinced such a scenario is easily achievable. I'll need to go search for `SCAN_PAGE_ANON` in the kernel to see if there is history on this. |
| 28/2 | 2026/08/04 19:42 | tool | grepper |
0mArgs:{
"Expression": "SCAN_PAGE_ANON"
}Results: {
"Output": "include/trace/events/huge_memory.h-26-\tEM( SCAN_PAGE_LOCK,\t\t\"page_locked\")\t\t\t\\\ninclude/trace/events/huge_memory.h:27:\tEM( SCAN_PAGE_ANON,\t\t\"page_not_anon\")\t\t\\\ninclude/trace/events/huge_memory.h-28-\tEM( SCAN_PAGE_LAZYFREE,\t\t\"page_lazyfree\")\t\t\\\n--\nmm/khugepaged.c=33=enum scan_result {\n--\nmm/khugepaged.c-49-\tSCAN_PAGE_LOCK,\nmm/khugepaged.c:50:\tSCAN_PAGE_ANON,\nmm/khugepaged.c-51-\tSCAN_PAGE_LAZYFREE,\n--\nmm/khugepaged.c=747=static enum pte_check_result collapse_check_pte(pte_t pteval,\n--\nmm/khugepaged.c-792-\t\tVM_WARN_ON_FOLIO(!folio_test_anon(ctx-\u003efolio), ctx-\u003efolio);\nmm/khugepaged.c:793:\t\treturn pte_check_fail(ctx, SCAN_PAGE_ANON);\nmm/khugepaged.c-794-\t}\n--\nmm/khugepaged.c=1153=static enum scan_result hugepage_vma_revalidate(struct mm_struct *mm, unsigned long address,\n--\nmm/khugepaged.c-1186-\tif (expect_anon \u0026\u0026 (!(*vmap)-\u003eanon_vma || !vma_is_anonymous(*vmap)))\nmm/khugepaged.c:1187:\t\treturn SCAN_PAGE_ANON;\nmm/khugepaged.c-1188-\treturn SCAN_SUCCEED;\n"
} |
| 29/2 | 2026/08/04 19:42 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 41509 output: 29 thoughts: 5232 Thoughts: **Refactoring Analysis of khugepaged.c Patch**
Okay, so I've been staring at this patch, and my initial thought was, "Is this just a refactoring, or are we looking at a bug waiting to happen?" `SCAN_PAGE_ANON` is flagged in `hugepage_vma_revalidate()`, but `collapse_check_pte()` is where it's specifically returned for a page. So, if that warning triggers, it's a bug, right?
After digging in, it *seems* like a pure refactoring that consolidates PTE checking logic using a helper function, `collapse_check_pte()`, between `collapse_scan_pmd()` and `__collapse_huge_page_isolate()`. The upshot is the merging of a `VM_BUG_ON_FOLIO` with a silent return into a single `VM_WARN_ON_FOLIO` + return, which, on the face of it, is a functional change, but as a side effect.
My expert gut tells me this could be a minefield for bugs, even if seemingly minor. Refactoring core MM code like this? Risky business. It’s worth checking if the logic was perfectly preserved across the change. For instance, the initialization of `ctx.unmapped` being `0` in `collapse_scan_pmd()` and `-1` in `__collapse_huge_page_isolate()` had me on alert.
Thankfully, after a line-by-line comparison, and a deep breath, it appears the author was meticulously careful. The old logic for PTE presence, UFFD checks, shared page checks (`folio_maybe_mapped_shared`), and even the lazyfree checks (`folio_test_lazyfree`) are preserved! The original code used to look like this:
```c
if (!pte_present(pteval)) {
...
}
```
Where the code now looks like this:
```c
if (!pte_present(pteval))
{
if (ctx->unmapped == -1)
return pte_check_fail(ctx, SCAN_PTE_NON_PRESENT);
}
```
The difference being the unmapped context which, after inspection, is handled correctly. And the remaining checks.
However, the change introduces the use of a `ctx` in the new helper function. This is all very good, but what about the order in which the folio checks are done? After careful consideration, it turns out that the `folio_expected_ref_count(folio) != folio_ref_count(folio)` check *is* correctly performed *after* the `folio_maybe_mapped_shared(folio)` check, as it was in the old code.
Also, the `folio` is updated at the correct time.
Wait a second - *is* there a difference? Let's check `collapse_scan_pmd()` and `__collapse_huge_page_isolate()` *again*. The logic for detecting swapped PTEs with UFFD enabled (`pte_swp_uffd_any(pteval)`) and write protection is intact. The same can be said for how the reference counting is handled.
Okay, so the ONLY functional change is the `VM_WARN_ON_FOLIO` for non-anonymous folios.
Now, should I recommend fuzzing this, despite the rigorous checks? This patch *does* touch core memory management code. Refactoring patches in core MM *are* worth fuzzing, even if I'm not seeing any immediate red flags. They can easily introduce subtle bugs, such as uninitialized variables or wrong return values.
Wait, back to those subtleties. Let’s do a final deep-dive check.
`ctx.unmapped`, which is not explicitly initialized in the struct, is zero-initialized by C. No issues there. `ctx.result` is properly handled by `pte_check_fail()`. And `ctx.folio` is correctly set within `collapse_check_pte()` before it's used.
But wait, if `pte_none_or_zero(pteval)` returns true, `collapse_check_pte()` returns `PTE_CHECK_CONTINUE`. `ctx->folio` *isn't* updated in that case. So, when the loop continues, `ctx->folio` holds the value from the *previous* iteration. Does this matter? After checking the code, it turns out this is how the old code *also* worked! `folio` is used later in `trace_mm_khugepaged_scan_pmd()`. So the behavior is preserved.
In fact, the logic for `__collapse_huge_page_isolate()` is also exactly the same!
|
| 30/2 | 2026/08/04 19:42 | tool | grepper |
0mArgs:{
"Expression": "TRACE_EVENT\\(mm_khugepaged_scan_pmd"
}Results: {
"Output": "include/trace/events/huge_memory.h=49=SCAN_STATUS\n--\ninclude/trace/events/huge_memory.h-55-\ninclude/trace/events/huge_memory.h:56:TRACE_EVENT(mm_khugepaged_scan_pmd,\ninclude/trace/events/huge_memory.h-57-\n"
} |
| 31/2 | 2026/08/04 19:43 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 47891 output: 38 thoughts: 23 Thoughts: Here's a summary of my thought process, as requested: **Diving into the Huge Memory Events** Okay, time to get my hands dirty. I need to understand what's happening with these huge memory events, so I'm going to jump into the code. Specifically, I'm aiming for `include/trace/events/huge_memory.h`, and I think the juicy stuff starts around line 56. I'm expecting to find the definitions for the relevant tracepoints or structures there. This should give me a solid foundation for interpreting the trace data I'm seeing and diagnosing any potential issues. Line 56 is just a starting point, of course; I'll likely be scanning around that area to get the full picture. Let's see what gems we find. |
| 32/2 | 2026/08/04 19:43 | tool | read-file |
0mArgs:{
"File": "include/trace/events/huge_memory.h",
"FirstLine": 56,
"LineCount": 20
}Results: {
"Contents": " 56:\tTRACE_EVENT(mm_khugepaged_scan_pmd,\n 57:\t\n 58:\t\tTP_PROTO(struct mm_struct *mm, struct folio *folio,\n 59:\t\t\t int referenced, int none_or_zero, int status, int unmapped),\n 60:\t\n 61:\t\tTP_ARGS(mm, folio, referenced, none_or_zero, status, unmapped),\n 62:\t\n 63:\t\tTP_STRUCT__entry(\n 64:\t\t\t__field(struct mm_struct *, mm)\n 65:\t\t\t__field(unsigned long, pfn)\n 66:\t\t\t__field(int, referenced)\n 67:\t\t\t__field(int, none_or_zero)\n 68:\t\t\t__field(int, status)\n 69:\t\t\t__field(int, unmapped)\n 70:\t\t),\n 71:\t\n 72:\t\tTP_fast_assign(\n 73:\t\t\t__entry-\u003emm = mm;\n 74:\t\t\t__entry-\u003epfn = folio ? folio_pfn(folio) : -1;\n 75:\t\t\t__entry-\u003ereferenced = referenced;\n"
} |
| 33/2 | 2026/08/04 19:43 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 48421 output: 27 thoughts: 90 Thoughts: Okay, here's my take on those thoughts, framed as my own: **Analyzing That Folio PFN Check** Alright, that `folio ? folio_pfn(folio) : -1` snippet is interesting. It's a nice, safe way to handle a NULL folio; no surprises there. And just to confirm, thinking about it... even if it's a zone device folio, `folio_pfn()` should be perfectly safe, right? Yep, it is. So, this discrepancy, this conditional in the code, is completely benign. It just alters the physical page number logged in the tracepoint in this *specific* error condition. It's not introducing a bug or anything problematic. Just a logging detail. Hmm, but wait... I need to consider the bigger picture here. What's the relationship between this and `trace_mm_collapse_huge_page_isolate()`? Is there any interaction with that function that I need to be aware of? Let me think through the implications of that for a moment... |
| 34/2 | 2026/08/04 19:43 | tool | grepper |
0mArgs:{
"Expression": "TRACE_EVENT\\(mm_collapse_huge_page_isolate"
}Results: {
"Output": "include/trace/events/huge_memory.h=90=TRACE_EVENT(mm_collapse_huge_page,\n--\ninclude/trace/events/huge_memory.h-116-\ninclude/trace/events/huge_memory.h:117:TRACE_EVENT(mm_collapse_huge_page_isolate,\ninclude/trace/events/huge_memory.h-118-\n"
} |
| 35/2 | 2026/08/04 19:43 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 48864 output: 39 thoughts: 6 Thoughts: Okay, I'm ready. Let's get to it. Here's a summary, in the first person, assuming I'm the expert: **Initial Assessment and Processing** Alright, let's dive into this. The initial step is, of course, to read the text. It's the foundation of any analysis. We need to parse the language, grasp the core ideas, and begin to form an internal model of its argument or content. Depending on the field, I’m immediately searching for specific keywords or concepts, perhaps related to methodologies, results, or implications. My prior knowledge and experience will immediately start to inform the process. I'm not just passively absorbing; I'm actively filtering, categorizing, and questioning as I go. Does this align with existing literature? Are there any immediate red flags, perhaps inconsistencies or unsupported claims? It’s a very dynamic process, this initial read. I'm trying to get a sense of the author's intent and scope quickly. I need to understand what this is all about! |
| 36/2 | 2026/08/04 19:43 | tool | read-file |
0mArgs:{
"File": "include/trace/events/huge_memory.h",
"FirstLine": 117,
"LineCount": 20
}Results: {
"Contents": " 117:\tTRACE_EVENT(mm_collapse_huge_page_isolate,\n 118:\t\n 119:\t\tTP_PROTO(struct folio *folio, int none_or_zero,\n 120:\t\t\t int referenced, int status, unsigned int order),\n 121:\t\n 122:\t\tTP_ARGS(folio, none_or_zero, referenced, status, order),\n 123:\t\n 124:\t\tTP_STRUCT__entry(\n 125:\t\t\t__field(unsigned long, pfn)\n 126:\t\t\t__field(int, none_or_zero)\n 127:\t\t\t__field(int, referenced)\n 128:\t\t\t__field(int, status)\n 129:\t\t\t__field(unsigned int, order)\n 130:\t\t),\n 131:\t\n 132:\t\tTP_fast_assign(\n 133:\t\t\t__entry-\u003epfn = folio ? folio_pfn(folio) : -1;\n 134:\t\t\t__entry-\u003enone_or_zero = none_or_zero;\n 135:\t\t\t__entry-\u003ereferenced = referenced;\n 136:\t\t\t__entry-\u003estatus = status;\n"
} |
| 37/2 | 2026/08/04 19:43 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 49442 output: 22 thoughts: 2289 Thoughts: **Assessment of the Patch's Impact and the Need for Fuzzing** Alright, let's break this down. My primary concern is ensuring this refactoring doesn't introduce any subtle regressions, especially given its touch on core MM components like khugepaged. The core functional change boils down to replacing a `VM_BUG_ON_FOLIO` with a `VM_WARN_ON_FOLIO` and the corresponding return value. I also added `VM_WARN_ON_FOLIO` in `collapse_scan_pmd()`. It seems that the logic's overall behavior is maintained. Specifically, I've checked multiple sections: The handling of NULL values, checks on various `ctx` fields (`unmapped`, `shared`, `none_or_zero`) and their interactions with limits, including looking into `collapse_max_ptes_none()` and how that is defined and used, and the initialization `collapse_control_init_scan(cc)`. This seems correct. I spent a fair bit of time on `collapse_scan_file()` and confirming the addition of `bitmap_zero` is safe there. My focus was primarily on `collapse_check_pte()` to ensure that the logic is identical, paying particular attention to the interactions between `pte_none_or_zero()` and `pte_present()`, and how they flow through `__collapse_huge_page_isolate()`. The behavior seems identical. While the semantic change seems limited, the introduction of the `VM_WARN_ON_FOLIO` is a potential trigger for an edge case where a non-anon folio might get mapped into anon VMA. Given the core MM nature of the changes, the risk of regressions, and the potential for a subtle edge case with the warning, it's prudent to treat this as a candidate for fuzzing. I'll flag `WorthFuzzing=true`. For fuzzing targets, I'll focus on the critical functions: `collapse_check_pte`, `collapse_scan_pmd`, `__collapse_huge_page_isolate` and `mthp_collapse`. I'll want to ensure that I have `TRANSPARENT_HUGEPAGE` enabled. I can also look at `READ_ONLY_THP_FOR_FS`, but it might not be relevant. I will also enable `USERFAULTFD`, since there's the check for `pte_uffd` in the code. |
| 38/2 | 2026/08/04 19:43 | tool | grepper |
0mArgs:{
"Expression": "config TRANSPARENT_HUGEPAGE"
}Results: {
"Output": "mm/Kconfig=829=config MM_ID\n--\nmm/Kconfig-831-\nmm/Kconfig:832:menuconfig TRANSPARENT_HUGEPAGE\nmm/Kconfig-833-\tbool \"Transparent Hugepage Support\"\n--\nmm/Kconfig=850=choice\n--\nmm/Kconfig-855-\nmm/Kconfig:856:\tconfig TRANSPARENT_HUGEPAGE_ALWAYS\nmm/Kconfig-857-\t\tbool \"always\"\n--\nmm/Kconfig-862-\nmm/Kconfig:863:\tconfig TRANSPARENT_HUGEPAGE_MADVISE\nmm/Kconfig-864-\t\tbool \"madvise\"\n--\nmm/Kconfig-871-\nmm/Kconfig:872:\tconfig TRANSPARENT_HUGEPAGE_NEVER\nmm/Kconfig-873-\t\tbool \"never\"\n--\nmm/Kconfig=879=choice\n--\nmm/Kconfig-888-\nmm/Kconfig:889:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_NEVER\nmm/Kconfig-890-\t\tbool \"never\"\n--\nmm/Kconfig-898-\nmm/Kconfig:899:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ALWAYS\nmm/Kconfig-900-\t\tbool \"always\"\n--\nmm/Kconfig-906-\nmm/Kconfig:907:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_WITHIN_SIZE\nmm/Kconfig-908-\t\tbool \"within_size\"\n--\nmm/Kconfig-914-\nmm/Kconfig:915:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ADVISE\nmm/Kconfig-916-\t\tbool \"advise\"\n--\nmm/Kconfig=924=choice\n--\nmm/Kconfig-933-\nmm/Kconfig:934:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_NEVER\nmm/Kconfig-935-\t\tbool \"never\"\n--\nmm/Kconfig-942-\nmm/Kconfig:943:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ALWAYS\nmm/Kconfig-944-\t\tbool \"always\"\n--\nmm/Kconfig-950-\nmm/Kconfig:951:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_WITHIN_SIZE\nmm/Kconfig-952-\t\tbool \"within_size\"\n--\nmm/Kconfig-958-\nmm/Kconfig:959:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ADVISE\nmm/Kconfig-960-\t\tbool \"advise\"\n"
} |
| 39/2 | 2026/08/04 19:43 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 52986 output: 19 thoughts: 324 Thoughts: **My Reasoning for Selecting Relevant Symbols** Okay, so I'm focusing on the significant configurations and API entry points related to Transparent Huge Pages (THP) for this task. The primary configuration I need to consider is, of course, `TRANSPARENT_HUGEPAGE`. I'll also bring in `USERFAULTFD` since it's likely relevant to the overall picture of THP management. Now, I'm at the point where I need to select functions. The instructions specify a preference for non-static, non-inlined API entry points over internal static helper functions. I need to be careful here because the compiler can inline. Let me double-check `collapse_check_pte`. Yep, it's static: ```c static enum pte_check_result collapse_check_pte(pte_t pteval, unsigned long addr, struct pte_check_context *ctx) ``` Therefore, I *won't* include it. It's likely inlined, and not a suitable entry point. Instead, I considered: `collapse_scan_pmd`, `__collapse_huge_page_isolate`, `mthp_collapse`, and `collapse_huge_page`. But, I quickly realized that those are all also static! So they are not what I need. I need to find the non-static entrypoints. Hmm, I know `khugepaged_scan_mm_slot` is static, and `khugepaged` itself (the kernel thread) is also static. Ah! Wait a minute, `madvise_collapse` *should* be non-static. I'll double check that. |
| 40/2 | 2026/08/04 19:43 | tool | grepper |
0mArgs:{
"Expression": "madvise_collapse"
}Results: {
"Output": "Documentation/admin-guide/mm/transhuge.rst=346=A higher value may increase memory footprint for some workloads.\n--\nDocumentation/admin-guide/mm/transhuge.rst-353-\nDocumentation/admin-guide/mm/transhuge.rst:354: Currently, madvise_collapse only supports collapsing to PMD-sized THPs\nDocumentation/admin-guide/mm/transhuge.rst-355- and does not attempt mTHP collapses.\n--\ninclude/linux/huge_mm.h=511=int hugepage_madvise(struct vm_area_struct *vma, vm_flags_t *vm_flags,\ninclude/linux/huge_mm.h-512-\t\t int advice);\ninclude/linux/huge_mm.h:513:int madvise_collapse(struct vm_area_struct *vma, unsigned long start,\ninclude/linux/huge_mm.h-514-\t\t unsigned long end, bool *lock_dropped);\n--\ninclude/linux/huge_mm.h=712=static inline int hugepage_madvise(struct vm_area_struct *vma,\n--\ninclude/linux/huge_mm.h-717-\ninclude/linux/huge_mm.h:718:static inline int madvise_collapse(struct vm_area_struct *vma,\ninclude/linux/huge_mm.h-719-\t\t\t\t unsigned long start,\n--\nmm/khugepaged.c=3172=bool current_is_khugepaged(void)\n--\nmm/khugepaged.c-3176-\nmm/khugepaged.c:3177:static int madvise_collapse_errno(enum scan_result r)\nmm/khugepaged.c-3178-{\n--\nmm/khugepaged.c-3208-\nmm/khugepaged.c:3209:int madvise_collapse(struct vm_area_struct *vma, unsigned long start,\nmm/khugepaged.c-3210-\t\t unsigned long end, bool *lock_dropped)\n--\nmm/khugepaged.c-3296-\treturn thps == ((hend - hstart) \u003e\u003e HPAGE_PMD_SHIFT) ? 0\nmm/khugepaged.c:3297:\t\t\t: madvise_collapse_errno(last_fail);\nmm/khugepaged.c-3298-}\n--\nmm/madvise.c=1338=static int madvise_vma_behavior(struct madvise_behavior *madv_behavior)\n--\nmm/madvise.c-1362-\tcase MADV_COLLAPSE:\nmm/madvise.c:1363:\t\treturn madvise_collapse(vma, range-\u003estart, range-\u003eend,\nmm/madvise.c-1364-\t\t\t\u0026madv_behavior-\u003elock_dropped);\n--\ntools/testing/selftests/mm/khugepaged.c=231=static void fill_memory(int *p, unsigned long start, unsigned long end)\n--\ntools/testing/selftests/mm/khugepaged.c-244- */\ntools/testing/selftests/mm/khugepaged.c:245:static int madvise_collapse_retry(void *p, unsigned long size)\ntools/testing/selftests/mm/khugepaged.c-246-{\n--\ntools/testing/selftests/mm/khugepaged.c=263=static void *alloc_hpage(struct mem_ops *ops)\n--\ntools/testing/selftests/mm/khugepaged.c-276-\tksft_print_msg(\"Allocate huge page...\");\ntools/testing/selftests/mm/khugepaged.c:277:\tif (madvise_collapse_retry(p, hpage_pmd_size))\ntools/testing/selftests/mm/khugepaged.c-278-\t\tksft_exit_fail_perror(\"madvise(MADV_COLLAPSE)\");\n--\ntools/testing/selftests/mm/khugepaged.c=504=static bool is_anon(struct mem_ops *ops)\n--\ntools/testing/selftests/mm/khugepaged.c-508-\ntools/testing/selftests/mm/khugepaged.c:509:static void __madvise_collapse(const char *msg, char *p, int nr_hpages,\ntools/testing/selftests/mm/khugepaged.c-510-\t\t\t struct mem_ops *ops, bool expect)\n--\ntools/testing/selftests/mm/khugepaged.c-532-\tmadvise(p, nr_hpages * hpage_pmd_size, MADV_HUGEPAGE);\ntools/testing/selftests/mm/khugepaged.c:533:\tret = madvise_collapse_retry(p, nr_hpages * hpage_pmd_size);\ntools/testing/selftests/mm/khugepaged.c-534-\tif (((bool)ret) == expect)\n--\ntools/testing/selftests/mm/khugepaged.c-543-\ntools/testing/selftests/mm/khugepaged.c:544:static void madvise_collapse(const char *msg, char *p, int nr_hpages,\ntools/testing/selftests/mm/khugepaged.c-545-\t\t\t struct mem_ops *ops, bool expect)\n--\ntools/testing/selftests/mm/khugepaged.c-549-\t\tksft_exit_fail_msg(\"Unexpected huge page\\n\");\ntools/testing/selftests/mm/khugepaged.c:550:\t__madvise_collapse(msg, p, nr_hpages, ops, expect);\ntools/testing/selftests/mm/khugepaged.c-551-}\n--\ntools/testing/selftests/mm/khugepaged.c=620=static struct collapse_context __madvise_context = {\ntools/testing/selftests/mm/khugepaged.c:621:\t.collapse = \u0026madvise_collapse,\ntools/testing/selftests/mm/khugepaged.c-622-\t.enforce_pte_scan_limits = false,\n--\ntools/testing/selftests/mm/khugepaged.c=988=static void collapse_max_ptes_shared(struct collapse_context *c, struct mem_ops *ops)\n--\ntools/testing/selftests/mm/khugepaged.c-1045-\ntools/testing/selftests/mm/khugepaged.c:1046:static void madvise_collapse_existing_thps(struct collapse_context *c,\ntools/testing/selftests/mm/khugepaged.c-1047-\t\t\t\t\t struct mem_ops *ops)\n--\ntools/testing/selftests/mm/khugepaged.c-1056-\t/* c-\u003ecollapse() will find a hugepage and complain - call directly. */\ntools/testing/selftests/mm/khugepaged.c:1057:\t__madvise_collapse(\"Re-collapse PMD-mapped hugepage\", p, 1, ops, true);\ntools/testing/selftests/mm/khugepaged.c-1058-\tvalidate_memory(p, 0, hpage_pmd_size);\n--\ntools/testing/selftests/mm/khugepaged.c=1208=int main(int argc, char **argv)\n--\ntools/testing/selftests/mm/khugepaged.c-1326-\ntools/testing/selftests/mm/khugepaged.c:1327:\tTEST(madvise_collapse_existing_thps, madvise_context, anon_ops);\ntools/testing/selftests/mm/khugepaged.c:1328:\tTEST(madvise_collapse_existing_thps, madvise_context, read_only_file_ops);\ntools/testing/selftests/mm/khugepaged.c:1329:\tTEST(madvise_collapse_existing_thps, madvise_context, read_write_file_read_ops);\ntools/testing/selftests/mm/khugepaged.c:1330:\tTEST(madvise_collapse_existing_thps, madvise_context, shmem_ops);\ntools/testing/selftests/mm/khugepaged.c-1331-\n"
} |
| 41/2 | 2026/08/04 19:43 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 55853 output: 160 thoughts: 511 Thoughts: **My Analysis of Potential Fuzzing Entrypoints** Okay, so I'm digging into this code and trying to figure out the best points to target for fuzzing. The goal, as I understand it, is to find non-static, non-inlined API entrypoints, as these are the functions that represent the "front door" of the functionality we want to test. Internal helper functions, being inlined by the compiler, are essentially hidden within the larger structures and don't offer as clear a testing surface. Initially, I identified `madvise_collapse` as a key candidate. It's a non-static entrypoint, perfect. I need to include it in FocusSymbols. I also looked at `khugepaged`, but that's a static kernel thread – so it's out. Then, I looked at `khugepaged_scan_pmd`, but that's been renamed to `collapse_scan_pmd`. `khugepaged_scan_mm_slot` is also not appropriate. After more investigation, `khugepaged_enter` and `khugepaged_enter_vma` also seem to fit the bill as non-static functions. However, the prompt really emphasizes preferring the non-static, non-inlined API entrypoints. So, I need to stay focused on `madvise_collapse`. Based on my understanding of the patch and the goal, the most effective entrypoint seems to be `madvise_collapse`. I'm going to stick with that. The broader context suggests this entrypoint is worth fuzzing, that's clear. The patch refactors some PTE checking logic, and `madvise(MADV_COLLAPSE)` and `khugepaged` can help verify that this change doesn't introduce regressions, specifically around PTE scanning or uninitialized variable usage. Now, let's make sure the configuration options are set appropriately. I need to check `USERFAULTFD` because the code appears to use `pte_uffd`. I've confirmed that `USERFAULTFD` is indeed needed. Also, `TRANSPARENT_HUGEPAGE` is essential because both `khugepaged` and `madvise_collapse` are part of the THP functionality. So, here's the plan: * **WorthFuzzing:** `true` * **FocusSymbols:** `["madvise_collapse"]` * **EnableConfigs:** `["TRANSPARENT_HUGEPAGE", "USERFAULTFD"]` * **Reasoning:** "The patch refactors PTE checking logic for THP collapse into a new helper function `collapse_check_pte()`. It also modifies the behavior when a non-anonymous folio is encountered in an anonymous VMA, changing a `VM_BUG_ON_FOLIO` to a `VM_WARN_ON_FOLIO` and returning an error. Fuzzing `madvise(MADV_COLLAPSE)` and `khugepaged` can help verify that this refactoring doesn't introduce subtle regressions in PTE scanning or uninitialized variable usage." I'm confident in this assessment. I'm going to call `set-results` now. |
| 42/2 | 2026/08/04 19:43 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE",
"USERFAULTFD"
],
"FocusSymbols": [
"madvise_collapse"
],
"Reasoning": "The patch refactors PTE checking logic for THP collapse into a new helper function collapse_check_pte(). It also modifies the behavior when a non-anonymous folio is encountered in an anonymous VMA, changing a VM_BUG_ON_FOLIO to a VM_WARN_ON_FOLIO and returning an error. Fuzzing madvise(MADV_COLLAPSE) and khugepaged can help verify that this refactoring doesn't introduce subtle regressions in PTE scanning or uninitialized variable usage.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"TRANSPARENT_HUGEPAGE",
"USERFAULTFD"
],
"FocusSymbols": [
"madvise_collapse"
],
"Reasoning": "The patch refactors PTE checking logic for THP collapse into a new helper function collapse_check_pte(). It also modifies the behavior when a non-anonymous folio is encountered in an anonymous VMA, changing a VM_BUG_ON_FOLIO to a VM_WARN_ON_FOLIO and returning an error. Fuzzing madvise(MADV_COLLAPSE) and khugepaged can help verify that this refactoring doesn't introduce subtle regressions in PTE scanning or uninitialized variable usage.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|