AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/20 19:02 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "SWAP"
  ],
  "FocusSymbols": [
    "__split_huge_page_to_list_to_order",
    "folio_split_unmapped",
    "folio_check_splittable",
    "__swap_cache_replace_folio"
  ],
  "KMSANReasoning": "The patch refactors the folio splitting logic in the memory management subsystem (specifically for anonymous, swapcache, and file-backed folios). It modifies how folios are unmapped, frozen, and split, updating page cache and swap cache references. These changes involve page reference counting, locking, and xarray manipulations. They do not introduce new data structures sent to user space, nor do they alter the initialization of memory buffers. Any bugs introduced by these changes would manifest as use-after-free, memory corruption, or locking issues, which are effectively detected by KASAN and LOCKDEP. There is no risk of uninitialized memory reads or info-leaks that would require KMSAN.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch refactors and fixes the logic for splitting folios, specifically handling anonymous, swapcache, and file-backed folios. It modifies core memory management code in mm/huge_memory.c and mm/swap_state.c, which is reachable and has functional impact.",
  "WorthFuzzing": true
}

1/1 2026/08/20 19:02 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 6dae3e78392768052a1426adbd7b39c7a2a217d2\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Aug 20 19:02:36 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/mm/huge_memory.c b/mm/huge_memory.c\nindex ced400f72d43a..cc9f7e0d4194e 100644\n--- a/mm/huge_memory.c\n+++ b/mm/huge_memory.c\n@@ -3589,9 +3589,6 @@ static void remap_page(struct folio *folio, unsigned long nr, int flags)\n {\n \tint i = 0;\n \n-\t/* If unmap_folio() uses try_to_migrate() on file, remove this check */\n-\tif (!folio_test_anon(folio))\n-\t\treturn;\n \tfor (;;) {\n \t\tremove_migration_ptes(folio, folio, TTU_RMAP_LOCKED | flags);\n \t\ti += folio_nr_pages(folio);\n@@ -3755,8 +3752,8 @@ static void __split_folio_to_order(struct folio *folio, int old_order,\n }\n \n /**\n- * __split_unmapped_folio() - splits an unmapped @folio to lower order folios in\n- * two ways: uniform split or non-uniform split.\n+ * __split_frozen_folio() - splits a frozen @folio to lower order folios\n+ * in two ways: uniform split or non-uniform split.\n  * @folio: the to-be-split folio\n  * @new_order: the smallest order of the after split folios (since buddy\n  *             allocator like split generates folios with orders from @folio's\n@@ -3764,7 +3761,6 @@ static void __split_folio_to_order(struct folio *folio, int old_order,\n  * @split_at: in buddy allocator like split, the folio containing @split_at\n  *            will be split until its order becomes @new_order.\n  * @xas: xa_state pointing to folio-\u003emapping-\u003ei_pages and locked by caller\n- * @mapping: @folio-\u003emapping\n  * @split_type: if the split is uniform or not (buddy allocator like split)\n  *\n  *\n@@ -3795,11 +3791,12 @@ static void __split_folio_to_order(struct folio *folio, int old_order,\n  * Return: 0 - successful, \u003c0 - failed (if -ENOMEM is returned, @folio might be\n  * split but not to @new_order, the caller needs to check)\n  */\n-static int __split_unmapped_folio(struct folio *folio, int new_order,\n+static int __split_frozen_folio(struct folio *folio, int new_order,\n \t\tstruct page *split_at, struct xa_state *xas,\n-\t\tstruct address_space *mapping, enum split_type split_type)\n+\t\tenum split_type split_type)\n {\n \tconst bool is_anon = folio_test_anon(folio);\n+\tconst bool is_swapcache = folio_test_swapcache(folio);\n \tint old_order = folio_order(folio);\n \tint start_order = split_type == SPLIT_TYPE_UNIFORM ? new_order : old_order - 1;\n \tstruct folio *old_folio = folio;\n@@ -3814,11 +3811,11 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,\n \t     split_order--) {\n \t\tint nr_new_folios = 1UL \u003c\u003c (old_order - split_order);\n \n-\t\t/* order-1 anonymous folio is not supported */\n-\t\tif (is_anon \u0026\u0026 split_order == 1)\n+\t\t/* order-1 anonymous or swapcache folio is not supported */\n+\t\tif ((is_anon || is_swapcache) \u0026\u0026 split_order == 1)\n \t\t\tcontinue;\n \n-\t\tif (mapping) {\n+\t\tif (xas) {\n \t\t\t/*\n \t\t\t * uniform split has xas_split_alloc() called before\n \t\t\t * irq is disabled to allocate enough memory, whereas\n@@ -3878,32 +3875,26 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,\n int folio_check_splittable(struct folio *folio, unsigned int new_order,\n \t\t\t   enum split_type split_type)\n {\n+\tbool is_anon = folio_test_anon(folio);\n+\tbool is_swapcache = folio_test_swapcache(folio);\n+\n \tVM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);\n \t/*\n \t * Folios that just got truncated cannot get split. Signal to the\n-\t * caller that there was a race.\n-\t *\n-\t * TODO: this will also currently refuse folios without a mapping in the\n-\t * swapcache (shmem or to-be-anon folios).\n+\t * caller that there was a race. A mappingless swap cache folio\n+\t * has no page cache entries to update, so it is fine to split.\n \t */\n-\tif (!folio-\u003emapping \u0026\u0026 !folio_test_anon(folio))\n+\tif (!folio-\u003emapping \u0026\u0026 !is_swapcache)\n \t\treturn -EBUSY;\n \n-\t/* order-1 is not supported for anonymous THP. */\n-\tif (folio_test_anon(folio) \u0026\u0026 new_order == 1)\n-\t\treturn -EINVAL;\n-\n \t/*\n-\t * swapcache folio could only be split to order 0\n-\t *\n-\t * non-uniform split creates after-split folios with orders from\n-\t * folio_order(folio) - 1 to new_order, making it not suitable for any\n-\t * swapcache folio split. Only uniform split to order-0 can be used\n-\t * here.\n+\t * Order-1 is unsupported: anon folios need subpage 2 for the\n+\t * deferred split list, hybrid shmem \u0026 swap cache folios are not\n+\t * splittable, and a splittable mappingless swap cache folio could\n+\t * be either anon or shmem, which we cannot tell apart.\n \t */\n-\tif ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) \u0026\u0026 folio_test_swapcache(folio)) {\n+\tif ((is_anon || is_swapcache) \u0026\u0026 new_order == 1)\n \t\treturn -EINVAL;\n-\t}\n \n \tif (is_huge_zero_folio(folio))\n \t\treturn -EINVAL;\n@@ -3911,39 +3902,97 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,\n \tif (folio_test_writeback(folio))\n \t\treturn -EBUSY;\n \n+\t/*\n+\t * A non-anon swapcache folio that still has a mapping can only be a\n+\t * shmem folio under SWAP IO, it's removed from either swap cache or\n+\t * shmem mapping afterward. There is little benefit in splitting them\n+\t * hence reject it here up front before touching anything.\n+\t */\n+\tif (!is_anon \u0026\u0026 is_swapcache \u0026\u0026 folio-\u003emapping)\n+\t\treturn -EBUSY;\n+\n \treturn 0;\n }\n \n-/* Number of folio references from the pagecache or the swapcache. */\n-static unsigned int folio_cache_ref_count(const struct folio *folio)\n+/* Number of folio references from the swapcache. */\n+static unsigned int folio_swapcache_ref_count(const struct folio *folio)\n {\n-\tif (folio_test_anon(folio) \u0026\u0026 !folio_test_swapcache(folio))\n+\tif (!folio_test_swapcache(folio))\n \t\treturn 0;\n \treturn folio_nr_pages(folio);\n }\n \n-static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int new_order,\n-\t\t\t\t\t     struct page *split_at, struct xa_state *xas,\n-\t\t\t\t\t     struct address_space *mapping, bool do_lru,\n-\t\t\t\t\t     struct list_head *list, enum split_type split_type,\n-\t\t\t\t\t     pgoff_t end, int *nr_shmem_dropped)\n+/**\n+ * __folio_split_unmap_and_freeze() - split an anon or swap cache folio\n+ * @folio: folio to split, must be locked\n+ * @new_order: the order of the after-split folios (uniform split), or the\n+ *             smallest order of the after-split folios (non-uniform split)\n+ * @split_at: in non-uniform split, the folio containing @split_at is split\n+ *            until its order becomes @new_order\n+ * @do_lru: if true, add after-split folios to @list if non NULL, otherwise to\n+ *          the LRU list\n+ * @anon_unmap: if true, unmap @folio before the split and remap it after\n+ * @list: after-split folios will be put on it if non NULL\n+ * @split_type: perform uniform split or not (non-uniform split)\n+ *\n+ * Helper for splitting an anon or swap cache folio. It unmaps @folio (unless\n+ * @anon_unmap is false), freezes its refcount, and performs the split, updates\n+ * the swap cache entries. Split folios are unfrozen and remapped.\n+ *\n+ * Return: 0 on success, otherwise an error number is returned.\n+ */\n+static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_order,\n+\t\t\t\t\t  struct page *split_at, bool do_lru, bool anon_unmap,\n+\t\t\t\t\t  struct list_head *list, enum split_type split_type)\n {\n \tstruct folio *end_folio = folio_next(folio);\n+\tbool is_anon = folio_test_anon(folio);\n+\tstruct swap_cluster_info *ci = NULL;\n \tstruct folio *new_folio, *next;\n \tint old_order = folio_order(folio);\n+\tstruct anon_vma *anon_vma = NULL;\n+\tenum ttu_flags ttu_flags = 0;\n \tstruct list_lru_one *lru;\n+\tstruct lruvec *lruvec;\n \tbool dequeue_deferred;\n \tint ret = 0;\n \n-\tVM_WARN_ON_ONCE(!mapping \u0026\u0026 end);\n+\t/*\n+\t * Unmap/remap needs the anon_vma. The caller does not necessarily\n+\t * hold an mmap_lock that would prevent the anon_vma from\n+\t * disappearing, so we first take a reference and lock it. This is\n+\t * similar to folio_lock_anon_vma_read() except the write lock is\n+\t * taken to serialize against parallel split or collapse.\n+\t */\n+\tif (anon_unmap) {\n+\t\tanon_vma = folio_get_anon_vma(folio);\n+\t\tif (!anon_vma)\n+\t\t\treturn -EBUSY;\n+\t\tanon_vma_lock_write(anon_vma);\n+\t}\n+\n+\t/* Racy check if we can split the page, before the optional unmap. */\n+\tif (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {\n+\t\tret = -EAGAIN;\n+\t\tgoto out_unlock;\n+\t}\n+\n+\tif (anon_unmap)\n+\t\tunmap_folio(folio);\n+\n+\tlocal_irq_disable();\n+\n \t/*\n \t * If this folio can be on the deferred split queue, lock out\n \t * the shrinker before freezing the ref. If the shrinker sees\n \t * a 0-ref folio, it assumes it beat folio_put() to the list\n \t * lock and must clean up the LRU state - the same dequeue we\n \t * will do below as part of the split.\n+\t *\n+\t * Only anon folios are ever queued on the deferred split list,\n+\t * so non-anon folios (mappingless swapcache) never need dequeuing.\n \t */\n-\tdequeue_deferred = folio_test_anon(folio) \u0026\u0026 old_order \u003e 1;\n+\tdequeue_deferred = old_order \u003e 1 \u0026\u0026 is_anon;\n \tif (dequeue_deferred) {\n \t\tstruct mem_cgroup *memcg;\n \n@@ -3952,128 +4001,256 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n\n \t\tlru = list_lru_lock(\u0026deferred_split_lru,\n \t\t\t\t    folio_nid(folio), \u0026memcg);\n \t}\n-\tif (folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {\n-\t\tstruct swap_cluster_info *ci = NULL;\n-\t\tstruct lruvec *lruvec;\n \n+\tif (!folio_ref_freeze(folio, folio_swapcache_ref_count(folio) + 1)) {\n \t\tif (dequeue_deferred) {\n-\t\t\t__list_lru_del(\u0026deferred_split_lru, lru,\n-\t\t\t\t       \u0026folio-\u003e_deferred_list, folio_nid(folio));\n-\t\t\tif (folio_test_partially_mapped(folio)) {\n-\t\t\t\tfolio_clear_partially_mapped(folio);\n-\t\t\t\tmod_mthp_stat(old_order,\n-\t\t\t\t\tMTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);\n-\t\t\t}\n \t\t\tlist_lru_unlock(lru);\n \t\t\trcu_read_unlock();\n \t\t}\n+\t\tret = -EAGAIN;\n+\t\tgoto out_no_split;\n+\t}\n \n-\t\tif (mapping) {\n-\t\t\tint nr = folio_nr_pages(folio);\n-\n-\t\t\tif (folio_test_pmd_mappable(folio) \u0026\u0026\n-\t\t\t    new_order \u003c HPAGE_PMD_ORDER) {\n-\t\t\t\tif (folio_test_swapbacked(folio)) {\n-\t\t\t\t\tlruvec_stat_mod_folio(folio,\n-\t\t\t\t\t\t\tNR_SHMEM_THPS, -nr);\n-\t\t\t\t} else {\n-\t\t\t\t\tlruvec_stat_mod_folio(folio,\n-\t\t\t\t\t\t\tNR_FILE_THPS, -nr);\n-\t\t\t\t}\n-\t\t\t}\n+\tif (dequeue_deferred) {\n+\t\t__list_lru_del(\u0026deferred_split_lru, lru,\n+\t\t\t       \u0026folio-\u003e_deferred_list, folio_nid(folio));\n+\t\tif (folio_test_partially_mapped(folio)) {\n+\t\t\tfolio_clear_partially_mapped(folio);\n+\t\t\tmod_mthp_stat(old_order,\n+\t\t\t\t      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);\n \t\t}\n+\t\tlist_lru_unlock(lru);\n+\t\trcu_read_unlock();\n+\t}\n \n-\t\tif (folio_test_swapcache(folio)) {\n-\t\t\tif (mapping) {\n-\t\t\t\tVM_WARN_ON_ONCE_FOLIO(mapping, folio);\n-\t\t\t\treturn -EINVAL;\n-\t\t\t}\n+\tif (folio_test_swapcache(folio))\n+\t\tci = swap_cluster_get_and_lock(folio);\n \n-\t\t\tci = swap_cluster_get_and_lock(folio);\n-\t\t}\n+\tif (do_lru)\n+\t\tlruvec = folio_lruvec_lock(folio);\n \n-\t\t/* lock lru list/PageCompound, ref frozen by page_ref_freeze */\n+\tret = __split_frozen_folio(folio, new_order, split_at, NULL, split_type);\n+\n+\t/*\n+\t * Unfreeze the post-split folios and put them back to the right\n+\t * place. Keep the head @folio frozen until the end: sub entries\n+\t * in swap cache must be updated first, so a concurrent\n+\t * swap_cache_get_folio() cannot return the head folio for a sub\n+\t * entry (folio_try_get() will fail on the head @folio until unfreeze).\n+\t */\n+\tfor (new_folio = folio_next(folio); new_folio != end_folio;\n+\t     new_folio = next) {\n+\t\tnext = folio_next(new_folio);\n+\t\tzone_device_private_split_cb(folio, new_folio);\n+\t\tfolio_ref_unfreeze(new_folio,\n+\t\t\t\t   folio_swapcache_ref_count(new_folio) + 1);\n \t\tif (do_lru)\n-\t\t\tlruvec = folio_lruvec_lock(folio);\n+\t\t\tlru_add_split_folio(folio, new_folio, lruvec, list);\n+\t\tif (ci)\n+\t\t\t__swap_cache_replace_folio(ci, folio, new_folio);\n+\t}\n \n-\t\tret = __split_unmapped_folio(folio, new_order, split_at, xas,\n-\t\t\t\t\t     mapping, split_type);\n+\tzone_device_private_split_cb(folio, NULL);\n+\tfolio_ref_unfreeze(folio, folio_swapcache_ref_count(folio) + 1);\n \n-\t\t/*\n-\t\t * Unfreeze after-split folios and put them back to the right\n-\t\t * list. @folio should be kept frozon until page cache\n-\t\t * entries are updated with all the other after-split folios\n-\t\t * to prevent others seeing stale page cache entries.\n-\t\t * As a result, new_folio starts from the next folio of\n-\t\t * @folio.\n-\t\t */\n-\t\tfor (new_folio = folio_next(folio); new_folio != end_folio;\n-\t\t     new_folio = next) {\n-\t\t\tunsigned long nr_pages = folio_nr_pages(new_folio);\n+\tif (do_lru)\n+\t\tlruvec_unlock(lruvec);\n+\tif (ci)\n+\t\tswap_cluster_unlock(ci);\n+out_no_split:\n+\tlocal_irq_enable();\n+\tif (anon_vma) {\n+\t\tif (!ret \u0026\u0026 !folio_is_device_private(folio))\n+\t\t\tttu_flags = TTU_USE_SHARED_ZEROPAGE;\n+\t\tremap_page(folio, 1 \u003c\u003c old_order, ttu_flags);\n+\t}\n+out_unlock:\n+\tif (anon_vma) {\n+\t\tanon_vma_unlock_write(anon_vma);\n+\t\tput_anon_vma(anon_vma);\n+\t}\n \n-\t\t\tnext = folio_next(new_folio);\n+\treturn ret;\n+}\n \n-\t\t\tzone_device_private_split_cb(folio, new_folio);\n+/**\n+ * __folio_split_unmap_and_freeze_file() - split a file-backed folio\n+ * @folio: folio to split, must be locked and file-backed\n+ * @new_order: the order of the after-split folios (uniform split), or the\n+ *             smallest order of the after-split folios (non-uniform split)\n+ * @split_at: in non-uniform split, the folio containing @split_at is split\n+ *            until its order becomes @new_order\n+ * @list: after-split folios will be put on it if non NULL\n+ * @split_type: perform uniform split or not (non-uniform split)\n+ *\n+ * Helper for splitting a file-backed folio. It unmaps @folio, freezes its\n+ * refcount, and perform the split, updates the page cache entries. Split\n+ * folios are unfrozen but not remapped, they are faulted back in on demand.\n+ *\n+ * Return: 0 on success, otherwise an error number is returned. (if -ENOMEM\n+ * is returned, @folio might be split but not to @new_order)\n+ */\n+static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int new_order,\n+\t\t\t\t\t       struct page *split_at, struct list_head *list,\n+\t\t\t\t\t       enum split_type split_type)\n+{\n+\tstruct address_space *mapping = folio-\u003emapping;\n+\tXA_STATE(xas, \u0026mapping-\u003ei_pages, folio-\u003eindex);\n+\tstruct folio *end_folio = folio_next(folio);\n+\tlong old_nr_pages = folio_nr_pages(folio);\n+\tstruct mem_cgroup *memcg, *old_memcg;\n+\tstruct folio *new_folio, *next;\n+\tint nr_shmem_dropped = 0;\n+\tunsigned int min_order;\n+\tstruct lruvec *lruvec;\n+\tpgoff_t end = 0;\n+\tgfp_t gfp;\n+\tint ret = 0;\n \n-\t\t\tfolio_ref_unfreeze(new_folio,\n-\t\t\t\t\t   folio_cache_ref_count(new_folio) + 1);\n+\tmin_order = mapping_min_folio_order(mapping);\n+\tif (new_order \u003c min_order)\n+\t\treturn -EINVAL;\n \n-\t\t\tif (do_lru)\n-\t\t\t\tlru_add_split_folio(folio, new_folio, lruvec, list);\n+\t/*\n+\t * Switch to folio's memcg as xarray node allocation can happen and\n+\t * needs to charge to it.\n+\t */\n+\tmemcg = get_mem_cgroup_from_folio(folio);\n+\told_memcg = set_active_memcg(memcg);\n \n-\t\t\t/*\n-\t\t\t * Anonymous folio with swap cache.\n-\t\t\t * NOTE: shmem in swap cache is not supported yet.\n-\t\t\t */\n-\t\t\tif (ci) {\n-\t\t\t\t__swap_cache_replace_folio(ci, folio, new_folio);\n-\t\t\t\tcontinue;\n-\t\t\t}\n+\tgfp = current_gfp_context(mapping_gfp_mask(mapping) \u0026 GFP_RECLAIM_MASK);\n+\tif (!filemap_release_folio(folio, gfp)) {\n+\t\tret = -EBUSY;\n+\t\tgoto fail_free;\n+\t}\n \n-\t\t\t/* Anonymous folio without swap cache */\n-\t\t\tif (!mapping)\n-\t\t\t\tcontinue;\n+\tmapping_set_update(\u0026xas, mapping);\n \n-\t\t\t/* Add the new folio to the page cache. */\n-\t\t\tif (new_folio-\u003eindex \u003c end) {\n-\t\t\t\t__xa_store(\u0026mapping-\u003ei_pages, new_folio-\u003eindex,\n-\t\t\t\t\t   new_folio, 0);\n-\t\t\t\tcontinue;\n-\t\t\t}\n+\tif (split_type == SPLIT_TYPE_UNIFORM) {\n+\t\tint old_order = folio_order(folio);\n \n-\t\t\tVM_WARN_ON_ONCE(!nr_shmem_dropped);\n-\t\t\t/* Drop folio beyond EOF: -\u003eindex \u003e= end */\n-\t\t\tif (shmem_mapping(mapping) \u0026\u0026 nr_shmem_dropped)\n-\t\t\t\t*nr_shmem_dropped += nr_pages;\n-\t\t\telse if (folio_test_clear_dirty(new_folio))\n-\t\t\t\tfolio_account_cleaned(\n-\t\t\t\t\tnew_folio, inode_to_wb(mapping-\u003ehost));\n-\t\t\t__filemap_remove_folio(new_folio, NULL);\n-\t\t\tfolio_put_refs(new_folio, nr_pages);\n+\t\txas_set_order(\u0026xas, folio-\u003eindex, new_order);\n+\t\txas_split_alloc(\u0026xas, folio, old_order, gfp);\n+\t\tif (xas_error(\u0026xas)) {\n+\t\t\tret = xas_error(\u0026xas);\n+\t\t\tgoto fail_free;\n \t\t}\n+\t}\n \n-\t\tzone_device_private_split_cb(folio, NULL);\n-\t\t/*\n-\t\t * Unfreeze @folio only after all page cache entries, which\n-\t\t * used to point to it, have been updated with new folios.\n-\t\t * Otherwise, a parallel folio_try_get() can grab @folio\n-\t\t * and its caller can see stale page cache entries.\n-\t\t */\n-\t\tfolio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);\n+\ti_mmap_lock_read(mapping);\n \n-\t\tif (do_lru)\n-\t\t\tlruvec_unlock(lruvec);\n+\t/* Racy check if we can split the page, before unmap_folio() */\n+\tif (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {\n+\t\tret = -EAGAIN;\n+\t\tgoto fail_mmap_unlock;\n+\t}\n \n-\t\tif (ci)\n-\t\t\tswap_cluster_unlock(ci);\n-\t} else {\n-\t\tif (dequeue_deferred) {\n-\t\t\tlist_lru_unlock(lru);\n-\t\t\trcu_read_unlock();\n+\t/*\n+\t * __split_frozen_folio() may need to trim off pages beyond\n+\t * EOF: but on 32-bit, i_size_read() takes an irq-unsafe\n+\t * seqlock, which cannot be nested inside the page tree lock.\n+\t * So note end now: i_size itself may be changed at any moment,\n+\t * but folio lock is good enough to serialize the trimming.\n+\t */\n+\tend = DIV_ROUND_UP(i_size_read(mapping-\u003ehost), PAGE_SIZE);\n+\tif (shmem_mapping(mapping))\n+\t\tend = shmem_fallocend(mapping-\u003ehost, end);\n+\n+\tunmap_folio(folio);\n+\n+\txas_lock_irq(\u0026xas);\n+\n+\t/*\n+\t * Check if the folio is present in page cache.\n+\t * We assume all tail are present too, if folio is there.\n+\t */\n+\tif (xas_load(\u0026xas) != folio) {\n+\t\tret = -EAGAIN;\n+\t\tgoto fail;\n+\t}\n+\n+\tif (!folio_ref_freeze(folio, old_nr_pages + 1)) {\n+\t\tret = -EAGAIN;\n+\t\tgoto fail;\n+\t}\n+\n+\tif (folio_test_pmd_mappable(folio) \u0026\u0026 new_order \u003c HPAGE_PMD_ORDER) {\n+\t\tif (folio_test_swapbacked(folio))\n+\t\t\tlruvec_stat_mod_folio(folio, NR_SHMEM_THPS, -old_nr_pages);\n+\t\telse\n+\t\t\tlruvec_stat_mod_folio(folio, NR_FILE_THPS, -old_nr_pages);\n+\t}\n+\n+\t/* lock lru list/PageCompound, ref frozen by page_ref_freeze */\n+\tlruvec = folio_lruvec_lock(folio);\n+\tret = __split_frozen_folio(folio, new_order, split_at, \u0026xas, split_type);\n+\n+\t/*\n+\t * Unfreeze after-split folios and put them back to the right\n+\t * list. @folio should be kept frozen until page cache\n+\t * entries are updated with all the other after-split folios\n+\t * to prevent others seeing stale page cache entries.\n+\t * As a result, new_folio starts from the next folio of\n+\t * @folio.\n+\t */\n+\tfor (new_folio = folio_next(folio); new_folio != end_folio;\n+\t     new_folio = next) {\n+\t\tunsigned long nr_pages = folio_nr_pages(new_folio);\n+\n+\t\t/* compute next before the folio can be freed below */\n+\t\tnext = folio_next(new_folio);\n+\n+\t\tfolio_ref_unfreeze(new_folio,\n+\t\t\t\t   folio_nr_pages(new_folio) + 1);\n+\n+\t\tlru_add_split_folio(folio, new_folio, lruvec, list);\n+\n+\t\t/* Add the new folio to the page cache. */\n+\t\tif (new_folio-\u003eindex \u003c end) {\n+\t\t\t__xa_store(\u0026mapping-\u003ei_pages, new_folio-\u003eindex,\n+\t\t\t\t   new_folio, 0);\n+\t\t\tcontinue;\n \t\t}\n-\t\treturn -EAGAIN;\n+\n+\t\t/* Drop folio beyond EOF: -\u003eindex \u003e= end */\n+\t\tif (shmem_mapping(mapping))\n+\t\t\tnr_shmem_dropped += nr_pages;\n+\t\telse if (folio_test_clear_dirty(new_folio))\n+\t\t\tfolio_account_cleaned(new_folio,\n+\t\t\t\t\t      inode_to_wb(mapping-\u003ehost));\n+\t\t__filemap_remove_folio(new_folio, NULL);\n+\t\tfolio_put_refs(new_folio, nr_pages);\n \t}\n \n+\t/*\n+\t * Unfreeze @folio only after all page cache entries, which\n+\t * used to point to it, have been updated with new folios.\n+\t * Otherwise, a parallel folio_try_get() can grab @folio\n+\t * and its caller can see stale page cache entries.\n+\t */\n+\tfolio_ref_unfreeze(folio, folio_nr_pages(folio) + 1);\n+\tlruvec_unlock(lruvec);\n+fail:\n+\t/*\n+\t * If we want to use try_to_migrate() on file in unmap_folio,\n+\t * remember to add remap_page() and adapt it.\n+\t */\n+\txas_unlock_irq(\u0026xas);\n+fail_mmap_unlock:\n+\tif (nr_shmem_dropped)\n+\t\tshmem_uncharge(mapping-\u003ehost, nr_shmem_dropped);\n+\t/*\n+\t * Drop the mapping while the inode is still pinned. @folio stays\n+\t * locked and present in the page cache, so eviction cannot free\n+\t * the inode yet, nothing past this point may touch the inode or\n+\t * the mapping.\n+\t */\n+\ti_mmap_unlock_read(mapping);\n+fail_free:\n+\t/* Restore the previously active memcg */\n+\tset_active_memcg(old_memcg);\n+\tmem_cgroup_put(memcg);\n+\txas_destroy(\u0026xas);\n \treturn ret;\n }\n \n@@ -4086,9 +4263,9 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n\n  * @list: after-split folios will be put on it if non NULL\n  * @split_type: perform uniform split or not (non-uniform split)\n  *\n- * It calls __split_unmapped_folio() to perform uniform and non-uniform split.\n+ * It calls __split_frozen_folio() to perform uniform and non-uniform split.\n  * It is in charge of checking whether the split is supported or not and\n- * preparing @folio for __split_unmapped_folio().\n+ * preparing @folio for __split_frozen_folio().\n  *\n  * After splitting, the after-split folio containing @lock_at remains locked\n  * and others are unlocked:\n@@ -4102,17 +4279,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,\n \t\tstruct page *split_at, struct page *lock_at,\n \t\tstruct list_head *list, enum split_type split_type)\n {\n-\tXA_STATE(xas, \u0026folio-\u003emapping-\u003ei_pages, folio-\u003eindex);\n+\tbool is_swapcache = folio_test_swapcache(folio);\n \tstruct folio *end_folio = folio_next(folio);\n \tbool is_anon = folio_test_anon(folio);\n-\tstruct mem_cgroup *memcg, *old_memcg;\n-\tstruct address_space *mapping = NULL;\n-\tstruct anon_vma *anon_vma = NULL;\n \tint old_order = folio_order(folio);\n \tstruct folio *new_folio, *next;\n-\tint nr_shmem_dropped = 0;\n-\tenum ttu_flags ttu_flags = 0;\n-\tpgoff_t end = 0;\n \tint ret;\n \n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);\n@@ -4120,141 +4291,29 @@ static int __folio_split(struct folio *folio, unsigned int new_order,\n \n \tif (folio != page_folio(split_at) || folio != page_folio(lock_at)) {\n \t\tret = -EINVAL;\n-\t\tgoto out_no_memcg;\n+\t\tgoto out;\n \t}\n \n \tif (new_order \u003e= old_order) {\n \t\tret = -EINVAL;\n-\t\tgoto out_no_memcg;\n+\t\tgoto out;\n \t}\n \n \tret = folio_check_splittable(folio, new_order, split_type);\n \tif (ret) {\n \t\tVM_WARN_ONCE(ret == -EINVAL, \"Tried to split an unsplittable folio\");\n-\t\tgoto out_no_memcg;\n-\t}\n-\n-\t/*\n-\t * switch to folio's memcg as xarray node allocation can happen and\n-\t * needs to charge to it.\n-\t */\n-\tmemcg = get_mem_cgroup_from_folio(folio);\n-\told_memcg = set_active_memcg(memcg);\n-\n-\tif (is_anon) {\n-\t\t/*\n-\t\t * The caller does not necessarily hold an mmap_lock that would\n-\t\t * prevent the anon_vma disappearing so we first we take a\n-\t\t * reference to it and then lock the anon_vma for write. This\n-\t\t * is similar to folio_lock_anon_vma_read except the write lock\n-\t\t * is taken to serialise against parallel split or collapse\n-\t\t * operations.\n-\t\t */\n-\t\tanon_vma = folio_get_anon_vma(folio);\n-\t\tif (!anon_vma) {\n-\t\t\tret = -EBUSY;\n-\t\t\tgoto out;\n-\t\t}\n-\t\tanon_vma_lock_write(anon_vma);\n-\t\tmapping = NULL;\n-\t} else {\n-\t\tunsigned int min_order;\n-\t\tgfp_t gfp;\n-\n-\t\tmapping = folio-\u003emapping;\n-\t\tmin_order = mapping_min_folio_order(mapping);\n-\t\tif (new_order \u003c min_order) {\n-\t\t\tret = -EINVAL;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tgfp = current_gfp_context(mapping_gfp_mask(mapping) \u0026\n-\t\t\t\t\t\t\tGFP_RECLAIM_MASK);\n-\n-\t\tif (!filemap_release_folio(folio, gfp)) {\n-\t\t\tret = -EBUSY;\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tmapping_set_update(\u0026xas, mapping);\n-\n-\t\tif (split_type == SPLIT_TYPE_UNIFORM) {\n-\t\t\txas_set_order(\u0026xas, folio-\u003eindex, new_order);\n-\t\t\txas_split_alloc(\u0026xas, folio, old_order, gfp);\n-\t\t\tif (xas_error(\u0026xas)) {\n-\t\t\t\tret = xas_error(\u0026xas);\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\t\t}\n-\n-\t\tanon_vma = NULL;\n-\t\ti_mmap_lock_read(mapping);\n-\n-\t\t/*\n-\t\t *__split_unmapped_folio() may need to trim off pages beyond\n-\t\t * EOF: but on 32-bit, i_size_read() takes an irq-unsafe\n-\t\t * seqlock, which cannot be nested inside the page tree lock.\n-\t\t * So note end now: i_size itself may be changed at any moment,\n-\t\t * but folio lock is good enough to serialize the trimming.\n-\t\t */\n-\t\tend = DIV_ROUND_UP(i_size_read(mapping-\u003ehost), PAGE_SIZE);\n-\t\tif (shmem_mapping(mapping))\n-\t\t\tend = shmem_fallocend(mapping-\u003ehost, end);\n-\t}\n-\n-\t/*\n-\t * Racy check if we can split the page, before unmap_folio() will\n-\t * split PMDs\n-\t */\n-\tif (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {\n-\t\tret = -EAGAIN;\n-\t\tgoto out_unlock;\n-\t}\n-\n-\tunmap_folio(folio);\n-\n-\t/* block interrupt reentry in xa_lock and spinlock */\n-\tlocal_irq_disable();\n-\tif (mapping) {\n-\t\t/*\n-\t\t * Check if the folio is present in page cache.\n-\t\t * We assume all tail are present too, if folio is there.\n-\t\t */\n-\t\txas_lock(\u0026xas);\n-\t\txas_reset(\u0026xas);\n-\t\tif (xas_load(\u0026xas) != folio) {\n-\t\t\tret = -EAGAIN;\n-\t\t\tgoto fail;\n-\t\t}\n+\t\tgoto out;\n \t}\n \n-\tret = __folio_freeze_and_split_unmapped(folio, new_order, split_at, \u0026xas, mapping,\n-\t\t\t\t\t\ttrue, list, split_type, end, \u0026nr_shmem_dropped);\n-fail:\n-\tif (mapping)\n-\t\txas_unlock(\u0026xas);\n-\n-\tlocal_irq_enable();\n-\n-\tif (nr_shmem_dropped)\n-\t\tshmem_uncharge(mapping-\u003ehost, nr_shmem_dropped);\n-\n-\tif (!ret \u0026\u0026 is_anon \u0026\u0026 !folio_is_device_private(folio))\n-\t\tttu_flags = TTU_USE_SHARED_ZEROPAGE;\n-\n-\tremap_page(folio, 1 \u003c\u003c old_order, ttu_flags);\n-\n-\t/*\n-\t * Drop the mapping while the inode is still pinned. @folio stays\n-\t * locked and present in the page cache until the loop below, so\n-\t * eviction cannot free the inode yet; @lock_at is not enough, it may\n-\t * be a tail beyond EOF that the split already dropped from the page\n-\t * cache. Nothing past this point may touch the inode or the mapping.\n-\t */\n-\tif (mapping) {\n-\t\ti_mmap_unlock_read(mapping);\n-\t\tmapping = NULL;\n-\t}\n+\tif (is_anon)\n+\t\tret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,\n+\t\t\t\t\t\t     true, list, split_type);\n+\telse if (is_swapcache)\n+\t\tret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,\n+\t\t\t\t\t\t     false, list, split_type);\n+\telse\n+\t\tret = __folio_split_unmap_and_freeze_file(folio, new_order, split_at,\n+\t\t\t\t\t\t\t  list, split_type);\n \n \t/*\n \t * Unlock all after-split folios except the one containing\n@@ -4265,29 +4324,19 @@ static int __folio_split(struct folio *folio, unsigned int new_order,\n \t\tif (new_folio == page_folio(lock_at))\n \t\t\tcontinue;\n \n-\t\tfolio_unlock(new_folio);\n \t\t/*\n \t\t * Subpages whose mapping has been zapped may be freed\n \t\t * earlier, but freeing them requires taking the\n-\t\t * lru_lock, so we defer put_page() on tail pages until\n+\t\t * lru_lock, so we defer folio_put() on tail pages until\n \t\t * after the split completes.\n \t\t */\n-\t\tfree_folio_and_swap_cache(new_folio);\n+\t\tif (is_swapcache \u0026\u0026 !folio_mapped(new_folio))\n+\t\t\tfolio_free_swap(new_folio);\n+\t\tfolio_unlock(new_folio);\n+\t\tfolio_put(new_folio);\n \t}\n \n-out_unlock:\n-\tif (anon_vma) {\n-\t\tanon_vma_unlock_write(anon_vma);\n-\t\tput_anon_vma(anon_vma);\n-\t}\n-\tif (mapping)\n-\t\ti_mmap_unlock_read(mapping);\n out:\n-\t/* restore to caller's old_memcg */\n-\tset_active_memcg(old_memcg);\n-\tmem_cgroup_put(memcg);\n-out_no_memcg:\n-\txas_destroy(\u0026xas);\n \tif (is_pmd_order(old_order))\n \t\tcount_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);\n \tcount_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);\n@@ -4311,29 +4360,20 @@ static int __folio_split(struct folio *folio, unsigned int new_order,\n  * isolated from LRU (if applicable)\n  *\n  * Upon return, the folio is not remapped, split folios are not added to LRU,\n- * free_folio_and_swap_cache() is not called, and new folios remain locked.\n+ * folio_free_swap() is not called, and new folios remain locked.\n  *\n  * Return: 0 on success, -EAGAIN if the folio cannot be split (e.g., due to\n  *         insufficient reference count or extra pins).\n  */\n int folio_split_unmapped(struct folio *folio, unsigned int new_order)\n {\n-\tint ret = 0;\n-\n \tVM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);\n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);\n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);\n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);\n \n-\tif (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)\n-\t\treturn -EAGAIN;\n-\n-\tlocal_irq_disable();\n-\tret = __folio_freeze_and_split_unmapped(folio, new_order, \u0026folio-\u003epage, NULL,\n-\t\t\t\t\t\tNULL, false, NULL, SPLIT_TYPE_UNIFORM,\n-\t\t\t\t\t\t0, NULL);\n-\tlocal_irq_enable();\n-\treturn ret;\n+\treturn __folio_split_unmap_and_freeze(folio, new_order, \u0026folio-\u003epage, false,\n+\t\t\t\t\t      false, NULL, SPLIT_TYPE_UNIFORM);\n }\n \n /*\n@@ -4352,10 +4392,9 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)\n  *    GUP pins, will result in the folio not getting split; instead, the caller\n  *    will receive an -EAGAIN.\n  *\n- * 4) @new_order \u003e 1, usually. Splitting to order-1 anonymous folios is not\n- *    supported for non-file-backed folios, because folio-\u003e_deferred_list, which\n- *    is used by partially mapped folios, is stored in subpage 2, but an order-1\n- *    folio only has subpages 0 and 1. File-backed order-1 folios are supported,\n+ * 4) @new_order != 1 for anon or swapcache. Anon folios need subpage 2 for\n+ *    _deferred_list, which order-1 folios lack, and a swapcache folio may\n+ *    become anon once faulted in. File-backed order-1 folios are supported,\n  *    since they do not use _deferred_list.\n  *\n  * After splitting, the caller's folio reference will be transferred to @page,\n@@ -4384,7 +4423,7 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)\n  * with the folio. Splitting to order 0 is compatible with all folios.\n  */\n int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\n-\t\t\t\t     unsigned int new_order)\n+\t\t\t\t       unsigned int new_order)\n {\n \tstruct folio *folio = page_folio(page);\n \ndiff --git a/mm/swap_state.c b/mm/swap_state.c\nindex b76eb3d876fd7..59a577f685b5b 100644\n--- a/mm/swap_state.c\n+++ b/mm/swap_state.c\n@@ -389,8 +389,9 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,\n \t    folio_order(old) != folio_order(new)) {\n \t\tci_off = swp_cluster_offset(old-\u003eswap);\n \t\tci_end = ci_off + folio_nr_pages(old);\n-\t\twhile (ci_off++ \u003c ci_end)\n+\t\tdo {\n \t\t\tWARN_ON_ONCE(swp_tb_to_folio(__swap_table_get(ci, ci_off)) != old);\n+\t\t} while (++ci_off \u003c ci_end);\n \t}\n }\n \n"
}

2/1 2026/08/20 19:02 agent patch-evaluator
1m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "SWAP"
  ],
  "FocusSymbols": [
    "__split_huge_page_to_list_to_order",
    "folio_split_unmapped",
    "folio_check_splittable",
    "__swap_cache_replace_folio"
  ],
  "Reasoning": "The patch refactors and fixes the logic for splitting folios, specifically handling anonymous, swapcache, and file-backed folios. It modifies core memory management code in mm/huge_memory.c and mm/swap_state.c, which is reachable and has functional impact.",
  "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 6dae3e78392768052a1426adbd7b39c7a2a217d2
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Aug 20 19:02:36 2026 +0000

    syz-cluster: applied patch under review

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ced400f72d43a..cc9f7e0d4194e 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3589,9 +3589,6 @@ static void remap_page(struct folio *folio, unsigned long nr, int flags)
 {
 	int i = 0;
 
-	/* If unmap_folio() uses try_to_migrate() on file, remove this check */
-	if (!folio_test_anon(folio))
-		return;
 	for (;;) {
 		remove_migration_ptes(folio, folio, TTU_RMAP_LOCKED | flags);
 		i += folio_nr_pages(folio);
@@ -3755,8 +3752,8 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
 }
 
 /**
- * __split_unmapped_folio() - splits an unmapped @folio to lower order folios in
- * two ways: uniform split or non-uniform split.
+ * __split_frozen_folio() - splits a frozen @folio to lower order folios
+ * in two ways: uniform split or non-uniform split.
  * @folio: the to-be-split folio
  * @new_order: the smallest order of the after split folios (since buddy
  *             allocator like split generates folios with orders from @folio's
@@ -3764,7 +3761,6 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
  * @split_at: in buddy allocator like split, the folio containing @split_at
  *            will be split until its order becomes @new_order.
  * @xas: xa_state pointing to folio->mapping->i_pages and locked by caller
- * @mapping: @folio->mapping
  * @split_type: if the split is uniform or not (buddy allocator like split)
  *
  *
@@ -3795,11 +3791,12 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
  * Return: 0 - successful, <0 - failed (if -ENOMEM is returned, @folio might be
  * split but not to @new_order, the caller needs to check)
  */
-static int __split_unmapped_folio(struct folio *folio, int new_order,
+static int __split_frozen_folio(struct folio *folio, int new_order,
 		struct page *split_at, struct xa_state *xas,
-		struct address_space *mapping, enum split_type split_type)
+		enum split_type split_type)
 {
 	const bool is_anon = folio_test_anon(folio);
+	const bool is_swapcache = folio_test_swapcache(folio);
 	int old_order = folio_order(folio);
 	int start_order = split_type == SPLIT_TYPE_UNIFORM ? new_order : old_order - 1;
 	struct folio *old_folio = folio;
@@ -3814,11 +3811,11 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 	     split_order--) {
 		int nr_new_folios = 1UL << (old_order - split_order);
 
-		/* order-1 anonymous folio is not supported */
-		if (is_anon && split_order == 1)
+		/* order-1 anonymous or swapcache folio is not supported */
+		if ((is_anon || is_swapcache) && split_order == 1)
 			continue;
 
-		if (mapping) {
+		if (xas) {
 			/*
 			 * uniform split has xas_split_alloc() called before
 			 * irq is disabled to allocate enough memory, whereas
@@ -3878,32 +3875,26 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 int folio_check_splittable(struct folio *folio, unsigned int new_order,
 			   enum split_type split_type)
 {
+	bool is_anon = folio_test_anon(folio);
+	bool is_swapcache = folio_test_swapcache(folio);
+
 	VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
 	/*
 	 * Folios that just got truncated cannot get split. Signal to the
-	 * caller that there was a race.
-	 *
-	 * TODO: this will also currently refuse folios without a mapping in the
-	 * swapcache (shmem or to-be-anon folios).
+	 * caller that there was a race. A mappingless swap cache folio
+	 * has no page cache entries to update, so it is fine to split.
 	 */
-	if (!folio->mapping && !folio_test_anon(folio))
+	if (!folio->mapping && !is_swapcache)
 		return -EBUSY;
 
-	/* order-1 is not supported for anonymous THP. */
-	if (folio_test_anon(folio) && new_order == 1)
-		return -EINVAL;
-
 	/*
-	 * swapcache folio could only be split to order 0
-	 *
-	 * non-uniform split creates after-split folios with orders from
-	 * folio_order(folio) - 1 to new_order, making it not suitable for any
-	 * swapcache folio split. Only uniform split to order-0 can be used
-	 * here.
+	 * Order-1 is unsupported: anon folios need subpage 2 for the
+	 * deferred split list, hybrid shmem & swap cache folios are not
+	 * splittable, and a splittable mappingless swap cache folio could
+	 * be either anon or shmem, which we cannot tell apart.
 	 */
-	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && folio_test_swapcache(folio)) {
+	if ((is_anon || is_swapcache) && new_order == 1)
 		return -EINVAL;
-	}
 
 	if (is_huge_zero_folio(folio))
 		return -EINVAL;
@@ -3911,39 +3902,97 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	if (folio_test_writeback(folio))
 		return -EBUSY;
 
+	/*
+	 * A non-anon swapcache folio that still has a mapping can only be a
+	 * shmem folio under SWAP IO, it's removed from either swap cache or
+	 * shmem mapping afterward. There is little benefit in splitting them
+	 * hence reject it here up front before touching anything.
+	 */
+	if (!is_anon && is_swapcache && folio->mapping)
+		return -EBUSY;
+
 	return 0;
 }
 
-/* Number of folio references from the pagecache or the swapcache. */
-static unsigned int folio_cache_ref_count(const struct folio *folio)
+/* Number of folio references from the swapcache. */
+static unsigned int folio_swapcache_ref_count(const struct folio *folio)
 {
-	if (folio_test_anon(folio) && !folio_test_swapcache(folio))
+	if (!folio_test_swapcache(folio))
 		return 0;
 	return folio_nr_pages(folio);
 }
 
-static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int new_order,
-					     struct page *split_at, struct xa_state *xas,
-					     struct address_space *mapping, bool do_lru,
-					     struct list_head *list, enum split_type split_type,
-					     pgoff_t end, int *nr_shmem_dropped)
+/**
+ * __folio_split_unmap_and_freeze() - split an anon or swap cache folio
+ * @folio: folio to split, must be locked
+ * @new_order: the order of the after-split folios (uniform split), or the
+ *             smallest order of the after-split folios (non-uniform split)
+ * @split_at: in non-uniform split, the folio containing @split_at is split
+ *            until its order becomes @new_order
+ * @do_lru: if true, add after-split folios to @list if non NULL, otherwise to
+ *          the LRU list
+ * @anon_unmap: if true, unmap @folio before the split and remap it after
+ * @list: after-split folios will be put on it if non NULL
+ * @split_type: perform uniform split or not (non-uniform split)
+ *
+ * Helper for splitting an anon or swap cache folio. It unmaps @folio (unless
+ * @anon_unmap is false), freezes its refcount, and performs the split, updates
+ * the swap cache entries. Split folios are unfrozen and remapped.
+ *
+ * Return: 0 on success, otherwise an error number is returned.
+ */
+static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_order,
+					  struct page *split_at, bool do_lru, bool anon_unmap,
+					  struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
+	bool is_anon = folio_test_anon(folio);
+	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio, *next;
 	int old_order = folio_order(folio);
+	struct anon_vma *anon_vma = NULL;
+	enum ttu_flags ttu_flags = 0;
 	struct list_lru_one *lru;
+	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
-	VM_WARN_ON_ONCE(!mapping && end);
+	/*
+	 * Unmap/remap needs the anon_vma. The caller does not necessarily
+	 * hold an mmap_lock that would prevent the anon_vma from
+	 * disappearing, so we first take a reference and lock it. This is
+	 * similar to folio_lock_anon_vma_read() except the write lock is
+	 * taken to serialize against parallel split or collapse.
+	 */
+	if (anon_unmap) {
+		anon_vma = folio_get_anon_vma(folio);
+		if (!anon_vma)
+			return -EBUSY;
+		anon_vma_lock_write(anon_vma);
+	}
+
+	/* Racy check if we can split the page, before the optional unmap. */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	if (anon_unmap)
+		unmap_folio(folio);
+
+	local_irq_disable();
+
 	/*
 	 * If this folio can be on the deferred split queue, lock out
 	 * the shrinker before freezing the ref. If the shrinker sees
 	 * a 0-ref folio, it assumes it beat folio_put() to the list
 	 * lock and must clean up the LRU state - the same dequeue we
 	 * will do below as part of the split.
+	 *
+	 * Only anon folios are ever queued on the deferred split list,
+	 * so non-anon folios (mappingless swapcache) never need dequeuing.
 	 */
-	dequeue_deferred = folio_test_anon(folio) && old_order > 1;
+	dequeue_deferred = old_order > 1 && is_anon;
 	if (dequeue_deferred) {
 		struct mem_cgroup *memcg;
 
@@ -3952,128 +4001,256 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 		lru = list_lru_lock(&deferred_split_lru,
 				    folio_nid(folio), &memcg);
 	}
-	if (folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
-		struct swap_cluster_info *ci = NULL;
-		struct lruvec *lruvec;
 
+	if (!folio_ref_freeze(folio, folio_swapcache_ref_count(folio) + 1)) {
 		if (dequeue_deferred) {
-			__list_lru_del(&deferred_split_lru, lru,
-				       &folio->_deferred_list, folio_nid(folio));
-			if (folio_test_partially_mapped(folio)) {
-				folio_clear_partially_mapped(folio);
-				mod_mthp_stat(old_order,
-					MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
-			}
 			list_lru_unlock(lru);
 			rcu_read_unlock();
 		}
+		ret = -EAGAIN;
+		goto out_no_split;
+	}
 
-		if (mapping) {
-			int nr = folio_nr_pages(folio);
-
-			if (folio_test_pmd_mappable(folio) &&
-			    new_order < HPAGE_PMD_ORDER) {
-				if (folio_test_swapbacked(folio)) {
-					lruvec_stat_mod_folio(folio,
-							NR_SHMEM_THPS, -nr);
-				} else {
-					lruvec_stat_mod_folio(folio,
-							NR_FILE_THPS, -nr);
-				}
-			}
+	if (dequeue_deferred) {
+		__list_lru_del(&deferred_split_lru, lru,
+			       &folio->_deferred_list, folio_nid(folio));
+		if (folio_test_partially_mapped(folio)) {
+			folio_clear_partially_mapped(folio);
+			mod_mthp_stat(old_order,
+				      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
 		}
+		list_lru_unlock(lru);
+		rcu_read_unlock();
+	}
 
-		if (folio_test_swapcache(folio)) {
-			if (mapping) {
-				VM_WARN_ON_ONCE_FOLIO(mapping, folio);
-				return -EINVAL;
-			}
+	if (folio_test_swapcache(folio))
+		ci = swap_cluster_get_and_lock(folio);
 
-			ci = swap_cluster_get_and_lock(folio);
-		}
+	if (do_lru)
+		lruvec = folio_lruvec_lock(folio);
 
-		/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	ret = __split_frozen_folio(folio, new_order, split_at, NULL, split_type);
+
+	/*
+	 * Unfreeze the post-split folios and put them back to the right
+	 * place. Keep the head @folio frozen until the end: sub entries
+	 * in swap cache must be updated first, so a concurrent
+	 * swap_cache_get_folio() cannot return the head folio for a sub
+	 * entry (folio_try_get() will fail on the head @folio until unfreeze).
+	 */
+	for (new_folio = folio_next(folio); new_folio != end_folio;
+	     new_folio = next) {
+		next = folio_next(new_folio);
+		zone_device_private_split_cb(folio, new_folio);
+		folio_ref_unfreeze(new_folio,
+				   folio_swapcache_ref_count(new_folio) + 1);
 		if (do_lru)
-			lruvec = folio_lruvec_lock(folio);
+			lru_add_split_folio(folio, new_folio, lruvec, list);
+		if (ci)
+			__swap_cache_replace_folio(ci, folio, new_folio);
+	}
 
-		ret = __split_unmapped_folio(folio, new_order, split_at, xas,
-					     mapping, split_type);
+	zone_device_private_split_cb(folio, NULL);
+	folio_ref_unfreeze(folio, folio_swapcache_ref_count(folio) + 1);
 
-		/*
-		 * Unfreeze after-split folios and put them back to the right
-		 * list. @folio should be kept frozon until page cache
-		 * entries are updated with all the other after-split folios
-		 * to prevent others seeing stale page cache entries.
-		 * As a result, new_folio starts from the next folio of
-		 * @folio.
-		 */
-		for (new_folio = folio_next(folio); new_folio != end_folio;
-		     new_folio = next) {
-			unsigned long nr_pages = folio_nr_pages(new_folio);
+	if (do_lru)
+		lruvec_unlock(lruvec);
+	if (ci)
+		swap_cluster_unlock(ci);
+out_no_split:
+	local_irq_enable();
+	if (anon_vma) {
+		if (!ret && !folio_is_device_private(folio))
+			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
+		remap_page(folio, 1 << old_order, ttu_flags);
+	}
+out_unlock:
+	if (anon_vma) {
+		anon_vma_unlock_write(anon_vma);
+		put_anon_vma(anon_vma);
+	}
 
-			next = folio_next(new_folio);
+	return ret;
+}
 
-			zone_device_private_split_cb(folio, new_folio);
+/**
+ * __folio_split_unmap_and_freeze_file() - split a file-backed folio
+ * @folio: folio to split, must be locked and file-backed
+ * @new_order: the order of the after-split folios (uniform split), or the
+ *             smallest order of the after-split folios (non-uniform split)
+ * @split_at: in non-uniform split, the folio containing @split_at is split
+ *            until its order becomes @new_order
+ * @list: after-split folios will be put on it if non NULL
+ * @split_type: perform uniform split or not (non-uniform split)
+ *
+ * Helper for splitting a file-backed folio. It unmaps @folio, freezes its
+ * refcount, and perform the split, updates the page cache entries. Split
+ * folios are unfrozen but not remapped, they are faulted back in on demand.
+ *
+ * Return: 0 on success, otherwise an error number is returned. (if -ENOMEM
+ * is returned, @folio might be split but not to @new_order)
+ */
+static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int new_order,
+					       struct page *split_at, struct list_head *list,
+					       enum split_type split_type)
+{
+	struct address_space *mapping = folio->mapping;
+	XA_STATE(xas, &mapping->i_pages, folio->index);
+	struct folio *end_folio = folio_next(folio);
+	long old_nr_pages = folio_nr_pages(folio);
+	struct mem_cgroup *memcg, *old_memcg;
+	struct folio *new_folio, *next;
+	int nr_shmem_dropped = 0;
+	unsigned int min_order;
+	struct lruvec *lruvec;
+	pgoff_t end = 0;
+	gfp_t gfp;
+	int ret = 0;
 
-			folio_ref_unfreeze(new_folio,
-					   folio_cache_ref_count(new_folio) + 1);
+	min_order = mapping_min_folio_order(mapping);
+	if (new_order < min_order)
+		return -EINVAL;
 
-			if (do_lru)
-				lru_add_split_folio(folio, new_folio, lruvec, list);
+	/*
+	 * Switch to folio's memcg as xarray node allocation can happen and
+	 * needs to charge to it.
+	 */
+	memcg = get_mem_cgroup_from_folio(folio);
+	old_memcg = set_active_memcg(memcg);
 
-			/*
-			 * Anonymous folio with swap cache.
-			 * NOTE: shmem in swap cache is not supported yet.
-			 */
-			if (ci) {
-				__swap_cache_replace_folio(ci, folio, new_folio);
-				continue;
-			}
+	gfp = current_gfp_context(mapping_gfp_mask(mapping) & GFP_RECLAIM_MASK);
+	if (!filemap_release_folio(folio, gfp)) {
+		ret = -EBUSY;
+		goto fail_free;
+	}
 
-			/* Anonymous folio without swap cache */
-			if (!mapping)
-				continue;
+	mapping_set_update(&xas, mapping);
 
-			/* Add the new folio to the page cache. */
-			if (new_folio->index < end) {
-				__xa_store(&mapping->i_pages, new_folio->index,
-					   new_folio, 0);
-				continue;
-			}
+	if (split_type == SPLIT_TYPE_UNIFORM) {
+		int old_order = folio_order(folio);
 
-			VM_WARN_ON_ONCE(!nr_shmem_dropped);
-			/* Drop folio beyond EOF: ->index >= end */
-			if (shmem_mapping(mapping) && nr_shmem_dropped)
-				*nr_shmem_dropped += nr_pages;
-			else if (folio_test_clear_dirty(new_folio))
-				folio_account_cleaned(
-					new_folio, inode_to_wb(mapping->host));
-			__filemap_remove_folio(new_folio, NULL);
-			folio_put_refs(new_folio, nr_pages);
+		xas_set_order(&xas, folio->index, new_order);
+		xas_split_alloc(&xas, folio, old_order, gfp);
+		if (xas_error(&xas)) {
+			ret = xas_error(&xas);
+			goto fail_free;
 		}
+	}
 
-		zone_device_private_split_cb(folio, NULL);
-		/*
-		 * Unfreeze @folio only after all page cache entries, which
-		 * used to point to it, have been updated with new folios.
-		 * Otherwise, a parallel folio_try_get() can grab @folio
-		 * and its caller can see stale page cache entries.
-		 */
-		folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+	i_mmap_lock_read(mapping);
 
-		if (do_lru)
-			lruvec_unlock(lruvec);
+	/* Racy check if we can split the page, before unmap_folio() */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto fail_mmap_unlock;
+	}
 
-		if (ci)
-			swap_cluster_unlock(ci);
-	} else {
-		if (dequeue_deferred) {
-			list_lru_unlock(lru);
-			rcu_read_unlock();
+	/*
+	 * __split_frozen_folio() may need to trim off pages beyond
+	 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
+	 * seqlock, which cannot be nested inside the page tree lock.
+	 * So note end now: i_size itself may be changed at any moment,
+	 * but folio lock is good enough to serialize the trimming.
+	 */
+	end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
+	if (shmem_mapping(mapping))
+		end = shmem_fallocend(mapping->host, end);
+
+	unmap_folio(folio);
+
+	xas_lock_irq(&xas);
+
+	/*
+	 * Check if the folio is present in page cache.
+	 * We assume all tail are present too, if folio is there.
+	 */
+	if (xas_load(&xas) != folio) {
+		ret = -EAGAIN;
+		goto fail;
+	}
+
+	if (!folio_ref_freeze(folio, old_nr_pages + 1)) {
+		ret = -EAGAIN;
+		goto fail;
+	}
+
+	if (folio_test_pmd_mappable(folio) && new_order < HPAGE_PMD_ORDER) {
+		if (folio_test_swapbacked(folio))
+			lruvec_stat_mod_folio(folio, NR_SHMEM_THPS, -old_nr_pages);
+		else
+			lruvec_stat_mod_folio(folio, NR_FILE_THPS, -old_nr_pages);
+	}
+
+	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	lruvec = folio_lruvec_lock(folio);
+	ret = __split_frozen_folio(folio, new_order, split_at, &xas, split_type);
+
+	/*
+	 * Unfreeze after-split folios and put them back to the right
+	 * list. @folio should be kept frozen until page cache
+	 * entries are updated with all the other after-split folios
+	 * to prevent others seeing stale page cache entries.
+	 * As a result, new_folio starts from the next folio of
+	 * @folio.
+	 */
+	for (new_folio = folio_next(folio); new_folio != end_folio;
+	     new_folio = next) {
+		unsigned long nr_pages = folio_nr_pages(new_folio);
+
+		/* compute next before the folio can be freed below */
+		next = folio_next(new_folio);
+
+		folio_ref_unfreeze(new_folio,
+				   folio_nr_pages(new_folio) + 1);
+
+		lru_add_split_folio(folio, new_folio, lruvec, list);
+
+		/* Add the new folio to the page cache. */
+		if (new_folio->index < end) {
+			__xa_store(&mapping->i_pages, new_folio->index,
+				   new_folio, 0);
+			continue;
 		}
-		return -EAGAIN;
+
+		/* Drop folio beyond EOF: ->index >= end */
+		if (shmem_mapping(mapping))
+			nr_shmem_dropped += nr_pages;
+		else if (folio_test_clear_dirty(new_folio))
+			folio_account_cleaned(new_folio,
+					      inode_to_wb(mapping->host));
+		__filemap_remove_folio(new_folio, NULL);
+		folio_put_refs(new_folio, nr_pages);
 	}
 
+	/*
+	 * Unfreeze @folio only after all page cache entries, which
+	 * used to point to it, have been updated with new folios.
+	 * Otherwise, a parallel folio_try_get() can grab @folio
+	 * and its caller can see stale page cache entries.
+	 */
+	folio_ref_unfreeze(folio, folio_nr_pages(folio) + 1);
+	lruvec_unlock(lruvec);
+fail:
+	/*
+	 * If we want to use try_to_migrate() on file in unmap_folio,
+	 * remember to add remap_page() and adapt it.
+	 */
+	xas_unlock_irq(&xas);
+fail_mmap_unlock:
+	if (nr_shmem_dropped)
+		shmem_uncharge(mapping->host, nr_shmem_dropped);
+	/*
+	 * Drop the mapping while the inode is still pinned. @folio stays
+	 * locked and present in the page cache, so eviction cannot free
+	 * the inode yet, nothing past this point may touch the inode or
+	 * the mapping.
+	 */
+	i_mmap_unlock_read(mapping);
+fail_free:
+	/* Restore the previously active memcg */
+	set_active_memcg(old_memcg);
+	mem_cgroup_put(memcg);
+	xas_destroy(&xas);
 	return ret;
 }
 
@@ -4086,9 +4263,9 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
  * @list: after-split folios will be put on it if non NULL
  * @split_type: perform uniform split or not (non-uniform split)
  *
- * It calls __split_unmapped_folio() to perform uniform and non-uniform split.
+ * It calls __split_frozen_folio() to perform uniform and non-uniform split.
  * It is in charge of checking whether the split is supported or not and
- * preparing @folio for __split_unmapped_folio().
+ * preparing @folio for __split_frozen_folio().
  *
  * After splitting, the after-split folio containing @lock_at remains locked
  * and others are unlocked:
@@ -4102,17 +4279,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		struct page *split_at, struct page *lock_at,
 		struct list_head *list, enum split_type split_type)
 {
-	XA_STATE(xas, &folio->mapping->i_pages, folio->index);
+	bool is_swapcache = folio_test_swapcache(folio);
 	struct folio *end_folio = folio_next(folio);
 	bool is_anon = folio_test_anon(folio);
-	struct mem_cgroup *memcg, *old_memcg;
-	struct address_space *mapping = NULL;
-	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
-	int nr_shmem_dropped = 0;
-	enum ttu_flags ttu_flags = 0;
-	pgoff_t end = 0;
 	int ret;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
@@ -4120,141 +4291,29 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	if (folio != page_folio(split_at) || folio != page_folio(lock_at)) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	if (new_order >= old_order) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	ret = folio_check_splittable(folio, new_order, split_type);
 	if (ret) {
 		VM_WARN_ONCE(ret == -EINVAL, "Tried to split an unsplittable folio");
-		goto out_no_memcg;
-	}
-
-	/*
-	 * switch to folio's memcg as xarray node allocation can happen and
-	 * needs to charge to it.
-	 */
-	memcg = get_mem_cgroup_from_folio(folio);
-	old_memcg = set_active_memcg(memcg);
-
-	if (is_anon) {
-		/*
-		 * The caller does not necessarily hold an mmap_lock that would
-		 * prevent the anon_vma disappearing so we first we take a
-		 * reference to it and then lock the anon_vma for write. This
-		 * is similar to folio_lock_anon_vma_read except the write lock
-		 * is taken to serialise against parallel split or collapse
-		 * operations.
-		 */
-		anon_vma = folio_get_anon_vma(folio);
-		if (!anon_vma) {
-			ret = -EBUSY;
-			goto out;
-		}
-		anon_vma_lock_write(anon_vma);
-		mapping = NULL;
-	} else {
-		unsigned int min_order;
-		gfp_t gfp;
-
-		mapping = folio->mapping;
-		min_order = mapping_min_folio_order(mapping);
-		if (new_order < min_order) {
-			ret = -EINVAL;
-			goto out;
-		}
-
-		gfp = current_gfp_context(mapping_gfp_mask(mapping) &
-							GFP_RECLAIM_MASK);
-
-		if (!filemap_release_folio(folio, gfp)) {
-			ret = -EBUSY;
-			goto out;
-		}
-
-		mapping_set_update(&xas, mapping);
-
-		if (split_type == SPLIT_TYPE_UNIFORM) {
-			xas_set_order(&xas, folio->index, new_order);
-			xas_split_alloc(&xas, folio, old_order, gfp);
-			if (xas_error(&xas)) {
-				ret = xas_error(&xas);
-				goto out;
-			}
-		}
-
-		anon_vma = NULL;
-		i_mmap_lock_read(mapping);
-
-		/*
-		 *__split_unmapped_folio() may need to trim off pages beyond
-		 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
-		 * seqlock, which cannot be nested inside the page tree lock.
-		 * So note end now: i_size itself may be changed at any moment,
-		 * but folio lock is good enough to serialize the trimming.
-		 */
-		end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
-		if (shmem_mapping(mapping))
-			end = shmem_fallocend(mapping->host, end);
-	}
-
-	/*
-	 * Racy check if we can split the page, before unmap_folio() will
-	 * split PMDs
-	 */
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
-		ret = -EAGAIN;
-		goto out_unlock;
-	}
-
-	unmap_folio(folio);
-
-	/* block interrupt reentry in xa_lock and spinlock */
-	local_irq_disable();
-	if (mapping) {
-		/*
-		 * Check if the folio is present in page cache.
-		 * We assume all tail are present too, if folio is there.
-		 */
-		xas_lock(&xas);
-		xas_reset(&xas);
-		if (xas_load(&xas) != folio) {
-			ret = -EAGAIN;
-			goto fail;
-		}
+		goto out;
 	}
 
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, split_at, &xas, mapping,
-						true, list, split_type, end, &nr_shmem_dropped);
-fail:
-	if (mapping)
-		xas_unlock(&xas);
-
-	local_irq_enable();
-
-	if (nr_shmem_dropped)
-		shmem_uncharge(mapping->host, nr_shmem_dropped);
-
-	if (!ret && is_anon && !folio_is_device_private(folio))
-		ttu_flags = TTU_USE_SHARED_ZEROPAGE;
-
-	remap_page(folio, 1 << old_order, ttu_flags);
-
-	/*
-	 * Drop the mapping while the inode is still pinned. @folio stays
-	 * locked and present in the page cache until the loop below, so
-	 * eviction cannot free the inode yet; @lock_at is not enough, it may
-	 * be a tail beyond EOF that the split already dropped from the page
-	 * cache. Nothing past this point may touch the inode or the mapping.
-	 */
-	if (mapping) {
-		i_mmap_unlock_read(mapping);
-		mapping = NULL;
-	}
+	if (is_anon)
+		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
+						     true, list, split_type);
+	else if (is_swapcache)
+		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
+						     false, list, split_type);
+	else
+		ret = __folio_split_unmap_and_freeze_file(folio, new_order, split_at,
+							  list, split_type);
 
 	/*
 	 * Unlock all after-split folios except the one containing
@@ -4265,29 +4324,19 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		if (new_folio == page_folio(lock_at))
 			continue;
 
-		folio_unlock(new_folio);
 		/*
 		 * Subpages whose mapping has been zapped may be freed
 		 * earlier, but freeing them requires taking the
-		 * lru_lock, so we defer put_page() on tail pages until
+		 * lru_lock, so we defer folio_put() on tail pages until
 		 * after the split completes.
 		 */
-		free_folio_and_swap_cache(new_folio);
+		if (is_swapcache && !folio_mapped(new_folio))
+			folio_free_swap(new_folio);
+		folio_unlock(new_folio);
+		folio_put(new_folio);
 	}
 
-out_unlock:
-	if (anon_vma) {
-		anon_vma_unlock_write(anon_vma);
-		put_anon_vma(anon_vma);
-	}
-	if (mapping)
-		i_mmap_unlock_read(mapping);
 out:
-	/* restore to caller's old_memcg */
-	set_active_memcg(old_memcg);
-	mem_cgroup_put(memcg);
-out_no_memcg:
-	xas_destroy(&xas);
 	if (is_pmd_order(old_order))
 		count_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);
 	count_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);
@@ -4311,29 +4360,20 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
  * isolated from LRU (if applicable)
  *
  * Upon return, the folio is not remapped, split folios are not added to LRU,
- * free_folio_and_swap_cache() is not called, and new folios remain locked.
+ * folio_free_swap() is not called, and new folios remain locked.
  *
  * Return: 0 on success, -EAGAIN if the folio cannot be split (e.g., due to
  *         insufficient reference count or extra pins).
  */
 int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 {
-	int ret = 0;
-
 	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);
 
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
-		return -EAGAIN;
-
-	local_irq_disable();
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, &folio->page, NULL,
-						NULL, false, NULL, SPLIT_TYPE_UNIFORM,
-						0, NULL);
-	local_irq_enable();
-	return ret;
+	return __folio_split_unmap_and_freeze(folio, new_order, &folio->page, false,
+					      false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*
@@ -4352,10 +4392,9 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  *    GUP pins, will result in the folio not getting split; instead, the caller
  *    will receive an -EAGAIN.
  *
- * 4) @new_order > 1, usually. Splitting to order-1 anonymous folios is not
- *    supported for non-file-backed folios, because folio->_deferred_list, which
- *    is used by partially mapped folios, is stored in subpage 2, but an order-1
- *    folio only has subpages 0 and 1. File-backed order-1 folios are supported,
+ * 4) @new_order != 1 for anon or swapcache. Anon folios need subpage 2 for
+ *    _deferred_list, which order-1 folios lack, and a swapcache folio may
+ *    become anon once faulted in. File-backed order-1 folios are supported,
  *    since they do not use _deferred_list.
  *
  * After splitting, the caller's folio reference will be transferred to @page,
