AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/15 08:55 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "TRANSPARENT_HUGEPAGE_ALWAYS"
  ],
  "FocusSymbols": [
    "collapse_check_pte",
    "__collapse_huge_page_isolate",
    "collapse_scan_pmd",
    "collapse_scan_file",
    "collapse_huge_page"
  ],
  "Reasoning": "The patch refactors the PTE checking logic during Transparent Huge Page (THP) collapse in `khugepaged`. It introduces a new `pte_check_context` struct and `collapse_check_pte()` helper function, which are used in `__collapse_huge_page_isolate()` and `collapse_scan_pmd()`. This modifies the core memory management logic for collapsing pages into THPs, which is reachable via the background `khugepaged` daemon and the `madvise(MADV_COLLAPSE)` syscall.",
  "WorthFuzzing": true
}

1/1 2026/07/15 08:55 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 9b44c66615160935b81f25f2b8a44dd1205877bb\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Wed Jul 15 08:55:40 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 16f37135ed80df..b187d618452f44 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 55157567dc4cc1..40125dcb4de9d6 100644\n--- a/mm/khugepaged.c\n+++ b/mm/khugepaged.c\n@@ -64,6 +64,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@@ -118,6 +124,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+ * collapse_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 collapse_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\tMTHP_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\tMTHP_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\tMTHP_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,7 +866,7 @@ 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 * 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@@ -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 (collapse_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@@ -1078,7 +1178,7 @@ 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 * vmas.\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@@ -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\tMTHP_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@@ -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@@ -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\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+\t\tfolio = ctx.folio;\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 (collapse_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@@ -1784,13 +1806,13 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\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/07/15 08:55 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "TRANSPARENT_HUGEPAGE_ALWAYS"
  ],
  "FocusSymbols": [
    "collapse_check_pte",
    "__collapse_huge_page_isolate",
    "collapse_scan_pmd",
    "collapse_scan_file",
    "collapse_huge_page"
  ],
  "Reasoning": "The patch refactors the PTE checking logic during Transparent Huge Page (THP) collapse in `khugepaged`. It introduces a new `pte_check_context` struct and `collapse_check_pte()` helper function, which are used in `__collapse_huge_page_isolate()` and `collapse_scan_pmd()`. This modifies the core memory management logic for collapsing pages into THPs, which is reachable via the background `khugepaged` daemon and the `madvise(MADV_COLLAPSE)` syscall.",
  "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).

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.
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.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. 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 9b44c66615160935b81f25f2b8a44dd1205877bb
Author: syz-cluster <triage@syzkaller.com>
Date:   Wed Jul 15 08:55:40 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 16f37135ed80df..b187d618452f44 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 55157567dc4cc1..40125dcb4de9d6 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -64,6 +64,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>
 
@@ -118,6 +124,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,
 	}
 }
 
+/*
+ * collapse_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 collapse_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,7 +866,7 @@ 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
+		 * folio can't be freed from under us. NOTE: folio lock
 		 * is needed to serialize against split_huge_page
 		 * when invoked from the VM.
 		 */
@@ -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 (collapse_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;
 }
@@ -1078,7 +1178,7 @@ 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
 	 * vmas.