@@ -4384,7 +4423,7 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  * with the folio. Splitting to order 0 is compatible with all folios.
  */
 int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
-				     unsigned int new_order)
+				       unsigned int new_order)
 {
 	struct folio *folio = page_folio(page);
 
diff --git a/mm/swap_state.c b/mm/swap_state.c
index b76eb3d876fd7..59a577f685b5b 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -389,8 +389,9 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
 	    folio_order(old) != folio_order(new)) {
 		ci_off = swp_cluster_offset(old->swap);
 		ci_end = ci_off + folio_nr_pages(old);
-		while (ci_off++ < ci_end)
+		do {
 			WARN_ON_ONCE(swp_tb_to_folio(__swap_table_get(ci, ci_off)) != old);
+		} while (++ci_off < ci_end);
 	}
 }
 

3/2 2026/08/20 19:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12879
							output: 29
							thoughts: 490
						

Thoughts:
**Analyzing a Kernel Memory Management Patch**

Okay, so I'm looking at a patch that's touched `mm/huge_memory.c` and `mm/swap_state.c`. It looks like they're reworking the folio splitting logic, which is interesting. Specifically, it's about how we handle anonymous, swapcache, and file-backed folios. They've renamed `__split_unmapped_folio` to `__split_frozen_folio`, which is a good indicator of some underlying change in semantics, and they've added new helpers like `__folio_split_unmap_and_freeze` and `__folio_split_unmap_and_freeze_file`. `__folio_split` has also been refactored to use the new helpers. The patch also modifies how `folio_check_splittable` handles swapcache folios. And, importantly, there's a change in `__swap_cache_replace_folio` too.

My gut says this is definitely worth a closer look, especially for fuzzing. This touches *core* memory management code – THP, folio splitting, and swap. These are prime targets for exposing subtle memory corruption issues. It's high-value, potentially high-risk, so let's dig deeper.

So, let's identify the key symbols in this patch. I've got: `remap_page`, `__split_frozen_folio`, `folio_check_splittable`, `folio_swapcache_ref_count`, `__folio_split_unmap_and_freeze`, `__folio_split_unmap_and_freeze_file`, `__folio_split`, `folio_split_unmapped`, `__split_huge_page_to_list_to_order`, and `__swap_cache_replace_folio`.

Now, the important ones for a fuzzing perspective are those that are globally accessible or externally visible and not inlined: `folio_check_splittable`, `folio_split_unmapped`, `__split_huge_page_to_list_to_order`, `__swap_cache_replace_folio`, and maybe even `split_huge_page_to_list_to_order`. It would be wise to search for usages of these to understand reachability and create tests.

Alright, time to get searching and understand how these fit into the larger memory management picture.



4/2 2026/08/20 19:02 tool grepper
0m Args:
{
  "Expression": "^(int|void|bool) .*folio_check_splittable"
}

Results:
{
  "Output": "include/linux/huge_mm.h=407=int split_folio_to_list(struct folio *folio, struct list_head *list);\ninclude/linux/huge_mm.h:408:int folio_check_splittable(struct folio *folio, unsigned int new_order,\ninclude/linux/huge_mm.h-409-\t\t\t   enum split_type split_type);\n--\nmm/huge_memory.c=3794=static int __split_frozen_folio(struct folio *folio, int new_order,\n--\nmm/huge_memory.c-3874- */\nmm/huge_memory.c:3875:int folio_check_splittable(struct folio *folio, unsigned int new_order,\nmm/huge_memory.c-3876-\t\t\t   enum split_type split_type)\n"
}