@@ -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
@@ -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);
@@ -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.
@@ -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);
+		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;
+		folio = ctx.folio;
 
 		/* 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 (collapse_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;
@@ -1784,13 +1806,13 @@ static enum scan_result collapse_scan_pmd(struct mm_struct *mm,
 		/* 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/07/15 08:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 10080
							output: 28
							thoughts: 463
						

Thoughts:
**Analyzing the Khugepaged PTE Checking Refactor**

Okay, so I'm looking at a patch that touches `mm/khugepaged.c` and `Documentation/admin-guide/mm/transhuge.rst`. Immediately, I see it's introducing a new `pte_check_context` struct and `collapse_check_pte()` which indicates a refactoring of the PTE checking logic within `__collapse_huge_page_isolate()` and `collapse_scan_pmd()`. It also brings in `collapse_is_referenced()` and `count_collapse_event()`. This is all clearly focused on cleaning up and potentially optimizing the khugepaged component, particularly that crucial PTE validation.

Given that `mm/khugepaged.c` deals with core kernel functionality – Transparent Huge Pages (THP) – and THP is a fairly standard feature with khugepaged constantly running in the background, I need to treat this change with considerable care. It's definitely reachable.

Let's break down the specific function changes. I'm seeing modifications to:
*   `__khugepaged_exit` (comment change)
*   `collapse_control_init_scan` (new)
*   `collapse_is_referenced` (new)
*   `count_collapse_event` (new)
*   `pte_check_fail` (new)
*   `collapse_check_pte` (new)
*   `__collapse_huge_page_isolate` (modified)
*   `hugepage_vma_revalidate` (comment change)
*   `alloc_charge_folio` (modified)
*   `collapse_huge_page` (modified)
*   `collapse_scan_pmd` (modified)
*   `collapse_scan_file` (modified)

The modified functions are the ones that should receive the highest scrutiny. I'm focusing on fuzzing these areas: `__collapse_huge_page_isolate`, `collapse_scan_pmd`, `collapse_scan_file`, and `collapse_huge_page`.

From a configuration perspective, I'll definitely be enabling `TRANSPARENT_HUGEPAGE`, and probably leaning toward either `TRANSPARENT_HUGEPAGE_MADVISE` or `TRANSPARENT_HUGEPAGE_ALWAYS` to fully exercise the changes during testing.

Now, let's have a look at the Kconfig file to confirm some assumptions.



4/2 2026/07/15 08:55 tool grepper
0m Args:
{
  "Expression": "khugepaged\\.o",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/Makefile=100=obj-$(CONFIG_DEVICE_MIGRATION) += migrate_device.o\nmm/Makefile:101:obj-$(CONFIG_TRANSPARENT_HUGEPAGE) += huge_memory.o khugepaged.o\nmm/Makefile-102-obj-$(CONFIG_PAGE_COUNTER) += page_counter.o\n"
}

5/2 2026/07/15 08:55 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 11207
							output: 28
							thoughts: 27
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Kernel Configuration Considerations**

Alright, so the immediate need is definitely `CONFIG_TRANSPARENT_HUGEPAGE`. That much is clear. But as we're digging into this, I know we can't stop there. My internal checklist immediately jumps to: what other configuration options might be relevant or impacted? This situation is rarely isolated. I need to quickly identify any dependencies, conflicts, or complementary features that I should be aware of and check out to make sure my final config is set up optimally. We all know how easy it is to miss something and end up with a performance bottleneck or, worse, instability. I need to make sure this config meshes well with any existing and/or future plans.



6/2 2026/07/15 08:55 tool grepper
0m Args:
{
  "Expression": "config TRANSPARENT_HUGEPAGE",
  "PathPrefix": ""
}

Results:
{
  "Output": "mm/Kconfig=835=config MM_ID\n--\nmm/Kconfig-837-\nmm/Kconfig:838:menuconfig TRANSPARENT_HUGEPAGE\nmm/Kconfig-839-\tbool \"Transparent Hugepage Support\"\n--\nmm/Kconfig=856=choice\n--\nmm/Kconfig-861-\nmm/Kconfig:862:\tconfig TRANSPARENT_HUGEPAGE_ALWAYS\nmm/Kconfig-863-\t\tbool \"always\"\n--\nmm/Kconfig-868-\nmm/Kconfig:869:\tconfig TRANSPARENT_HUGEPAGE_MADVISE\nmm/Kconfig-870-\t\tbool \"madvise\"\n--\nmm/Kconfig-877-\nmm/Kconfig:878:\tconfig TRANSPARENT_HUGEPAGE_NEVER\nmm/Kconfig-879-\t\tbool \"never\"\n--\nmm/Kconfig=885=choice\n--\nmm/Kconfig-894-\nmm/Kconfig:895:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_NEVER\nmm/Kconfig-896-\t\tbool \"never\"\n--\nmm/Kconfig-904-\nmm/Kconfig:905:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ALWAYS\nmm/Kconfig-906-\t\tbool \"always\"\n--\nmm/Kconfig-912-\nmm/Kconfig:913:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_WITHIN_SIZE\nmm/Kconfig-914-\t\tbool \"within_size\"\n--\nmm/Kconfig-920-\nmm/Kconfig:921:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ADVISE\nmm/Kconfig-922-\t\tbool \"advise\"\n--\nmm/Kconfig=930=choice\n--\nmm/Kconfig-939-\nmm/Kconfig:940:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_NEVER\nmm/Kconfig-941-\t\tbool \"never\"\n--\nmm/Kconfig-948-\nmm/Kconfig:949:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ALWAYS\nmm/Kconfig-950-\t\tbool \"always\"\n--\nmm/Kconfig-956-\nmm/Kconfig:957:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_WITHIN_SIZE\nmm/Kconfig-958-\t\tbool \"within_size\"\n--\nmm/Kconfig-964-\nmm/Kconfig:965:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ADVISE\nmm/Kconfig-966-\t\tbool \"advise\"\n"
}

7/2 2026/07/15 08:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12140
							output: 30
							thoughts: 192
						

Thoughts:
**Analyzing Huge Page Collapse Optimization**

Okay, so I'm thinking I need to add `TRANSPARENT_HUGEPAGE` and `TRANSPARENT_HUGEPAGE_ALWAYS` to the `EnableConfigs`. That seems like the right move, given what I'm trying to achieve.

Now, let's just make sure I've got my head around the functions that are going to be affected by these changes. I've got: `__collapse_huge_page_isolate`, `collapse_scan_pmd`, `collapse_scan_file`, and `collapse_huge_page`.

Right, all of those are either static or part of `khugepaged`. Okay, are any of these in a hot path? I have to consider the performance implications. `collapse_scan_pmd` is called by `khugepaged`, a background thread, so it's not a direct syscall hotpath, but it *does* scan memory, which is important. `collapse_huge_page` is also called by `khugepaged`. And then `__collapse_huge_page_isolate` is called by both `collapse_huge_page` and `madvise_collapse`. Need to check `madvise_collapse`. That's a critical path, so must take care there.



8/2 2026/07/15 08:56 tool grepper
0m Args:
{
  "Expression": "__collapse_huge_page_isolate",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/khugepaged.c=747=static enum pte_check_result collapse_check_pte(pte_t pteval,\n--\nmm/khugepaged.c-812-\nmm/khugepaged.c:813:static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\nmm/khugepaged.c-814-\t\tunsigned long start_addr, pte_t *pte, struct collapse_control *cc,\n--\nmm/khugepaged.c=996=static void __collapse_huge_page_copy_failed(pte_t *pte,\n--\nmm/khugepaged.c-1013-\t * Release both raw and compound pages isolated\nmm/khugepaged.c:1014:\t * in __collapse_huge_page_isolate.\nmm/khugepaged.c-1015-\t */\n--\nmm/khugepaged.c=1376=static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long start_addr,\n--\nmm/khugepaged.c-1477-\tif (pte) {\nmm/khugepaged.c:1478:\t\tresult = __collapse_huge_page_isolate(vma, start_addr, pte, cc,\nmm/khugepaged.c-1479-\t\t\t\t\t\t      order, \u0026compound_pagelist);\n"
}

9/2 2026/07/15 08:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13014
							output: 33
							thoughts: 64
						

Thoughts:
Here's my summary, as the expert:

**Tracing the Huge Page Collapse**

Okay, so let's break this down. My understanding is that `__collapse_huge_page_isolate` is the real workhorse here, called by `collapse_huge_page`. Right, that's straightforward. Now the question is, what triggers `collapse_huge_page` itself? It looks like the two primary paths are `khugepaged_scan_pmd` – the kernel's huge page management daemon – and potentially `madvise_collapse` which is related to userspace page advisory. I need to confirm that. Therefore, I should start by digging into the calling contexts of `collapse_huge_page` directly. That'll give me a clearer picture of how these huge pages are being targeted for collapse. Time to dive into the source.



10/2 2026/07/15 08:56 tool grepper
0m Args:
{
  "Expression": "collapse_huge_page",
  "PathPrefix": "mm/khugepaged.c"
}

Results:
{
  "Output": "mm/khugepaged.c=747=static enum pte_check_result collapse_check_pte(pte_t pteval,\n--\nmm/khugepaged.c-812-\nmm/khugepaged.c:813:static enum scan_result __collapse_huge_page_isolate(struct vm_area_struct *vma,\nmm/khugepaged.c-814-\t\tunsigned long start_addr, pte_t *pte, struct collapse_control *cc,\n--\nmm/khugepaged.c-920-\t\tresult = SCAN_SUCCEED;\nmm/khugepaged.c:921:\t\ttrace_mm_collapse_huge_page_isolate(folio, ctx.none_or_zero,\nmm/khugepaged.c-922-\t\t\t\t\t\t    referenced, result, order);\n--\nmm/khugepaged.c-926-\trelease_pte_pages(pte, _pte, compound_pagelist);\nmm/khugepaged.c:927:\ttrace_mm_collapse_huge_page_isolate(folio, ctx.none_or_zero,\nmm/khugepaged.c-928-\t\t\t\t\t    referenced, result, order);\n--\nmm/khugepaged.c-931-\nmm/khugepaged.c:932:static void __collapse_huge_page_copy_succeeded(pte_t *pte,\nmm/khugepaged.c-933-\t\tstruct vm_area_struct *vma, unsigned long address,\n--\nmm/khugepaged.c-995-\nmm/khugepaged.c:996:static void __collapse_huge_page_copy_failed(pte_t *pte,\nmm/khugepaged.c-997-\t\tpmd_t *pmd, pmd_t orig_pmd, struct vm_area_struct *vma,\n--\nmm/khugepaged.c-1013-\t * Release both raw and compound pages isolated\nmm/khugepaged.c:1014:\t * in __collapse_huge_page_isolate.\nmm/khugepaged.c-1015-\t */\n--\nmm/khugepaged.c-1019-/*\nmm/khugepaged.c:1020: * __collapse_huge_page_copy - attempts to copy memory contents from raw\nmm/khugepaged.c-1021- * pages to a hugepage. Cleans up the raw pages if copying succeeds;\n--\nmm/khugepaged.c-1033- */\nmm/khugepaged.c:1034:static enum scan_result __collapse_huge_page_copy(pte_t *pte, struct folio *folio,\nmm/khugepaged.c-1035-\t\tpmd_t *pmd, pmd_t orig_pmd, struct vm_area_struct *vma,\n--\nmm/khugepaged.c-1063-\tif (likely(result == SCAN_SUCCEED))\nmm/khugepaged.c:1064:\t\t__collapse_huge_page_copy_succeeded(pte, vma, address, ptl,\nmm/khugepaged.c-1065-\t\t\t\t\t\t    order, compound_pagelist);\nmm/khugepaged.c-1066-\telse\nmm/khugepaged.c:1067:\t\t__collapse_huge_page_copy_failed(pte, pmd, orig_pmd, vma,\nmm/khugepaged.c-1068-\t\t\t\t\t\t order, compound_pagelist);\n--\nmm/khugepaged.c=1224=static enum scan_result check_pmd_still_valid(struct mm_struct *mm,\n--\nmm/khugepaged.c-1247- */\nmm/khugepaged.c:1248:static enum scan_result __collapse_huge_page_swapin(struct mm_struct *mm,\nmm/khugepaged.c-1249-\t\tstruct vm_area_struct *vma, unsigned long start_addr,\n--\nmm/khugepaged.c-1333-out:\nmm/khugepaged.c:1334:\ttrace_mm_collapse_huge_page_swapin(mm, swapped_in, referenced, result,\nmm/khugepaged.c-1335-\t\t\t\t\t   order);\n--\nmm/khugepaged.c=1339=static enum scan_result alloc_charge_folio(struct folio **foliop, struct mm_struct *mm,\n--\nmm/khugepaged.c-1370-/*\nmm/khugepaged.c:1371: * collapse_huge_page expects the mmap_lock to be unlocked before entering and\nmm/khugepaged.c-1372- * will always return with the lock unlocked, to avoid holding the mmap_lock\n--\nmm/khugepaged.c-1375- */\nmm/khugepaged.c:1376:static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long start_addr,\nmm/khugepaged.c-1377-\t\tint referenced, int unmapped, struct collapse_control *cc,\n--\nmm/khugepaged.c-1417-\t\t/*\nmm/khugepaged.c:1418:\t\t * __collapse_huge_page_swapin will return with mmap_lock\nmm/khugepaged.c-1419-\t\t * released when it fails. So we jump out_nolock directly in\n--\nmm/khugepaged.c-1421-\t\t */\nmm/khugepaged.c:1422:\t\tresult = __collapse_huge_page_swapin(mm, vma, start_addr, pmd,\nmm/khugepaged.c-1423-\t\t\t\t\t\t     referenced, order);\n--\nmm/khugepaged.c-1477-\tif (pte) {\nmm/khugepaged.c:1478:\t\tresult = __collapse_huge_page_isolate(vma, start_addr, pte, cc,\nmm/khugepaged.c-1479-\t\t\t\t\t\t      order, \u0026compound_pagelist);\n--\nmm/khugepaged.c-1509-\nmm/khugepaged.c:1510:\tresult = __collapse_huge_page_copy(pte, folio, pmd, _pmd,\nmm/khugepaged.c-1511-\t\t\t\t\t   vma, start_addr, pte_ptl,\n--\nmm/khugepaged.c-1556-\t\tfolio_put(folio);\nmm/khugepaged.c:1557:\ttrace_mm_collapse_huge_page(mm, result == SCAN_SUCCEED, result, order);\nmm/khugepaged.c-1558-\treturn result;\n--\nmm/khugepaged.c=1587=static enum scan_result mthp_collapse(struct mm_struct *mm,\n--\nmm/khugepaged.c-1620-\t\t\tcollapse_address = address + offset * PAGE_SIZE;\nmm/khugepaged.c:1621:\t\t\tret = collapse_huge_page(mm, collapse_address, referenced,\nmm/khugepaged.c-1622-\t\t\t\t\t\t unmapped, cc, order);\n--\nmm/khugepaged.c=1689=static enum scan_result collapse_scan_pmd(struct mm_struct *mm,\n--\nmm/khugepaged.c-1805-\tif (result == SCAN_SUCCEED) {\nmm/khugepaged.c:1806:\t\t/* collapse_huge_page expects the lock to be dropped before calling */\nmm/khugepaged.c-1807-\t\tmmap_read_unlock(mm);\n"
}