5/2 2026/08/20 19:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14136
							output: 28
							thoughts: 90
						

Thoughts:
Here's my take on the mental mapping process, as I understand it:

**Mapping Key Functions to Memory Management Structures**

Okay, so I'm drilling down into some core memory management functions. I'm noting that `folio_check_splittable`, `folio_split_unmapped`, and `__split_huge_page_to_list_to_order` are all clustered in `mm/huge_memory.c`. That placement is exactly what I would expect, they are all working on operations related to splitting and managing huge pages, so clearly they are organized logically in the huge memory management area. The common directory makes perfect sense.

Then there's `__swap_cache_replace_folio`. That one's in `mm/swap_state.c`. Hmm, right, that's related to swapping, which makes total sense. I'm mentally filing this under "swap cache interaction." It seems to be a function dealing with replacing a folio in the swap cache, which is distinct from the huge page splitting/management, hence the different file. Good organization there, too. I appreciate the separation of concerns, the module organization is sane and logical. I can quickly determine the context for a given function by knowing its location.



6/2 2026/08/20 19:02 tool grepper
0m Args:
{
  "Expression": "^(int|void|bool) .*folio_split_unmapped"
}

Results:
{
  "Output": "include/linux/huge_mm.h=403=int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-404-\t\tunsigned int new_order);\ninclude/linux/huge_mm.h:405:int folio_split_unmapped(struct folio *folio, unsigned int new_order);\ninclude/linux/huge_mm.h-406-unsigned int min_order_for_split(struct folio *folio);\n--\nmm/huge_memory.c=4278=static int __folio_split(struct folio *folio, unsigned int new_order,\n--\nmm/huge_memory.c-4367- */\nmm/huge_memory.c:4368:int folio_split_unmapped(struct folio *folio, unsigned int new_order)\nmm/huge_memory.c-4369-{\n"
}