11/2 2026/07/15 08:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15273
							output: 27
							thoughts: 52
						

Thoughts:
Okay, here's my interpretation of that thought process, from an expert's perspective:

**Investigating the `collapse_huge_page` Call Chain**

Right, so I'm digging into this code and trying to trace the execution path. I see that `collapse_huge_page` is being invoked by `mthp_collapse`, and that in turn is called by `collapse_scan_pmd`. That's a pretty clear chain, easy enough to follow. Now, the key question is whether or not `madvise_collapse` is also calling `collapse_huge_page`. I need to confirm if it has its own path to it, or if it is reliant on this existing path. I want to double-check that; there could be some assumptions being made that aren't quite correct, or maybe some unexpected behavior emerging from this particular call. I better go and check the cross-references for `collapse_huge_page` to see if `madvise_collapse` is indeed calling it directly or indirectly. It's a fundamental part of the overall flow for managing huge pages.



12/2 2026/07/15 08:56 tool grepper
0m Args:
{
  "Expression": "madvise_collapse",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/khugepaged.c=3174=bool current_is_khugepaged(void)\n--\nmm/khugepaged.c-3178-\nmm/khugepaged.c:3179:static int madvise_collapse_errno(enum scan_result r)\nmm/khugepaged.c-3180-{\n--\nmm/khugepaged.c-3210-\nmm/khugepaged.c:3211:int madvise_collapse(struct vm_area_struct *vma, unsigned long start,\nmm/khugepaged.c-3212-\t\t     unsigned long end, bool *lock_dropped)\n--\nmm/khugepaged.c-3298-\treturn thps == ((hend - hstart) \u003e\u003e HPAGE_PMD_SHIFT) ? 0\nmm/khugepaged.c:3299:\t\t\t: madvise_collapse_errno(last_fail);\nmm/khugepaged.c-3300-}\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"
}

13/2 2026/07/15 08:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15969
							output: 33
							thoughts: 59
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own:

**Initial Assessment: MADV_COLLAPSE is a Target**

Alright, so `madvise_collapse` is the entry point for `madvise` with the `MADV_COLLAPSE` flag. That makes it immediately interesting from a fuzzing perspective. It's core kernel code, directly addressable, and ripe for potential vulnerability discovery. A straightforward route to reach it. Good.

Now, my next step is clear: I need to delve into the `madvise_collapse` implementation. I want to trace the code path, identify the functions it calls, and understand its interactions with the rest of the memory management subsystem. Knowing what it touches is key to constructing effective fuzzing strategies. Gotta understand the ecosystem this function works within. From there, I can figure out what parts of the system are most likely to fail under stressful and unexpected input.



14/2 2026/07/15 08:56 tool grepper
0m Args:
{
  "Expression": "madvise_collapse",
  "PathPrefix": "mm/khugepaged.c"
}

Results:
{
  "Output": "mm/khugepaged.c=3174=bool current_is_khugepaged(void)\n--\nmm/khugepaged.c-3178-\nmm/khugepaged.c:3179:static int madvise_collapse_errno(enum scan_result r)\nmm/khugepaged.c-3180-{\n--\nmm/khugepaged.c-3210-\nmm/khugepaged.c:3211:int madvise_collapse(struct vm_area_struct *vma, unsigned long start,\nmm/khugepaged.c-3212-\t\t     unsigned long end, bool *lock_dropped)\n--\nmm/khugepaged.c-3298-\treturn thps == ((hend - hstart) \u003e\u003e HPAGE_PMD_SHIFT) ? 0\nmm/khugepaged.c:3299:\t\t\t: madvise_collapse_errno(last_fail);\nmm/khugepaged.c-3300-}\n"
}

15/2 2026/07/15 08:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16523
							output: 38
							thoughts: 32
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience:

**Analyzing `madvise_collapse`'s Dependency on Huge Page Collapse Mechanisms**

Alright, I'm diving into the `madvise_collapse` function. My primary focus right now is pinpointing how it handles huge page collapses. The question is, does `madvise_collapse` directly leverage either `collapse_huge_page` or `collapse_scan_pmd`? I need to understand the control flow here, because that will directly affect our understanding of the underlying memory management strategy. Specifically, are we looking at direct, targeted collapses or a more sweeping scan-based approach? My goal is to quickly ascertain whether `madvise_collapse` triggers either function, and if so, how. This is critical for understanding its performance characteristics and potential interaction with existing huge page configurations. Let's see what the code reveals.



16/2 2026/07/15 08:56 tool read-file
0m Args:
{
  "File": "mm/khugepaged.c",
  "FirstLine": 3211,
  "LineCount": 100
}