7/2 2026/08/20 19:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14756
							output: 36
							thoughts: 20
						

Thoughts:
Here's my take on those thoughts:

**Analyzing `__split_huge_page_to_list_to_order`**

Okay, time to dive into `__split_huge_page_to_list_to_order`. This function is likely a critical piece of memory management, probably within a virtual memory system. Given the name, I'm guessing it handles the process of breaking down a large, "huge" page into smaller, more manageable units, and then, crucially, placing those smaller pages into some kind of ordered structure – probably a linked list, queue, or perhaps even a tree if the order is complex.

My initial thought is to look for how the function determines *why* it needs to split the page. Is it due to fragmentation, memory pressure, a specific allocation request that doesn't align with a huge page boundary, or something else entirely? The 'to_order' part suggests that the split isn't just a basic decomposition; there's an ordering requirement involved. That makes me wonder what the ordering criteria are – are we talking about physical address, virtual address, or something more nuanced like usage patterns or access frequency?

I need to quickly grasp the implications of the ordering. What kind of overhead is introduced by this ordered structure? What algorithms are being used to maintain this order efficiently? And, of course, what are the implications on performance? Are we optimizing for allocation speed, deallocation speed, or something else entirely? How does the chosen data structure scale with the number of split pages? This also begs the question of how the huge page itself is managed *before* and *after* the split – do we need to track it as a partially-used entity? Is there any mechanism for "re-merging" smaller pages back into a huge page later on? This is critical for avoiding further fragmentation. I also want to check for any potential race conditions here. The function likely will operate on shared memory which could result in a race condition.

I need to understand what the larger code context is. What process or subsystem calls this function? The context will shed light on what the design goals and trade-offs are.



8/2 2026/08/20 19:03 tool grepper
0m Args:
{
  "Expression": "^(int|void|bool) .*__split_huge_page_to_list_to_order"
}

Results:
{
  "Output": "include/linux/huge_mm.h=398=enum split_type {\n--\ninclude/linux/huge_mm.h-402-\ninclude/linux/huge_mm.h:403:int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-404-\t\tunsigned int new_order);\n--\nmm/huge_memory.c=4368=int folio_split_unmapped(struct folio *folio, unsigned int new_order)\n--\nmm/huge_memory.c-4424- */\nmm/huge_memory.c:4425:int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\nmm/huge_memory.c-4426-\t\t\t\t       unsigned int new_order)\n"
}

9/2 2026/08/20 19:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15490
							output: 30
							thoughts: 14
						

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

**Diving into `__swap_cache_replace_folio`**

Alright, time to dig into the guts of the swap cache replacement logic. Let's see how `__swap_cache_replace_folio` is handling things. I want to understand the exact mechanics of this function in particular. What's it doing when a folio needs to be swapped out? How's it making replacement decisions? We know how critical this is for performance, so I'm hoping to get a clearer picture of its impact and potential bottlenecks. No time to waste, let's look under the hood!



10/2 2026/08/20 19:03 tool grepper
0m Args:
{
  "Expression": "^(int|void|bool) .*__swap_cache_replace_folio"
}

Results:
{
  "Output": "mm/swap.h=321=void __swap_cache_del_folio(struct swap_cluster_info *ci,\nmm/swap.h-322-\t\t\t    struct folio *folio, swp_entry_t entry, void *shadow);\nmm/swap.h:323:void __swap_cache_replace_folio(struct swap_cluster_info *ci,\nmm/swap.h-324-\t\t\t\tstruct folio *old, struct folio *new);\n--\nmm/swap_state.c=336=void swap_cache_del_folio(struct folio *folio)\n--\nmm/swap_state.c-361- */\nmm/swap_state.c:362:void __swap_cache_replace_folio(struct swap_cluster_info *ci,\nmm/swap_state.c-363-\t\t\t\tstruct folio *old, struct folio *new)\n"
}

11/2 2026/08/20 19:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15908
							output: 35
							thoughts: 23
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Checking a Potential Bottleneck**

Okay, so I'm just quickly running through the mental checklist here. I'm focusing in on the data processing side of things, specifically on this `split_huge_page_to_list_to_order` function. It's a prime suspect for performance issues given the scale of data we're likely dealing with. I need to make sure the implementation is efficient, particularly concerning memory allocation, iteration, and any potential inefficiencies within sorting or indexing. Even though I have a pretty strong understanding of the system, it's wise to revisit any function that handles a large input space just to be sure there aren't any subtle, easily overlooked, bottlenecks. Time to take a closer look at the actual implementation.



12/2 2026/08/20 19:03 tool grepper
0m Args:
{
  "Expression": "^(int|void|bool) .*split_huge_page_to_list_to_order"
}

Results:
{
  "Output": "include/linux/huge_mm.h=398=enum split_type {\n--\ninclude/linux/huge_mm.h-402-\ninclude/linux/huge_mm.h:403:int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-404-\t\tunsigned int new_order);\n--\nmm/huge_memory.c=4368=int folio_split_unmapped(struct folio *folio, unsigned int new_order)\n--\nmm/huge_memory.c-4424- */\nmm/huge_memory.c:4425:int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\nmm/huge_memory.c-4426-\t\t\t\t       unsigned int new_order)\n"
}

13/2 2026/08/20 19:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16371
							output: 42
							thoughts: 40
						

Thoughts:
Okay, here's my summary, in the first person, as requested:

**Deconstructing `split_huge_page_to_list_to_order`**

Right, let me see...`split_huge_page_to_list_to_order`. That's a mouthful, and based on the naming convention, it's almost certainly an inline function or a macro, designed for efficiency. Given the context, it's likely dealing with some aspect of huge page management, probably related to fragmentation or coalescing. Okay, I need to understand this better. My next logical step is to dive into the definition. The kernel's naming conventions are usually pretty consistent, so the header file defining it should be in the obvious place. Let's start with `include/linux/huge_mm.h`. That's where I expect to find the magic behind this function. Time to check it out.



14/2 2026/08/20 19:03 tool grepper
0m Args:
{
  "Expression": "split_huge_page_to_list_to_order",
  "PathPrefix": "include/linux/huge_mm.h"
}

Results:
{
  "Output": "include/linux/huge_mm.h=398=enum split_type {\n--\ninclude/linux/huge_mm.h-402-\ninclude/linux/huge_mm.h:403:int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-404-\t\tunsigned int new_order);\n--\ninclude/linux/huge_mm.h=410=int folio_split(struct folio *folio, unsigned int new_order, struct page *page,\n--\ninclude/linux/huge_mm.h-412-\ninclude/linux/huge_mm.h:413:static inline int split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-414-\t\tunsigned int new_order)\ninclude/linux/huge_mm.h-415-{\ninclude/linux/huge_mm.h:416:\treturn __split_huge_page_to_list_to_order(page, list, new_order);\ninclude/linux/huge_mm.h-417-}\ninclude/linux/huge_mm.h=418=static inline int split_huge_page_to_order(struct page *page, unsigned int new_order)\ninclude/linux/huge_mm.h-419-{\ninclude/linux/huge_mm.h:420:\treturn split_huge_page_to_list_to_order(page, NULL, new_order);\ninclude/linux/huge_mm.h-421-}\n--\ninclude/linux/huge_mm.h=423=static inline int split_huge_page(struct page *page)\ninclude/linux/huge_mm.h-424-{\ninclude/linux/huge_mm.h:425:\treturn split_huge_page_to_list_to_order(page, NULL, 0);\ninclude/linux/huge_mm.h-426-}\n--\ninclude/linux/huge_mm.h=645=static inline int\ninclude/linux/huge_mm.h:646:split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-647-\t\tunsigned int new_order)\n--\ninclude/linux/huge_mm.h=826=static inline int split_folio_to_order(struct folio *folio, int new_order)\ninclude/linux/huge_mm.h-827-{\ninclude/linux/huge_mm.h:828:\treturn split_huge_page_to_list_to_order(\u0026folio-\u003epage, NULL, new_order);\ninclude/linux/huge_mm.h-829-}\n"
}

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

Args:
null

Results:
null

Tokens:
							input: 17350
							output: 22
							thoughts: 82
						

Thoughts:
Here's my thought process, as if I were reviewing this code:

**Analyzing Huge Page Splitting Functions**

Alright, so `__split_huge_page_to_list_to_order` is definitely the key non-inlined function involved in this huge page splitting process. That's a good starting point. Now, let's quickly see if there are other players in this particular domain. Ah, yes, `folio_split_unmapped`, `folio_check_splittable`, and `__swap_cache_replace_folio` - all of them warrant a closer look to understand their interactions with the main split function, and how each is involved in the overall huge page management.

Next, I need to make sure I understand the conditional logic for huge page splitting; specifically, I need to ensure that the `TRANSPARENT_HUGEPAGE` feature is, indeed, active and required in this code path. That will be critical for understanding *why* these functions are called.



16/2 2026/08/20 19:03 tool grepper
0m Args:
{
  "Expression": "config TRANSPARENT_HUGEPAGE"
}

Results:
{
  "Output": "mm/Kconfig=832=config MM_ID\n--\nmm/Kconfig-834-\nmm/Kconfig:835:menuconfig TRANSPARENT_HUGEPAGE\nmm/Kconfig-836-\tbool \"Transparent Hugepage Support\"\n--\nmm/Kconfig=853=choice\n--\nmm/Kconfig-858-\nmm/Kconfig:859:\tconfig TRANSPARENT_HUGEPAGE_ALWAYS\nmm/Kconfig-860-\t\tbool \"always\"\n--\nmm/Kconfig-865-\nmm/Kconfig:866:\tconfig TRANSPARENT_HUGEPAGE_MADVISE\nmm/Kconfig-867-\t\tbool \"madvise\"\n--\nmm/Kconfig-874-\nmm/Kconfig:875:\tconfig TRANSPARENT_HUGEPAGE_NEVER\nmm/Kconfig-876-\t\tbool \"never\"\n--\nmm/Kconfig=882=choice\n--\nmm/Kconfig-891-\nmm/Kconfig:892:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_NEVER\nmm/Kconfig-893-\t\tbool \"never\"\n--\nmm/Kconfig-901-\nmm/Kconfig:902:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ALWAYS\nmm/Kconfig-903-\t\tbool \"always\"\n--\nmm/Kconfig-909-\nmm/Kconfig:910:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_WITHIN_SIZE\nmm/Kconfig-911-\t\tbool \"within_size\"\n--\nmm/Kconfig-917-\nmm/Kconfig:918:\tconfig TRANSPARENT_HUGEPAGE_SHMEM_HUGE_ADVISE\nmm/Kconfig-919-\t\tbool \"advise\"\n--\nmm/Kconfig=927=choice\n--\nmm/Kconfig-936-\nmm/Kconfig:937:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_NEVER\nmm/Kconfig-938-\t\tbool \"never\"\n--\nmm/Kconfig-945-\nmm/Kconfig:946:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ALWAYS\nmm/Kconfig-947-\t\tbool \"always\"\n--\nmm/Kconfig-953-\nmm/Kconfig:954:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_WITHIN_SIZE\nmm/Kconfig-955-\t\tbool \"within_size\"\n--\nmm/Kconfig-961-\nmm/Kconfig:962:\tconfig TRANSPARENT_HUGEPAGE_TMPFS_HUGE_ADVISE\nmm/Kconfig-963-\t\tbool \"advise\"\n"
}