Results:
{
  "Contents": "3211:\tint madvise_collapse(struct vm_area_struct *vma, unsigned long start,\n3212:\t\t\t     unsigned long end, bool *lock_dropped)\n3213:\t{\n3214:\t\tstruct collapse_control *cc;\n3215:\t\tstruct mm_struct *mm = vma-\u003evm_mm;\n3216:\t\tunsigned long hstart, hend, addr;\n3217:\t\tenum scan_result last_fail = SCAN_FAIL;\n3218:\t\tint thps = 0;\n3219:\t\tbool mmap_unlocked = false;\n3220:\t\n3221:\t\tBUG_ON(vma-\u003evm_start \u003e start);\n3222:\t\tBUG_ON(vma-\u003evm_end \u003c end);\n3223:\t\n3224:\t\tif (!collapse_possible(vma, vma-\u003evm_flags, TVA_FORCED_COLLAPSE))\n3225:\t\t\treturn -EINVAL;\n3226:\t\n3227:\t\thstart = ALIGN(start, HPAGE_PMD_SIZE);\n3228:\t\thend = ALIGN_DOWN(end, HPAGE_PMD_SIZE);\n3229:\t\n3230:\t\tif (hstart \u003e= hend)\n3231:\t\t\treturn 0;\n3232:\t\n3233:\t\tcc = kmalloc_obj(*cc);\n3234:\t\tif (!cc)\n3235:\t\t\treturn -ENOMEM;\n3236:\t\tcc-\u003eis_khugepaged = false;\n3237:\t\tcc-\u003eprogress = 0;\n3238:\t\n3239:\t\tmmgrab(mm);\n3240:\t\tlru_add_drain_all();\n3241:\t\n3242:\t\tfor (addr = hstart; addr \u003c hend; addr += HPAGE_PMD_SIZE) {\n3243:\t\t\tenum scan_result result = SCAN_FAIL;\n3244:\t\n3245:\t\t\tif (mmap_unlocked) {\n3246:\t\t\t\tcond_resched();\n3247:\t\t\t\tmmap_read_lock(mm);\n3248:\t\t\t\tmmap_unlocked = false;\n3249:\t\t\t\t*lock_dropped = true;\n3250:\t\t\t\tresult = hugepage_vma_revalidate(mm, addr, false, \u0026vma,\n3251:\t\t\t\t\t\t\t\t cc, HPAGE_PMD_ORDER);\n3252:\t\t\t\tif (result != SCAN_SUCCEED) {\n3253:\t\t\t\t\tlast_fail = result;\n3254:\t\t\t\t\tgoto out_nolock;\n3255:\t\t\t\t}\n3256:\t\n3257:\t\t\t\thend = min(hend, vma-\u003evm_end \u0026 HPAGE_PMD_MASK);\n3258:\t\t\t}\n3259:\t\n3260:\t\t\tresult = collapse_single_pmd(addr, vma, \u0026mmap_unlocked, cc);\n3261:\t\n3262:\t\t\tswitch (result) {\n3263:\t\t\tcase SCAN_SUCCEED:\n3264:\t\t\tcase SCAN_PMD_MAPPED:\n3265:\t\t\t\t++thps;\n3266:\t\t\t\tbreak;\n3267:\t\t\t/* Whitelisted set of results where continuing OK */\n3268:\t\t\tcase SCAN_NO_PTE_TABLE:\n3269:\t\t\tcase SCAN_PTE_NON_PRESENT:\n3270:\t\t\tcase SCAN_PTE_UFFD:\n3271:\t\t\tcase SCAN_LACK_REFERENCED_PAGE:\n3272:\t\t\tcase SCAN_PAGE_NULL:\n3273:\t\t\tcase SCAN_PAGE_COUNT:\n3274:\t\t\tcase SCAN_PAGE_LOCK:\n3275:\t\t\tcase SCAN_PAGE_COMPOUND:\n3276:\t\t\tcase SCAN_PAGE_LRU:\n3277:\t\t\tcase SCAN_DEL_PAGE_LRU:\n3278:\t\t\t\tlast_fail = result;\n3279:\t\t\t\tbreak;\n3280:\t\t\tdefault:\n3281:\t\t\t\tlast_fail = result;\n3282:\t\t\t\t/* Other error, exit */\n3283:\t\t\t\tgoto out_maybelock;\n3284:\t\t\t}\n3285:\t\t}\n3286:\t\n3287:\tout_maybelock:\n3288:\t\t/* Caller expects us to hold mmap_lock on return */\n3289:\t\tif (mmap_unlocked) {\n3290:\t\t\t*lock_dropped = true;\n3291:\t\t\tmmap_read_lock(mm);\n3292:\t\t}\n3293:\tout_nolock:\n3294:\t\tmmap_assert_locked(mm);\n3295:\t\tmmdrop(mm);\n3296:\t\tkfree(cc);\n3297:\t\n3298:\t\treturn thps == ((hend - hstart) \u003e\u003e HPAGE_PMD_SHIFT) ? 0\n3299:\t\t\t\t: madvise_collapse_errno(last_fail);\n3300:\t}\n"
}

17/2 2026/07/15 08:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18101
							output: 34
							thoughts: 28
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into `madvise_collapse` and `collapse_single_pmd`**

Okay, so I see `madvise_collapse` is calling `collapse_single_pmd`. Naturally, my first instinct is to dive into `collapse_single_pmd`. I need to understand exactly what's going on within that function.  `madvise_collapse` is the entry point, but `collapse_single_pmd` is clearly where the core operation is happening. I need to trace the flow, see what data structures it's operating on, and understand the logic behind the collapse mechanism. What are the preconditions? What are the edge cases? I'm anticipating this is where the real interesting stuff happens, so let's get into it.



18/2 2026/07/15 08:56 tool grepper
0m Args:
{
  "Expression": "collapse_single_pmd",
  "PathPrefix": "mm/khugepaged.c"
}

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=3211=int madvise_collapse(struct vm_area_struct *vma, unsigned long start,\n--\nmm/khugepaged.c-3259-\nmm/khugepaged.c:3260:\t\tresult = collapse_single_pmd(addr, vma, \u0026mmap_unlocked, cc);\nmm/khugepaged.c-3261-\n"
}

19/2 2026/07/15 08:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18691
							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 'collapse_single_pmd'**

Alright, let's dive into this function, `collapse_single_pmd`. My initial thought is to understand its purpose within the broader context. What's its role? Is it a core component or a utility function? Based on the name, my guess is it's designed to take a single PMD (presumably a "Partial Mode Decomposition" object or similar, depending on the field) and reduce it, or "collapse" it, in some way. The "single" part is key; it hints at either a batch-processing scenario (where this would be one iteration) or a function specifically designed for individual operations. I'll need to look at its implementation or at least the surrounding code to confirm this. What are its inputs? What's the anticipated output format? Are there any preconditions it expects, such as the PMD being already preprocessed? Are there potential edge cases or error conditions it's designed to handle, or does it assume certain data structures? Getting a clear picture of the input and output is the first step towards understanding its functionality and how it might fit into a larger data analysis pipeline. I'm keen to see if this function is optimized for speed or for clarity, or if it makes any assumptions I need to be aware of.



20/2 2026/07/15 08:56 tool read-file
0m Args:
{
  "File": "mm/khugepaged.c",
  "FirstLine": 2811,
  "LineCount": 50
}

Results:
{
  "Contents": "2811:\tstatic enum scan_result collapse_single_pmd(unsigned long addr,\n2812:\t\t\tstruct vm_area_struct *vma, bool *lock_dropped,\n2813:\t\t\tstruct collapse_control *cc)\n2814:\t{\n2815:\t\tstruct mm_struct *mm = vma-\u003evm_mm;\n2816:\t\tbool triggered_wb = false;\n2817:\t\tenum scan_result result;\n2818:\t\tstruct file *file;\n2819:\t\tpgoff_t pgoff;\n2820:\t\n2821:\t\tmmap_assert_locked(mm);\n2822:\t\n2823:\t\tif (vma_is_anonymous(vma)) {\n2824:\t\t\tresult = collapse_scan_pmd(mm, vma, addr, lock_dropped, cc);\n2825:\t\t\tgoto end;\n2826:\t\t}\n2827:\t\n2828:\t\tfile = get_file(vma-\u003evm_file);\n2829:\t\tpgoff = linear_page_index(vma, addr);\n2830:\t\n2831:\t\tmmap_read_unlock(mm);\n2832:\t\t*lock_dropped = true;\n2833:\tretry:\n2834:\t\tresult = collapse_scan_file(mm, addr, file, pgoff, cc);\n2835:\t\n2836:\t\t/*\n2837:\t\t * For MADV_COLLAPSE, when encountering dirty pages, try to writeback,\n2838:\t\t * then retry the collapse one time.\n2839:\t\t */\n2840:\t\tif (!cc-\u003eis_khugepaged \u0026\u0026 result == SCAN_PAGE_DIRTY_OR_WRITEBACK \u0026\u0026\n2841:\t\t    !triggered_wb \u0026\u0026 mapping_can_writeback(file-\u003ef_mapping)) {\n2842:\t\t\tconst loff_t lstart = (loff_t)pgoff \u003c\u003c PAGE_SHIFT;\n2843:\t\t\tconst loff_t lend = lstart + HPAGE_PMD_SIZE - 1;\n2844:\t\n2845:\t\t\tfilemap_write_and_wait_range(file-\u003ef_mapping, lstart, lend);\n2846:\t\t\ttriggered_wb = true;\n2847:\t\t\tgoto retry;\n2848:\t\t}\n2849:\t\tfput(file);\n2850:\t\n2851:\t\tif (result == SCAN_PTE_MAPPED_HUGEPAGE) {\n2852:\t\t\tmmap_read_lock(mm);\n2853:\t\t\tif (collapse_test_exit_or_disable(mm))\n2854:\t\t\t\tresult = SCAN_ANY_PROCESS;\n2855:\t\t\telse\n2856:\t\t\t\tresult = try_collapse_pte_mapped_thp(mm, addr,\n2857:\t\t\t\t\t\t\t\t     !cc-\u003eis_khugepaged);\n2858:\t\t\tif (result == SCAN_PMD_MAPPED)\n2859:\t\t\t\tresult = SCAN_SUCCEED;\n2860:\t\t\tmmap_read_unlock(mm);\n"
}

21/2 2026/07/15 08:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19859
							output: 32
							thoughts: 316
						

Thoughts:
**Fuzzing Target Identification: THP Collapse**

Okay, so `collapse_single_pmd` is the key, and it calls `collapse_scan_pmd` and `collapse_scan_file`. Good. We know those functions have been modified. That means this is all reachable through `madvise(..., MADV_COLLAPSE)` *and* the background `khugepaged` daemon. Perfect. That's a solid attack surface for fuzzing. This is definitely worth pursuing.

Let's quickly recap what's changed:

*   We're looking at modifications to `mm/khugepaged.c`.
*   And the documentation update in `Documentation/admin-guide/mm/transhuge.rst` provides context.

Essentially, the changes restructure the PTE-checking logic within `khugepaged`. They've created `collapse_check_pte()` and the `pte_check_context` struct, refactoring how both anonymous and file-backed THP collapse work.

So the main symbols to focus on are:

*   `collapse_check_pte` (that new helper)
*   `__collapse_huge_page_isolate`
*   `collapse_scan_pmd`
*   `collapse_scan_file`
*   `collapse_huge_page`
*   `madvise_collapse`

Now, let's nail down the necessary configurations to test this effectively. We need:

*   `TRANSPARENT_HUGEPAGE`
*   Either `TRANSPARENT_HUGEPAGE_ALWAYS` or `TRANSPARENT_HUGEPAGE_MADVISE`.

Just to be thorough, are there any *other* related configs we should be aware of? Let's check `CONFIG_READ_ONLY_THP_FOR_FS`... Might be worth a quick look in `mm/Kconfig` to see if there's anything else that's relevant.



22/2 2026/07/15 08:56 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "READ_ONLY_THP_FOR_FS",
  "PathPrefix": "mm/"
}

Results:
{
  "error": "no matches"
}

23/2 2026/07/15 08:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20647
							output: 26
							thoughts: 56
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Kernel Configuration Hunting**