17/2 2026/08/20 19:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18379
							output: 28
							thoughts: 34
						

Thoughts:
**My Assessment of Huge Page Dependencies**

Okay, so I'm dealing with `mm/huge_memory.c` and immediately, my brain flags that `TRANSPARENT_HUGEPAGE` is definitely a requirement. Makes sense, obviously. Now, the next logical step is to dive into `mm/Makefile`. That's where I need to confirm the build dependencies and ensure everything is linking correctly and as expected. It's the standard, efficient approach.



18/2 2026/08/20 19:03 tool grepper
0m Args:
{
  "Expression": "huge_memory.o",
  "PathPrefix": "mm/Makefile"
}

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

19/2 2026/08/20 19:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18637
							output: 28
							thoughts: 28
						

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

**Investigating Memory Management Dependencies**

Alright, so the discussion has clearly pointed out that we absolutely need to include `TRANSPARENT_HUGEPAGE` – no question about that. It's the lynchpin. But now I'm starting to think about dependencies further down the line. What's the impact of this change on other sections of the kernel? Specifically, I need to consider `mm/swap_state.c`. How will this new transparent hugepage implementation interact with the swap mechanisms? Are there any potential conflicts, performance bottlenecks, or race conditions I need to anticipate? This is a key area to explore further, so I'm filing that for deeper investigation.



20/2 2026/08/20 19:03 tool grepper
0m Args:
{
  "Expression": "swap_state.o",
  "PathPrefix": "mm/Makefile"
}

Results:
{
  "Output": "mm/Makefile=76=endif\nmm/Makefile-77-\nmm/Makefile:78:obj-$(CONFIG_SWAP)\t+= page_io.o swap_state.o swapfile.o\nmm/Makefile-79-obj-$(CONFIG_ZSWAP)\t+= zswap.o\n"
}

21/2 2026/08/20 19:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18937
							output: 36
							thoughts: 134
						

Thoughts:
**Analyzing the Potential Impact of a Swap Modification**

Okay, so it looks like this patch necessitates some form of `SWAP` handling. My first instinct is always to look for context, so let's see what other configurations might be impacted. Looking through the proposed changes, I see that the patch touches several key functions that are all very interesting to me: `__split_huge_page_to_list_to_order`, `folio_split_unmapped`, `folio_check_splittable`, and `__swap_cache_replace_folio`. They all seem highly relevant.

My immediate focus, given my expertise, is to carefully examine those specific symbols. I need to confirm these functions are present in the kernel and, critically, aren't declared as static. This confirms that these functions are actually externally available and can be patched. I need to confirm the definitions and usage of those functions, what files use them, and what the effect will be of modifying them. From there I can determine the impact of these changes.



22/2 2026/08/20 19:03 tool grepper
0m Args:
{
  "Expression": "^(int|void|bool) .*__split_huge_page_to_list_to_order"
}

Results:
{
  "Output": "include/linux/huge_mm.h=398=enum split_type {\n--\ninclude/linux/huge_mm.h-402-\ninclude/linux/huge_mm.h:403:int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-404-\t\tunsigned int new_order);\n--\nmm/huge_memory.c=4368=int folio_split_unmapped(struct folio *folio, unsigned int new_order)\n--\nmm/huge_memory.c-4424- */\nmm/huge_memory.c:4425:int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\nmm/huge_memory.c-4426-\t\t\t\t       unsigned int new_order)\n"
}

23/2 2026/08/20 19:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19553
							output: 28
							thoughts: 19
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Double-Checking the `folio_split_unmapped` Functionality**

Okay, so the concept of unmapped folios—those that don't neatly align with our standard indexing—is definitely something we need to account for. No debate there, they *do* exist, and the system needs to handle them. The key now is to ensure that the logic within `folio_split_unmapped` is robust. Is it accounting for all edge cases? Are there potential performance bottlenecks? Are the assumptions about the unmapped data still valid given recent changes to the source material? Before I move on, I want to confirm that this function is still performing as intended. Let's see if the handling of folio split is good.



24/2 2026/08/20 19:03 tool grepper
0m Args:
{
  "Expression": "^(int|void|bool) .*folio_split_unmapped"
}

Results:
{
  "Output": "include/linux/huge_mm.h=403=int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,\ninclude/linux/huge_mm.h-404-\t\tunsigned int new_order);\ninclude/linux/huge_mm.h:405:int folio_split_unmapped(struct folio *folio, unsigned int new_order);\ninclude/linux/huge_mm.h-406-unsigned int min_order_for_split(struct folio *folio);\n--\nmm/huge_memory.c=4278=static int __folio_split(struct folio *folio, unsigned int new_order,\n--\nmm/huge_memory.c-4367- */\nmm/huge_memory.c:4368:int folio_split_unmapped(struct folio *folio, unsigned int new_order)\nmm/huge_memory.c-4369-{\n"
}

25/2 2026/08/20 19:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20009
							output: 29
							thoughts: 14
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:

**Double-Checking `folio_check_splittable`**

Alright, time to revisit `folio_check_splittable`. I'm going back over this because, let's be honest, even after the initial review, there's always a nagging feeling that something could have been overlooked, especially with a function as critical as this. My concern is, is it truly robust enough to handle the edge cases? Does it cover all the permutations of data structures and states we're likely to encounter in a real-world scenario?

I need to make sure I haven't made any assumptions about the data that aren't explicitly validated within the function. Thinking about it, I should run through a mental checklist of possible failure points: null values, empty structures, exceptionally large datasets, and even concurrent access issues, if applicable. I also need to ensure that the logic aligns perfectly with the underlying requirements of the splitting process, specifically addressing how partial splits should be evaluated.

I am going to check the unit tests, obviously, but sometimes those tests are written to cover the obvious cases and there are still issues lurking in the code. I am going to have to make sure they are rigorous. And I may need to write some new ones to ensure all scenarios are covered, potentially generating some random data for a deeper dive. The goal is to be absolutely confident that `folio_check_splittable` won't introduce any unexpected behavior down the line, potentially causing data corruption or, even worse, system instability. I am going to be extra vigilant and think through the code as though I am going to be the person who will be debugging it at 3 am.



26/2 2026/08/20 19:04 tool grepper
0m Args:
{
  "Expression": "^(int|void|bool) .*folio_check_splittable"
}

Results:
{
  "Output": "include/linux/huge_mm.h=407=int split_folio_to_list(struct folio *folio, struct list_head *list);\ninclude/linux/huge_mm.h:408:int folio_check_splittable(struct folio *folio, unsigned int new_order,\ninclude/linux/huge_mm.h-409-\t\t\t   enum split_type split_type);\n--\nmm/huge_memory.c=3794=static int __split_frozen_folio(struct folio *folio, int new_order,\n--\nmm/huge_memory.c-3874- */\nmm/huge_memory.c:3875:int folio_check_splittable(struct folio *folio, unsigned int new_order,\nmm/huge_memory.c-3876-\t\t\t   enum split_type split_type)\n"
}

27/2 2026/08/20 19:04 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20638
							output: 30
							thoughts: 15
						

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

**Double-Checking `__swap_cache_replace_folio`**

Alright, time to revisit `__swap_cache_replace_folio`. I need to ensure that the logic within it is still sound. It's a critical function, and a subtle bug here could have cascading and devastating effects, particularly on memory management and swap performance. I need to run through the replace strategy to ensure that the conditions within that function are still safe, and have not been affected by subsequent improvements.

Specifically, I'm thinking about the edge cases. Am I still handling the synchronous and asynchronous replacement scenarios correctly? Does the current implementation avoid any potential race conditions, especially given concurrent access to the swap cache? Are the locking primitives appropriate and effective? Are the memory barriers in place where they need to be, or are there any opportunities to optimize them without introducing risk? I need to review it under high load, too. I want to make sure it's working as expected.

I also want to check its interaction with other parts of the memory management subsystem. Are there any unexpected dependencies or interactions with page reclaim, dirty page handling, or NUMA optimizations that might introduce subtle performance bottlenecks or even correctness issues? I'll probably also need to run it through a battery of tests, covering both typical and pathological scenarios, to build some confidence. Overall, I want to make sure this is solid.



28/2 2026/08/20 19:04 tool grepper
0m Args:
{
  "Expression": "^(int|void|bool) .*__swap_cache_replace_folio"
}

Results:
{
  "Output": "mm/swap.h=321=void __swap_cache_del_folio(struct swap_cluster_info *ci,\nmm/swap.h-322-\t\t\t    struct folio *folio, swp_entry_t entry, void *shadow);\nmm/swap.h:323:void __swap_cache_replace_folio(struct swap_cluster_info *ci,\nmm/swap.h-324-\t\t\t\tstruct folio *old, struct folio *new);\n--\nmm/swap_state.c=336=void swap_cache_del_folio(struct folio *folio)\n--\nmm/swap_state.c-361- */\nmm/swap_state.c:362:void __swap_cache_replace_folio(struct swap_cluster_info *ci,\nmm/swap_state.c-363-\t\t\t\tstruct folio *old, struct folio *new)\n"
}

29/2 2026/08/20 19:04 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21216
							output: 20
							thoughts: 176
						

Thoughts:
**Analyzing Memory Management Symbols and Dependencies**

Okay, let's break this down. My initial focus symbols are: `__split_huge_page_to_list_to_order`, `folio_split_unmapped`, `folio_check_splittable`, and `__swap_cache_replace_folio`. I'm starting to get a feel for how these relate to different configurations.

I've got `TRANSPARENT_HUGEPAGE` and `SWAP` in my mental stack for the required configs. Wait, hold on. `__swap_cache_replace_folio` is definitely tied to `mm/swap_state.c`, which, as I recall, needs `CONFIG_SWAP`. Makes sense. And `__split_huge_page_to_list_to_order` is in `mm/huge_memory.c`, clearly implicating `CONFIG_TRANSPARENT_HUGEPAGE`. No surprises there.

But I'm not done yet. I need to be thorough. I wonder if there's any other relevant config in play? Let me check for `CONFIG_THP_SWAP`. Gotta consider the interplay between transparent hugepages and swap space. I'll need to keep that in mind as I delve further into this.



30/2 2026/08/20 19:04 tool grepper
0m Args:
{
  "Expression": "config THP_SWAP"
}

Results:
{
  "Output": "mm/Kconfig=969=endchoice\nmm/Kconfig-970-\nmm/Kconfig:971:config THP_SWAP\nmm/Kconfig-972-\tdef_bool y\n"
}

31/2 2026/08/20 19:04 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21751
							output: 29
							thoughts: 50
						

Thoughts:
Okay, here's my interpretation of those thoughts, presented as a first-person expert summary:

**Thinking Through THP_SWAP's Dependencies**

Alright, so `THP_SWAP`... that's a boolean option, and it's defined as `def_bool y`. The interesting part is what determines that default value. The logic states that it's contingent on two things: `TRANSPARENT_HUGEPAGE` and `SWAP`. That makes perfect sense; the ability to swap out huge pages, well, obviously that depends on having both the transparent hugepages feature enabled and swap space available.

To clarify the full impact of these conditions, I need to look into how exactly this dependency chain is set up. Best way to do this is to check the `mm/Kconfig` file in the kernel source. That file will detail the exact relationships and constraints in place around the `THP_SWAP` option. I'll take a look there to confirm my understanding and make sure there are no other subtle implications of enabling or disabling this.



32/2 2026/08/20 19:04 tool grepper
0m Args:
{
  "Expression": "THP_SWAP",
  "PathPrefix": "mm/Kconfig"
}

Results:
{
  "Output": "mm/Kconfig=810=config ARCH_WANT_GENERAL_HUGETLB\n--\nmm/Kconfig-812-\nmm/Kconfig:813:config ARCH_WANTS_THP_SWAP\nmm/Kconfig-814-\tdef_bool n\n--\nmm/Kconfig=969=endchoice\nmm/Kconfig-970-\nmm/Kconfig:971:config THP_SWAP\nmm/Kconfig-972-\tdef_bool y\nmm/Kconfig:973:\tdepends on ARCH_WANTS_THP_SWAP \u0026\u0026 SWAP \u0026\u0026 64BIT\nmm/Kconfig-974-\thelp\n"
}

33/2 2026/08/20 19:04 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22236
							output: 150
							thoughts: 293
						

Thoughts:
**My Analysis of THP_SWAP Dependency and Code Changes**

Okay, so `THP_SWAP` hinges on `ARCH_WANTS_THP_SWAP`, `SWAP`, and a 64-bit architecture. That's the gate. Now, looking at the code, the modifications are concentrated in `mm/huge_memory.c` and `mm/swap_state.c`. Right away, I see that `mm/huge_memory.c` is going to be part of the build *only* if `CONFIG_TRANSPARENT_HUGEPAGE` is enabled, and `mm/swap_state.c` relies on `CONFIG_SWAP`. So, `TRANSPARENT_HUGEPAGE` and `SWAP` are the key configurations to keep in mind. Those are the big knobs here.

Now, let me go back over the changes in `mm/huge_memory.c`. I need to understand *exactly* what's being touched. Ah, they've modified `__split_unmapped_folio` (renamed to `__split_frozen_folio`), `folio_check_splittable`, `__folio_freeze_and_split_unmapped` (replaced by `__folio_split_unmap_and_freeze` and `__folio_split_unmap_and_freeze_file`), `__folio_split`, `folio_split_unmapped`, and `__split_huge_page_to_list_to_order`. It looks like it is all about refactoring the folio splitting logic to handle swapcache and anonymous folios more effectively. This is a fundamental change in the way THP interacts with swap. This is definitely a spot worth a deep fuzzing campaign.

Alright, I'm ready to move forward. Let's call `set-results`.



34/2 2026/08/20 19:04 tool set-results
0m Args:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "SWAP"
  ],
  "FocusSymbols": [
    "__split_huge_page_to_list_to_order",
    "folio_split_unmapped",
    "folio_check_splittable",
    "__swap_cache_replace_folio"
  ],
  "Reasoning": "The patch refactors and fixes the logic for splitting folios, specifically handling anonymous, swapcache, and file-backed folios. It modifies core memory management code in mm/huge_memory.c and mm/swap_state.c, which is reachable and has functional impact.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "TRANSPARENT_HUGEPAGE",
    "SWAP"
  ],
  "FocusSymbols": [
    "__split_huge_page_to_list_to_order",
    "folio_split_unmapped",
    "folio_check_splittable",
    "__swap_cache_replace_folio"
  ],
  "Reasoning": "The patch refactors and fixes the logic for splitting folios, specifically handling anonymous, swapcache, and file-backed folios. It modifies core memory management code in mm/huge_memory.c and mm/swap_state.c, which is reachable and has functional impact.",
  "WorthFuzzing": true
}

35/1 2026/08/20 19:04 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

36/2 2026/08/20 19:04 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch refactors the folio splitting logic in the memory management subsystem (specifically for anonymous, swapcache, and file-backed folios). It modifies how folios are unmapped, frozen, and split, updating page cache and swap cache references. These changes involve page reference counting, locking, and xarray manipulations. They do not introduce new data structures sent to user space, nor do they alter the initialization of memory buffers. Any bugs introduced by these changes would manifest as use-after-free, memory corruption, or locking issues, which are effectively detected by KASAN and LOCKDEP. There is no risk of uninitialized memory reads or info-leaks that would require KMSAN.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
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 6dae3e78392768052a1426adbd7b39c7a2a217d2
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Aug 20 19:02:36 2026 +0000

    syz-cluster: applied patch under review

diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index ced400f72d43a..cc9f7e0d4194e 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -3589,9 +3589,6 @@ static void remap_page(struct folio *folio, unsigned long nr, int flags)
 {
 	int i = 0;
 
-	/* If unmap_folio() uses try_to_migrate() on file, remove this check */
-	if (!folio_test_anon(folio))
-		return;
 	for (;;) {
 		remove_migration_ptes(folio, folio, TTU_RMAP_LOCKED | flags);
 		i += folio_nr_pages(folio);
@@ -3755,8 +3752,8 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
 }
 
 /**
- * __split_unmapped_folio() - splits an unmapped @folio to lower order folios in
- * two ways: uniform split or non-uniform split.
+ * __split_frozen_folio() - splits a frozen @folio to lower order folios
+ * in two ways: uniform split or non-uniform split.
  * @folio: the to-be-split folio
  * @new_order: the smallest order of the after split folios (since buddy
  *             allocator like split generates folios with orders from @folio's
@@ -3764,7 +3761,6 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
  * @split_at: in buddy allocator like split, the folio containing @split_at
  *            will be split until its order becomes @new_order.
  * @xas: xa_state pointing to folio->mapping->i_pages and locked by caller
- * @mapping: @folio->mapping
  * @split_type: if the split is uniform or not (buddy allocator like split)
  *
  *
@@ -3795,11 +3791,12 @@ static void __split_folio_to_order(struct folio *folio, int old_order,
  * Return: 0 - successful, <0 - failed (if -ENOMEM is returned, @folio might be
  * split but not to @new_order, the caller needs to check)
  */
-static int __split_unmapped_folio(struct folio *folio, int new_order,
+static int __split_frozen_folio(struct folio *folio, int new_order,
 		struct page *split_at, struct xa_state *xas,
-		struct address_space *mapping, enum split_type split_type)
+		enum split_type split_type)
 {
 	const bool is_anon = folio_test_anon(folio);
+	const bool is_swapcache = folio_test_swapcache(folio);
 	int old_order = folio_order(folio);
 	int start_order = split_type == SPLIT_TYPE_UNIFORM ? new_order : old_order - 1;
 	struct folio *old_folio = folio;
@@ -3814,11 +3811,11 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 	     split_order--) {
 		int nr_new_folios = 1UL << (old_order - split_order);
 
-		/* order-1 anonymous folio is not supported */
-		if (is_anon && split_order == 1)
+		/* order-1 anonymous or swapcache folio is not supported */
+		if ((is_anon || is_swapcache) && split_order == 1)
 			continue;
 
-		if (mapping) {
+		if (xas) {
 			/*
 			 * uniform split has xas_split_alloc() called before
 			 * irq is disabled to allocate enough memory, whereas
@@ -3878,32 +3875,26 @@ static int __split_unmapped_folio(struct folio *folio, int new_order,
 int folio_check_splittable(struct folio *folio, unsigned int new_order,
 			   enum split_type split_type)
 {
+	bool is_anon = folio_test_anon(folio);
+	bool is_swapcache = folio_test_swapcache(folio);
+
 	VM_WARN_ON_FOLIO(!folio_test_locked(folio), folio);
 	/*
 	 * Folios that just got truncated cannot get split. Signal to the
-	 * caller that there was a race.
-	 *
-	 * TODO: this will also currently refuse folios without a mapping in the
-	 * swapcache (shmem or to-be-anon folios).
+	 * caller that there was a race. A mappingless swap cache folio
+	 * has no page cache entries to update, so it is fine to split.
 	 */
-	if (!folio->mapping && !folio_test_anon(folio))
+	if (!folio->mapping && !is_swapcache)
 		return -EBUSY;
 
-	/* order-1 is not supported for anonymous THP. */
-	if (folio_test_anon(folio) && new_order == 1)
-		return -EINVAL;
-
 	/*
-	 * swapcache folio could only be split to order 0
-	 *
-	 * non-uniform split creates after-split folios with orders from
-	 * folio_order(folio) - 1 to new_order, making it not suitable for any
-	 * swapcache folio split. Only uniform split to order-0 can be used
-	 * here.
+	 * Order-1 is unsupported: anon folios need subpage 2 for the
+	 * deferred split list, hybrid shmem & swap cache folios are not
+	 * splittable, and a splittable mappingless swap cache folio could
+	 * be either anon or shmem, which we cannot tell apart.
 	 */
-	if ((split_type == SPLIT_TYPE_NON_UNIFORM || new_order) && folio_test_swapcache(folio)) {
+	if ((is_anon || is_swapcache) && new_order == 1)
 		return -EINVAL;
-	}
 
 	if (is_huge_zero_folio(folio))
 		return -EINVAL;
@@ -3911,39 +3902,97 @@ int folio_check_splittable(struct folio *folio, unsigned int new_order,
 	if (folio_test_writeback(folio))
 		return -EBUSY;
 
+	/*
+	 * A non-anon swapcache folio that still has a mapping can only be a
+	 * shmem folio under SWAP IO, it's removed from either swap cache or
+	 * shmem mapping afterward. There is little benefit in splitting them
+	 * hence reject it here up front before touching anything.
+	 */
+	if (!is_anon && is_swapcache && folio->mapping)
+		return -EBUSY;
+
 	return 0;
 }
 
-/* Number of folio references from the pagecache or the swapcache. */
-static unsigned int folio_cache_ref_count(const struct folio *folio)
+/* Number of folio references from the swapcache. */
+static unsigned int folio_swapcache_ref_count(const struct folio *folio)
 {
-	if (folio_test_anon(folio) && !folio_test_swapcache(folio))
+	if (!folio_test_swapcache(folio))
 		return 0;
 	return folio_nr_pages(folio);
 }
 
-static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int new_order,
-					     struct page *split_at, struct xa_state *xas,
-					     struct address_space *mapping, bool do_lru,
-					     struct list_head *list, enum split_type split_type,
-					     pgoff_t end, int *nr_shmem_dropped)
+/**
+ * __folio_split_unmap_and_freeze() - split an anon or swap cache folio
+ * @folio: folio to split, must be locked
+ * @new_order: the order of the after-split folios (uniform split), or the
+ *             smallest order of the after-split folios (non-uniform split)
+ * @split_at: in non-uniform split, the folio containing @split_at is split
+ *            until its order becomes @new_order
+ * @do_lru: if true, add after-split folios to @list if non NULL, otherwise to
+ *          the LRU list
+ * @anon_unmap: if true, unmap @folio before the split and remap it after
+ * @list: after-split folios will be put on it if non NULL
+ * @split_type: perform uniform split or not (non-uniform split)
+ *
+ * Helper for splitting an anon or swap cache folio. It unmaps @folio (unless
+ * @anon_unmap is false), freezes its refcount, and performs the split, updates
+ * the swap cache entries. Split folios are unfrozen and remapped.
+ *
+ * Return: 0 on success, otherwise an error number is returned.
+ */
+static int __folio_split_unmap_and_freeze(struct folio *folio, unsigned int new_order,
+					  struct page *split_at, bool do_lru, bool anon_unmap,
+					  struct list_head *list, enum split_type split_type)
 {
 	struct folio *end_folio = folio_next(folio);
+	bool is_anon = folio_test_anon(folio);
+	struct swap_cluster_info *ci = NULL;
 	struct folio *new_folio, *next;
 	int old_order = folio_order(folio);
+	struct anon_vma *anon_vma = NULL;
+	enum ttu_flags ttu_flags = 0;
 	struct list_lru_one *lru;
+	struct lruvec *lruvec;
 	bool dequeue_deferred;
 	int ret = 0;
 
-	VM_WARN_ON_ONCE(!mapping && end);
+	/*
+	 * Unmap/remap needs the anon_vma. The caller does not necessarily
+	 * hold an mmap_lock that would prevent the anon_vma from
+	 * disappearing, so we first take a reference and lock it. This is
+	 * similar to folio_lock_anon_vma_read() except the write lock is
+	 * taken to serialize against parallel split or collapse.
+	 */
+	if (anon_unmap) {
+		anon_vma = folio_get_anon_vma(folio);
+		if (!anon_vma)
+			return -EBUSY;
+		anon_vma_lock_write(anon_vma);
+	}
+
+	/* Racy check if we can split the page, before the optional unmap. */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto out_unlock;
+	}
+
+	if (anon_unmap)
+		unmap_folio(folio);
+
+	local_irq_disable();
+
 	/*
 	 * If this folio can be on the deferred split queue, lock out
 	 * the shrinker before freezing the ref. If the shrinker sees
 	 * a 0-ref folio, it assumes it beat folio_put() to the list
 	 * lock and must clean up the LRU state - the same dequeue we
 	 * will do below as part of the split.
+	 *
+	 * Only anon folios are ever queued on the deferred split list,
+	 * so non-anon folios (mappingless swapcache) never need dequeuing.
 	 */
-	dequeue_deferred = folio_test_anon(folio) && old_order > 1;
+	dequeue_deferred = old_order > 1 && is_anon;
 	if (dequeue_deferred) {
 		struct mem_cgroup *memcg;
 
@@ -3952,128 +4001,256 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
 		lru = list_lru_lock(&deferred_split_lru,
 				    folio_nid(folio), &memcg);
 	}
-	if (folio_ref_freeze(folio, folio_cache_ref_count(folio) + 1)) {
-		struct swap_cluster_info *ci = NULL;
-		struct lruvec *lruvec;
 
+	if (!folio_ref_freeze(folio, folio_swapcache_ref_count(folio) + 1)) {
 		if (dequeue_deferred) {
-			__list_lru_del(&deferred_split_lru, lru,
-				       &folio->_deferred_list, folio_nid(folio));
-			if (folio_test_partially_mapped(folio)) {
-				folio_clear_partially_mapped(folio);
-				mod_mthp_stat(old_order,
-					MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
-			}
 			list_lru_unlock(lru);
 			rcu_read_unlock();
 		}
+		ret = -EAGAIN;
+		goto out_no_split;
+	}
 
-		if (mapping) {
-			int nr = folio_nr_pages(folio);
-
-			if (folio_test_pmd_mappable(folio) &&
-			    new_order < HPAGE_PMD_ORDER) {
-				if (folio_test_swapbacked(folio)) {
-					lruvec_stat_mod_folio(folio,
-							NR_SHMEM_THPS, -nr);
-				} else {
-					lruvec_stat_mod_folio(folio,
-							NR_FILE_THPS, -nr);
-				}
-			}
+	if (dequeue_deferred) {
+		__list_lru_del(&deferred_split_lru, lru,
+			       &folio->_deferred_list, folio_nid(folio));
+		if (folio_test_partially_mapped(folio)) {
+			folio_clear_partially_mapped(folio);
+			mod_mthp_stat(old_order,
+				      MTHP_STAT_NR_ANON_PARTIALLY_MAPPED, -1);
 		}
+		list_lru_unlock(lru);
+		rcu_read_unlock();
+	}
 
-		if (folio_test_swapcache(folio)) {
-			if (mapping) {
-				VM_WARN_ON_ONCE_FOLIO(mapping, folio);
-				return -EINVAL;
-			}
+	if (folio_test_swapcache(folio))
+		ci = swap_cluster_get_and_lock(folio);
 
-			ci = swap_cluster_get_and_lock(folio);
-		}
+	if (do_lru)
+		lruvec = folio_lruvec_lock(folio);
 
-		/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	ret = __split_frozen_folio(folio, new_order, split_at, NULL, split_type);
+
+	/*
+	 * Unfreeze the post-split folios and put them back to the right
+	 * place. Keep the head @folio frozen until the end: sub entries
+	 * in swap cache must be updated first, so a concurrent
+	 * swap_cache_get_folio() cannot return the head folio for a sub
+	 * entry (folio_try_get() will fail on the head @folio until unfreeze).
+	 */
+	for (new_folio = folio_next(folio); new_folio != end_folio;
+	     new_folio = next) {
+		next = folio_next(new_folio);
+		zone_device_private_split_cb(folio, new_folio);
+		folio_ref_unfreeze(new_folio,
+				   folio_swapcache_ref_count(new_folio) + 1);
 		if (do_lru)
-			lruvec = folio_lruvec_lock(folio);
+			lru_add_split_folio(folio, new_folio, lruvec, list);
+		if (ci)
+			__swap_cache_replace_folio(ci, folio, new_folio);
+	}
 
-		ret = __split_unmapped_folio(folio, new_order, split_at, xas,
-					     mapping, split_type);
+	zone_device_private_split_cb(folio, NULL);
+	folio_ref_unfreeze(folio, folio_swapcache_ref_count(folio) + 1);
 
-		/*
-		 * Unfreeze after-split folios and put them back to the right
-		 * list. @folio should be kept frozon until page cache
-		 * entries are updated with all the other after-split folios
-		 * to prevent others seeing stale page cache entries.
-		 * As a result, new_folio starts from the next folio of
-		 * @folio.
-		 */
-		for (new_folio = folio_next(folio); new_folio != end_folio;
-		     new_folio = next) {
-			unsigned long nr_pages = folio_nr_pages(new_folio);
+	if (do_lru)
+		lruvec_unlock(lruvec);
+	if (ci)
+		swap_cluster_unlock(ci);
+out_no_split:
+	local_irq_enable();
+	if (anon_vma) {
+		if (!ret && !folio_is_device_private(folio))
+			ttu_flags = TTU_USE_SHARED_ZEROPAGE;
+		remap_page(folio, 1 << old_order, ttu_flags);
+	}
+out_unlock:
+	if (anon_vma) {
+		anon_vma_unlock_write(anon_vma);
+		put_anon_vma(anon_vma);
+	}
 
-			next = folio_next(new_folio);
+	return ret;
+}
 
-			zone_device_private_split_cb(folio, new_folio);
+/**
+ * __folio_split_unmap_and_freeze_file() - split a file-backed folio
+ * @folio: folio to split, must be locked and file-backed
+ * @new_order: the order of the after-split folios (uniform split), or the
+ *             smallest order of the after-split folios (non-uniform split)
+ * @split_at: in non-uniform split, the folio containing @split_at is split
+ *            until its order becomes @new_order
+ * @list: after-split folios will be put on it if non NULL
+ * @split_type: perform uniform split or not (non-uniform split)
+ *
+ * Helper for splitting a file-backed folio. It unmaps @folio, freezes its
+ * refcount, and perform the split, updates the page cache entries. Split
+ * folios are unfrozen but not remapped, they are faulted back in on demand.
+ *
+ * Return: 0 on success, otherwise an error number is returned. (if -ENOMEM
+ * is returned, @folio might be split but not to @new_order)
+ */
+static int __folio_split_unmap_and_freeze_file(struct folio *folio, unsigned int new_order,
+					       struct page *split_at, struct list_head *list,
+					       enum split_type split_type)
+{
+	struct address_space *mapping = folio->mapping;
+	XA_STATE(xas, &mapping->i_pages, folio->index);
+	struct folio *end_folio = folio_next(folio);
+	long old_nr_pages = folio_nr_pages(folio);
+	struct mem_cgroup *memcg, *old_memcg;
+	struct folio *new_folio, *next;
+	int nr_shmem_dropped = 0;
+	unsigned int min_order;
+	struct lruvec *lruvec;
+	pgoff_t end = 0;
+	gfp_t gfp;
+	int ret = 0;
 
-			folio_ref_unfreeze(new_folio,
-					   folio_cache_ref_count(new_folio) + 1);
+	min_order = mapping_min_folio_order(mapping);
+	if (new_order < min_order)
+		return -EINVAL;
 
-			if (do_lru)
-				lru_add_split_folio(folio, new_folio, lruvec, list);
+	/*
+	 * Switch to folio's memcg as xarray node allocation can happen and
+	 * needs to charge to it.
+	 */
+	memcg = get_mem_cgroup_from_folio(folio);
+	old_memcg = set_active_memcg(memcg);
 
-			/*
-			 * Anonymous folio with swap cache.
-			 * NOTE: shmem in swap cache is not supported yet.
-			 */
-			if (ci) {
-				__swap_cache_replace_folio(ci, folio, new_folio);
-				continue;
-			}
+	gfp = current_gfp_context(mapping_gfp_mask(mapping) & GFP_RECLAIM_MASK);
+	if (!filemap_release_folio(folio, gfp)) {
+		ret = -EBUSY;
+		goto fail_free;
+	}
 
-			/* Anonymous folio without swap cache */
-			if (!mapping)
-				continue;
+	mapping_set_update(&xas, mapping);
 
-			/* Add the new folio to the page cache. */
-			if (new_folio->index < end) {
-				__xa_store(&mapping->i_pages, new_folio->index,
-					   new_folio, 0);
-				continue;
-			}
+	if (split_type == SPLIT_TYPE_UNIFORM) {
+		int old_order = folio_order(folio);
 
-			VM_WARN_ON_ONCE(!nr_shmem_dropped);
-			/* Drop folio beyond EOF: ->index >= end */
-			if (shmem_mapping(mapping) && nr_shmem_dropped)
-				*nr_shmem_dropped += nr_pages;
-			else if (folio_test_clear_dirty(new_folio))
-				folio_account_cleaned(
-					new_folio, inode_to_wb(mapping->host));
-			__filemap_remove_folio(new_folio, NULL);
-			folio_put_refs(new_folio, nr_pages);
+		xas_set_order(&xas, folio->index, new_order);
+		xas_split_alloc(&xas, folio, old_order, gfp);
+		if (xas_error(&xas)) {
+			ret = xas_error(&xas);
+			goto fail_free;
 		}
+	}
 
-		zone_device_private_split_cb(folio, NULL);
-		/*
-		 * Unfreeze @folio only after all page cache entries, which
-		 * used to point to it, have been updated with new folios.
-		 * Otherwise, a parallel folio_try_get() can grab @folio
-		 * and its caller can see stale page cache entries.
-		 */
-		folio_ref_unfreeze(folio, folio_cache_ref_count(folio) + 1);
+	i_mmap_lock_read(mapping);
 
-		if (do_lru)
-			lruvec_unlock(lruvec);
+	/* Racy check if we can split the page, before unmap_folio() */
+	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
+		ret = -EAGAIN;
+		goto fail_mmap_unlock;
+	}
 
-		if (ci)
-			swap_cluster_unlock(ci);
-	} else {
-		if (dequeue_deferred) {
-			list_lru_unlock(lru);
-			rcu_read_unlock();
+	/*
+	 * __split_frozen_folio() may need to trim off pages beyond
+	 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
+	 * seqlock, which cannot be nested inside the page tree lock.
+	 * So note end now: i_size itself may be changed at any moment,
+	 * but folio lock is good enough to serialize the trimming.
+	 */
+	end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
+	if (shmem_mapping(mapping))
+		end = shmem_fallocend(mapping->host, end);
+
+	unmap_folio(folio);
+
+	xas_lock_irq(&xas);
+
+	/*
+	 * Check if the folio is present in page cache.
+	 * We assume all tail are present too, if folio is there.
+	 */
+	if (xas_load(&xas) != folio) {
+		ret = -EAGAIN;
+		goto fail;
+	}
+
+	if (!folio_ref_freeze(folio, old_nr_pages + 1)) {
+		ret = -EAGAIN;
+		goto fail;
+	}
+
+	if (folio_test_pmd_mappable(folio) && new_order < HPAGE_PMD_ORDER) {
+		if (folio_test_swapbacked(folio))
+			lruvec_stat_mod_folio(folio, NR_SHMEM_THPS, -old_nr_pages);
+		else
+			lruvec_stat_mod_folio(folio, NR_FILE_THPS, -old_nr_pages);
+	}
+
+	/* lock lru list/PageCompound, ref frozen by page_ref_freeze */
+	lruvec = folio_lruvec_lock(folio);
+	ret = __split_frozen_folio(folio, new_order, split_at, &xas, split_type);
+
+	/*
+	 * Unfreeze after-split folios and put them back to the right
+	 * list. @folio should be kept frozen until page cache
+	 * entries are updated with all the other after-split folios
+	 * to prevent others seeing stale page cache entries.
+	 * As a result, new_folio starts from the next folio of
+	 * @folio.
+	 */
+	for (new_folio = folio_next(folio); new_folio != end_folio;
+	     new_folio = next) {
+		unsigned long nr_pages = folio_nr_pages(new_folio);
+
+		/* compute next before the folio can be freed below */
+		next = folio_next(new_folio);
+
+		folio_ref_unfreeze(new_folio,
+				   folio_nr_pages(new_folio) + 1);
+
+		lru_add_split_folio(folio, new_folio, lruvec, list);
+
+		/* Add the new folio to the page cache. */
+		if (new_folio->index < end) {
+			__xa_store(&mapping->i_pages, new_folio->index,
+				   new_folio, 0);
+			continue;
 		}
-		return -EAGAIN;
+
+		/* Drop folio beyond EOF: ->index >= end */
+		if (shmem_mapping(mapping))
+			nr_shmem_dropped += nr_pages;
+		else if (folio_test_clear_dirty(new_folio))
+			folio_account_cleaned(new_folio,
+					      inode_to_wb(mapping->host));
+		__filemap_remove_folio(new_folio, NULL);
+		folio_put_refs(new_folio, nr_pages);
 	}
 
+	/*
+	 * Unfreeze @folio only after all page cache entries, which
+	 * used to point to it, have been updated with new folios.
+	 * Otherwise, a parallel folio_try_get() can grab @folio
+	 * and its caller can see stale page cache entries.
+	 */
+	folio_ref_unfreeze(folio, folio_nr_pages(folio) + 1);
+	lruvec_unlock(lruvec);
+fail:
+	/*
+	 * If we want to use try_to_migrate() on file in unmap_folio,
+	 * remember to add remap_page() and adapt it.
+	 */
+	xas_unlock_irq(&xas);
+fail_mmap_unlock:
+	if (nr_shmem_dropped)
+		shmem_uncharge(mapping->host, nr_shmem_dropped);
+	/*
+	 * Drop the mapping while the inode is still pinned. @folio stays
+	 * locked and present in the page cache, so eviction cannot free
+	 * the inode yet, nothing past this point may touch the inode or
+	 * the mapping.
+	 */
+	i_mmap_unlock_read(mapping);
+fail_free:
+	/* Restore the previously active memcg */
+	set_active_memcg(old_memcg);
+	mem_cgroup_put(memcg);
+	xas_destroy(&xas);
 	return ret;
 }
 
@@ -4086,9 +4263,9 @@ static int __folio_freeze_and_split_unmapped(struct folio *folio, unsigned int n
  * @list: after-split folios will be put on it if non NULL
  * @split_type: perform uniform split or not (non-uniform split)
  *
- * It calls __split_unmapped_folio() to perform uniform and non-uniform split.
+ * It calls __split_frozen_folio() to perform uniform and non-uniform split.
  * It is in charge of checking whether the split is supported or not and
- * preparing @folio for __split_unmapped_folio().
+ * preparing @folio for __split_frozen_folio().
  *
  * After splitting, the after-split folio containing @lock_at remains locked
  * and others are unlocked:
@@ -4102,17 +4279,11 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		struct page *split_at, struct page *lock_at,
 		struct list_head *list, enum split_type split_type)
 {
-	XA_STATE(xas, &folio->mapping->i_pages, folio->index);
+	bool is_swapcache = folio_test_swapcache(folio);
 	struct folio *end_folio = folio_next(folio);
 	bool is_anon = folio_test_anon(folio);
-	struct mem_cgroup *memcg, *old_memcg;
-	struct address_space *mapping = NULL;
-	struct anon_vma *anon_vma = NULL;
 	int old_order = folio_order(folio);
 	struct folio *new_folio, *next;
-	int nr_shmem_dropped = 0;
-	enum ttu_flags ttu_flags = 0;
-	pgoff_t end = 0;
 	int ret;
 
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
@@ -4120,141 +4291,29 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 
 	if (folio != page_folio(split_at) || folio != page_folio(lock_at)) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	if (new_order >= old_order) {
 		ret = -EINVAL;
-		goto out_no_memcg;
+		goto out;
 	}
 
 	ret = folio_check_splittable(folio, new_order, split_type);
 	if (ret) {
 		VM_WARN_ONCE(ret == -EINVAL, "Tried to split an unsplittable folio");
-		goto out_no_memcg;
-	}
-
-	/*
-	 * switch to folio's memcg as xarray node allocation can happen and
-	 * needs to charge to it.
-	 */
-	memcg = get_mem_cgroup_from_folio(folio);
-	old_memcg = set_active_memcg(memcg);
-
-	if (is_anon) {
-		/*
-		 * The caller does not necessarily hold an mmap_lock that would
-		 * prevent the anon_vma disappearing so we first we take a
-		 * reference to it and then lock the anon_vma for write. This
-		 * is similar to folio_lock_anon_vma_read except the write lock
-		 * is taken to serialise against parallel split or collapse
-		 * operations.
-		 */
-		anon_vma = folio_get_anon_vma(folio);
-		if (!anon_vma) {
-			ret = -EBUSY;
-			goto out;
-		}
-		anon_vma_lock_write(anon_vma);
-		mapping = NULL;
-	} else {
-		unsigned int min_order;
-		gfp_t gfp;
-
-		mapping = folio->mapping;
-		min_order = mapping_min_folio_order(mapping);
-		if (new_order < min_order) {
-			ret = -EINVAL;
-			goto out;
-		}
-
-		gfp = current_gfp_context(mapping_gfp_mask(mapping) &
-							GFP_RECLAIM_MASK);
-
-		if (!filemap_release_folio(folio, gfp)) {
-			ret = -EBUSY;
-			goto out;
-		}
-
-		mapping_set_update(&xas, mapping);
-
-		if (split_type == SPLIT_TYPE_UNIFORM) {
-			xas_set_order(&xas, folio->index, new_order);
-			xas_split_alloc(&xas, folio, old_order, gfp);
-			if (xas_error(&xas)) {
-				ret = xas_error(&xas);
-				goto out;
-			}
-		}
-
-		anon_vma = NULL;
-		i_mmap_lock_read(mapping);
-
-		/*
-		 *__split_unmapped_folio() may need to trim off pages beyond
-		 * EOF: but on 32-bit, i_size_read() takes an irq-unsafe
-		 * seqlock, which cannot be nested inside the page tree lock.
-		 * So note end now: i_size itself may be changed at any moment,
-		 * but folio lock is good enough to serialize the trimming.
-		 */
-		end = DIV_ROUND_UP(i_size_read(mapping->host), PAGE_SIZE);
-		if (shmem_mapping(mapping))
-			end = shmem_fallocend(mapping->host, end);
-	}
-
-	/*
-	 * Racy check if we can split the page, before unmap_folio() will
-	 * split PMDs
-	 */
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1) {
-		ret = -EAGAIN;
-		goto out_unlock;
-	}
-
-	unmap_folio(folio);
-
-	/* block interrupt reentry in xa_lock and spinlock */
-	local_irq_disable();
-	if (mapping) {
-		/*
-		 * Check if the folio is present in page cache.
-		 * We assume all tail are present too, if folio is there.
-		 */
-		xas_lock(&xas);
-		xas_reset(&xas);
-		if (xas_load(&xas) != folio) {
-			ret = -EAGAIN;
-			goto fail;
-		}
+		goto out;
 	}
 
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, split_at, &xas, mapping,
-						true, list, split_type, end, &nr_shmem_dropped);
-fail:
-	if (mapping)
-		xas_unlock(&xas);
-
-	local_irq_enable();
-
-	if (nr_shmem_dropped)
-		shmem_uncharge(mapping->host, nr_shmem_dropped);
-
-	if (!ret && is_anon && !folio_is_device_private(folio))
-		ttu_flags = TTU_USE_SHARED_ZEROPAGE;
-
-	remap_page(folio, 1 << old_order, ttu_flags);
-
-	/*
-	 * Drop the mapping while the inode is still pinned. @folio stays
-	 * locked and present in the page cache until the loop below, so
-	 * eviction cannot free the inode yet; @lock_at is not enough, it may
-	 * be a tail beyond EOF that the split already dropped from the page
-	 * cache. Nothing past this point may touch the inode or the mapping.
-	 */
-	if (mapping) {
-		i_mmap_unlock_read(mapping);
-		mapping = NULL;
-	}
+	if (is_anon)
+		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
+						     true, list, split_type);
+	else if (is_swapcache)
+		ret = __folio_split_unmap_and_freeze(folio, new_order, split_at, true,
+						     false, list, split_type);
+	else
+		ret = __folio_split_unmap_and_freeze_file(folio, new_order, split_at,
+							  list, split_type);
 
 	/*
 	 * Unlock all after-split folios except the one containing
@@ -4265,29 +4324,19 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
 		if (new_folio == page_folio(lock_at))
 			continue;
 
-		folio_unlock(new_folio);
 		/*
 		 * Subpages whose mapping has been zapped may be freed
 		 * earlier, but freeing them requires taking the
-		 * lru_lock, so we defer put_page() on tail pages until
+		 * lru_lock, so we defer folio_put() on tail pages until
 		 * after the split completes.
 		 */
-		free_folio_and_swap_cache(new_folio);
+		if (is_swapcache && !folio_mapped(new_folio))
+			folio_free_swap(new_folio);
+		folio_unlock(new_folio);
+		folio_put(new_folio);
 	}
 
-out_unlock:
-	if (anon_vma) {
-		anon_vma_unlock_write(anon_vma);
-		put_anon_vma(anon_vma);
-	}
-	if (mapping)
-		i_mmap_unlock_read(mapping);
 out:
-	/* restore to caller's old_memcg */
-	set_active_memcg(old_memcg);
-	mem_cgroup_put(memcg);
-out_no_memcg:
-	xas_destroy(&xas);
 	if (is_pmd_order(old_order))
 		count_vm_event(!ret ? THP_SPLIT_PAGE : THP_SPLIT_PAGE_FAILED);
 	count_mthp_stat(old_order, !ret ? MTHP_STAT_SPLIT : MTHP_STAT_SPLIT_FAILED);
@@ -4311,29 +4360,20 @@ static int __folio_split(struct folio *folio, unsigned int new_order,
  * isolated from LRU (if applicable)
  *
  * Upon return, the folio is not remapped, split folios are not added to LRU,
- * free_folio_and_swap_cache() is not called, and new folios remain locked.
+ * folio_free_swap() is not called, and new folios remain locked.
  *
  * Return: 0 on success, -EAGAIN if the folio cannot be split (e.g., due to
  *         insufficient reference count or extra pins).
  */
 int folio_split_unmapped(struct folio *folio, unsigned int new_order)
 {
-	int ret = 0;
-
 	VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_large(folio), folio);
 	VM_WARN_ON_ONCE_FOLIO(!folio_test_anon(folio), folio);
 
-	if (folio_expected_ref_count(folio) != folio_ref_count(folio) - 1)
-		return -EAGAIN;
-
-	local_irq_disable();
-	ret = __folio_freeze_and_split_unmapped(folio, new_order, &folio->page, NULL,
-						NULL, false, NULL, SPLIT_TYPE_UNIFORM,
-						0, NULL);
-	local_irq_enable();
-	return ret;
+	return __folio_split_unmap_and_freeze(folio, new_order, &folio->page, false,
+					      false, NULL, SPLIT_TYPE_UNIFORM);
 }
 
 /*
@@ -4352,10 +4392,9 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  *    GUP pins, will result in the folio not getting split; instead, the caller
  *    will receive an -EAGAIN.
  *
- * 4) @new_order > 1, usually. Splitting to order-1 anonymous folios is not
- *    supported for non-file-backed folios, because folio->_deferred_list, which
- *    is used by partially mapped folios, is stored in subpage 2, but an order-1
- *    folio only has subpages 0 and 1. File-backed order-1 folios are supported,
+ * 4) @new_order != 1 for anon or swapcache. Anon folios need subpage 2 for
+ *    _deferred_list, which order-1 folios lack, and a swapcache folio may
+ *    become anon once faulted in. File-backed order-1 folios are supported,
  *    since they do not use _deferred_list.
  *
  * After splitting, the caller's folio reference will be transferred to @page,
@@ -4384,7 +4423,7 @@ int folio_split_unmapped(struct folio *folio, unsigned int new_order)
  * with the folio. Splitting to order 0 is compatible with all folios.
  */
 int __split_huge_page_to_list_to_order(struct page *page, struct list_head *list,
-				     unsigned int new_order)
+				       unsigned int new_order)
 {
 	struct folio *folio = page_folio(page);
 
diff --git a/mm/swap_state.c b/mm/swap_state.c
index b76eb3d876fd7..59a577f685b5b 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -389,8 +389,9 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
 	    folio_order(old) != folio_order(new)) {
 		ci_off = swp_cluster_offset(old->swap);
 		ci_end = ci_off + folio_nr_pages(old);
-		while (ci_off++ < ci_end)
+		do {
 			WARN_ON_ONCE(swp_tb_to_folio(__swap_table_get(ci, ci_off)) != old);
+		} while (++ci_off < ci_end);
 	}
 }
 

37/3 2026/08/20 19:04 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 12876
							output: 166
							thoughts: 469
						

Thoughts:
**My Assessment of the Folio Splitting Patch**

Okay, so I've looked over this patch. It's touching `mm/huge_memory.c` and `mm/swap_state.c`, which immediately tells me we're dealing with the nitty-gritty of memory management, specifically how we're handling Transparent Huge Pages (THP) and their interaction with the swap cache and page cache. The core of the changes is a refactoring of the folio splitting logic. They're renaming functions, like `__split_unmapped_folio` to `__split_frozen_folio` and similar variations, but the core functionality seems the same: unmapping, freezing, and splitting these folios.

My initial scan tells me this is all purely internal memory management. No new data structures are being introduced that would get exposed to user space. It's all about how we manage page refcounts, deal with locking, and update those xarrays and LRU lists when splitting a folio.

Since it's memory management, the typical risks are the usual suspects: use-after-free, double frees, out-of-bounds accesses, and deadlocks. But, since there is no data copied to or from user space or uninitialized memory, KMSAN doesn't seem to be required for these changes. We're already protected by KASAN, KCSAN, and LOCKDEP for the usual memory corruption and locking problems.

I took a closer look to double-check my initial assessment. I see `xas_split_alloc` being used, but those are just allocating xarray nodes, not user-visible data. Nothing new is being exposed to userspace. Memory initialization doesn't seem to be a factor here; we're just splitting existing folios.

Therefore, *NeedsKMSAN* is definitely false. This patch is a good example of internal memory management changes that should *not* require KMSAN scrutiny.



38/3 2026/08/20 19:04 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch refactors the folio splitting logic in the memory management subsystem (specifically for anonymous, swapcache, and file-backed folios). It modifies how folios are unmapped, frozen, and split, updating page cache and swap cache references. These changes involve page reference counting, locking, and xarray manipulations. They do not introduce new data structures sent to user space, nor do they alter the initialization of memory buffers. Any bugs introduced by these changes would manifest as use-after-free, memory corruption, or locking issues, which are effectively detected by KASAN and LOCKDEP. There is no risk of uninitialized memory reads or info-leaks that would require KMSAN.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch refactors the folio splitting logic in the memory management subsystem (specifically for anonymous, swapcache, and file-backed folios). It modifies how folios are unmapped, frozen, and split, updating page cache and swap cache references. These changes involve page reference counting, locking, and xarray manipulations. They do not introduce new data structures sent to user space, nor do they alter the initialization of memory buffers. Any bugs introduced by these changes would manifest as use-after-free, memory corruption, or locking issues, which are effectively detected by KASAN and LOCKDEP. There is no risk of uninitialized memory reads or info-leaks that would require KMSAN.",
  "NeedsKMSAN": false
}

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)