Right, so I'm trying to track down this THP (Transparent Huge Pages) configuration option. It's coming to me that I might be dealing with an older kernel version where it was specifically labelled as `CONFIG_READ_ONLY_THP_FOR_FS`, or something similar. More likely, and more contemporary, I'm probably looking for simply `TRANSPARENT_HUGEPAGE`. I know these things tend to evolve.

Best bet is to dive into the Kconfig files, specifically `mm/Kconfig`. That's where all the memory management related configurations are defined, and the THP-related stuff *has* to be in there. Time to confirm my assumptions and nail down the precise name, potential dependencies, and default settings. It's frustrating when these options get renamed. I just need to get the right nomenclature straight so I can proceed with the task at hand.



24/2 2026/07/15 08:56 tool grepper
0m Args:
{
  "Expression": "config.*THP",
  "PathPrefix": "mm/"
}

Results:
{
  "Output": "mm/Kconfig=813=config ARCH_WANT_GENERAL_HUGETLB\n--\nmm/Kconfig-815-\nmm/Kconfig:816:config ARCH_WANTS_THP_SWAP\nmm/Kconfig-817-\tdef_bool n\n--\nmm/Kconfig=972=endchoice\nmm/Kconfig-973-\nmm/Kconfig:974:config THP_SWAP\nmm/Kconfig-975-\tdef_bool y\n"
}

25/2 2026/07/15 08:56 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21084
							output: 211
							thoughts: 744
						

Thoughts:
**My Analysis of the THP Collapse Refactoring**

Alright, let's break this down. My primary target is the `TRANSPARENT_HUGEPAGE` functionality – that's the core area of interest. I'm focusing on the changes related to huge page collapse, and specifically, how this patch refactors the PTE (Page Table Entry) checking logic.

First, I identified `madvise_collapse` as a key entry point, since it's the userspace trigger for this behavior. While `khugepaged` handles this in the background, `madvise` gives a more direct way to hit this code during fuzzing.

My analysis led me to a few key focus symbols that encapsulate the core modifications. `collapse_check_pte` is the new helper function introduced, and it appears to be central to the changes, so I'll be keeping a very close eye on that. Then there's `__collapse_huge_page_isolate` which uses it, along with `collapse_scan_pmd`, `collapse_scan_file` and `collapse_huge_page`.  I'm also paying attention to `alloc_charge_folio` due to its modification using `count_collapse_event`.

I confirmed that `collapse_check_pte` is static, meaning it should be in the symbol table unless inlined. This is useful for debugging and tracing. `collapse_is_referenced` is static inline, so it might be inlined. `count_collapse_event` is also static, so it should be in the symbol table.

Therefore, the key symbols I'm focusing on are:
*   `collapse_check_pte`
*   `__collapse_huge_page_isolate`
*   `collapse_scan_pmd`
*   `collapse_scan_file`
*   `collapse_huge_page`

My reasoning is that the patch refactors the PTE checking logic during Transparent Huge Page (THP) collapse, involving new structures and helper functions which are used in `khugepaged` and are accessible via the `madvise(MADV_COLLAPSE)` syscall. It's a fundamental change to how pages are managed and collapsed into THPs, and that's precisely where my attention lies.

In terms of configuration, `TRANSPARENT_HUGEPAGE` (and specifically, `TRANSPARENT_HUGEPAGE_MADVISE` which controls the `madvise` behavior) will be critical to reproduce this behavior. It seems `TRANSPARENT_HUGEPAGE_ALWAYS` is another option that works.

Okay, let's call `set-results`.



26/2 2026/07/15 08:57 tool set-results
0m Args:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "TRANSPARENT_HUGEPAGE_ALWAYS"
  ],
  "FocusSymbols": [
    "collapse_check_pte",
    "__collapse_huge_page_isolate",
    "collapse_scan_pmd",
    "collapse_scan_file",
    "collapse_huge_page"
  ],
  "Reasoning": "The patch refactors the PTE checking logic during Transparent Huge Page (THP) collapse in `khugepaged`. It introduces a new `pte_check_context` struct and `collapse_check_pte()` helper function, which are used in `__collapse_huge_page_isolate()` and `collapse_scan_pmd()`. This modifies the core memory management logic for collapsing pages into THPs, which is reachable via the background `khugepaged` daemon and the `madvise(MADV_COLLAPSE)` syscall.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "TRANSPARENT_HUGEPAGE_ALWAYS"
  ],
  "FocusSymbols": [
    "collapse_check_pte",
    "__collapse_huge_page_isolate",
    "collapse_scan_pmd",
    "collapse_scan_file",
    "collapse_huge_page"
  ],
  "Reasoning": "The patch refactors the PTE checking logic during Transparent Huge Page (THP) collapse in `khugepaged`. It introduces a new `pte_check_context` struct and `collapse_check_pte()` helper function, which are used in `__collapse_huge_page_isolate()` and `collapse_scan_pmd()`. This modifies the core memory management logic for collapsing pages into THPs, which is reachable via the background `khugepaged` daemon and the `madvise(MADV_COLLAPSE)` syscall.",
  "WorthFuzzing": true
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)