| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/17 15:11 | flow | patch-triage |
2mArgs:null Results: {
"EnableConfigs": [
"SWAP",
"ZSWAP",
"ZSWAP_DEFAULT_ON",
"ZSWAP_SHRINKER_DEFAULT_ON"
],
"FocusSymbols": [
"zswap_folio_is_writeback_buffer",
"zswap_lookup_and_clear_shadows",
"swapin_sync",
"workingset_refault"
],
"KMSANReasoning": "The patch series modifies the swap and zswap subsystems to handle workingset shadows during zswap writeback. It parks the shadow in the zswap tree and restores it upon swap-in or swap cache deletion. These changes involve pointer manipulation, xarray operations, and folio state management. There are no new data structures allocated and copied to user space, nor any complex structures where uninitialized fields might be read. Any bugs introduced by these changes, such as incorrect pointer dereferences, use-after-free, or memory corruption, would be effectively detected by KASAN and other standard kernel debugging tools. There is no risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies the swap and zswap subsystems to properly handle workingset shadows for zswap writeback buffers. It introduces `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows` to manage parked shadows in the zswap tree during writeback and swap-in. These are core memory management changes that affect page reclaim, swap cache, and zswap, which are reachable and have functional impact.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/17 15:11 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 6c364abd5a072dace2951f5f0f1d1fcabcd8d3e7\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Aug 17 15:11:19 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/include/linux/zswap.h b/include/linux/zswap.h\nindex 30c193a1207e1..dfb7153236f5c 100644\n--- a/include/linux/zswap.h\n+++ b/include/linux/zswap.h\n@@ -35,6 +35,8 @@ void zswap_lruvec_state_init(struct lruvec *lruvec);\n void zswap_folio_swapin(struct folio *folio);\n bool zswap_is_enabled(void);\n bool zswap_never_enabled(void);\n+bool zswap_folio_is_writeback_buffer(struct folio *folio);\n+void *zswap_lookup_and_clear_shadows(struct folio *folio);\n #else\n \n struct zswap_lruvec_state {};\n@@ -69,6 +71,16 @@ static inline bool zswap_never_enabled(void)\n \treturn true;\n }\n \n+static inline bool zswap_folio_is_writeback_buffer(struct folio *folio)\n+{\n+\treturn false;\n+}\n+\n+static inline void *zswap_lookup_and_clear_shadows(struct folio *folio)\n+{\n+\treturn NULL;\n+}\n+\n #endif\n \n #endif /* _LINUX_ZSWAP_H */\ndiff --git a/mm/memory.c b/mm/memory.c\nindex 6b8280cfc1dbe..17e904662eb65 100644\n--- a/mm/memory.c\n+++ b/mm/memory.c\n@@ -4759,6 +4759,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)\n \tunsigned long page_idx;\n \tunsigned long address;\n \tpte_t *ptep;\n+\tvoid *shadow;\n \n \tif (!pte_unmap_same(vmf))\n \t\tgoto out;\n@@ -4880,6 +4881,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)\n \t\tgoto out_page;\n \t}\n \n+\tshadow = zswap_lookup_and_clear_shadows(folio);\n+\tif (shadow)\n+\t\tworkingset_refault(folio, shadow);\n+\n \t/*\n \t * KSM sometimes has to copy on read faults, for example, if\n \t * folio-\u003eindex of non-ksm folios would be nonlinear inside the\ndiff --git a/mm/shmem.c b/mm/shmem.c\nindex 9001aaf3b7b94..f968b68b60c52 100644\n--- a/mm/shmem.c\n+++ b/mm/shmem.c\n@@ -2250,6 +2250,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,\n \tstruct folio *folio = NULL;\n \tint error, nr_pages, order;\n \tpgoff_t offset;\n+\tvoid *shadow;\n \n \tVM_BUG_ON(!*foliop || !xa_is_value(*foliop));\n \tindex_entry = radix_to_swp_entry(*foliop);\n@@ -2361,6 +2362,10 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,\n \t */\n \tarch_swap_restore(folio_swap(swap, folio), folio);\n \n+\tshadow = zswap_lookup_and_clear_shadows(folio);\n+\tif (shadow)\n+\t\tworkingset_refault(folio, shadow);\n+\n \tif (shmem_should_replace_folio(folio, gfp)) {\n \t\terror = shmem_replace_folio(\u0026folio, gfp, info, index, vma);\n \t\tif (error)\ndiff --git a/mm/swap_state.c b/mm/swap_state.c\nindex 9c3a5cf997786..7ea4a2ba08416 100644\n--- a/mm/swap_state.c\n+++ b/mm/swap_state.c\n@@ -22,6 +22,7 @@\n #include \u003clinux/vmalloc.h\u003e\n #include \u003clinux/huge_mm.h\u003e\n #include \u003clinux/shmem_fs.h\u003e\n+#include \u003clinux/zswap.h\u003e\n #include \"internal.h\"\n #include \"swap_table.h\"\n #include \"swap.h\"\n@@ -257,12 +258,21 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,\n \tunsigned int ci_start, ci_off, ci_end;\n \tbool folio_swapped = false, need_free = false;\n \tunsigned long nr_pages = folio_nr_pages(folio);\n+\tvoid *shadow_parked;\n \n \tVM_WARN_ON_ONCE(__swap_entry_to_cluster(entry) != ci);\n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);\n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_swapcache(folio), folio);\n \tVM_WARN_ON_ONCE_FOLIO(folio_test_writeback(folio), folio);\n \n+\t/*\n+\t * A zswap writeback buffer parked the slot's original shadow in the\n+\t * zswap tree: restore it into the slot for later swap-in.\n+\t */\n+\tshadow_parked = zswap_lookup_and_clear_shadows(folio);\n+\tif (shadow_parked)\n+\t\tshadow = shadow_parked;\n+\n \tsi = __swap_entry_to_info(entry);\n \tci_start = swp_cluster_offset(entry);\n \tci_end = ci_start + nr_pages;\n@@ -477,8 +487,6 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,\n \n \t/* memsw uncharges swap when folio is added to swap cache */\n \tmemcg1_swapin(folio);\n-\tif (shadow)\n-\t\tworkingset_refault(folio, shadow);\n \n \tnode_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);\n \tlruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);\n@@ -638,17 +646,26 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,\n \t\t\t\t\t struct swap_iocb **plug, bool readahead)\n {\n \tstruct folio *folio;\n+\tvoid *shadow = NULL;\n \n \tdo {\n \t\tfolio = swap_cache_get_folio(entry);\n \t\tif (folio)\n \t\t\treturn folio;\n+\t\t/*\n+\t\t * Capture the slot's shadow before the allocation overwrites it,\n+\t\t * so a fresh swap-in can be evaluated as a refault below.\n+\t\t */\n+\t\tshadow = swap_cache_get_shadow(entry);\n \t\tfolio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);\n \t} while (PTR_ERR(folio) == -EEXIST);\n \n \tif (IS_ERR_OR_NULL(folio))\n \t\treturn NULL;\n \n+\tif (shadow)\n+\t\tworkingset_refault(folio, shadow);\n+\n \tswap_read_folio(folio, plug);\n \tif (readahead) {\n \t\tfolio_set_readahead(folio);\n@@ -678,17 +695,26 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,\n \t\t\t struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx)\n {\n \tstruct folio *folio;\n+\tvoid *shadow = NULL;\n \n \tdo {\n \t\tfolio = swap_cache_get_folio(entry);\n \t\tif (folio)\n \t\t\treturn folio;\n+\t\t/*\n+\t\t * Capture the slot's shadow before the allocation overwrites it,\n+\t\t * so a fresh swap-in can be evaluated as a refault below.\n+\t\t */\n+\t\tshadow = swap_cache_get_shadow(entry);\n \t\tfolio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);\n \t} while (PTR_ERR(folio) == -EEXIST);\n \n \tif (IS_ERR(folio))\n \t\treturn folio;\n \n+\tif (shadow)\n+\t\tworkingset_refault(folio, shadow);\n+\n \tswap_read_folio(folio, NULL);\n \treturn folio;\n }\ndiff --git a/mm/vmscan.c b/mm/vmscan.c\nindex 56708d1d2dfd5..c27e957322b82 100644\n--- a/mm/vmscan.c\n+++ b/mm/vmscan.c\n@@ -48,6 +48,7 @@\n #include \u003clinux/prefetch.h\u003e\n #include \u003clinux/printk.h\u003e\n #include \u003clinux/dax.h\u003e\n+#include \u003clinux/zswap.h\u003e\n #include \u003clinux/psi.h\u003e\n #include \u003clinux/pagewalk.h\u003e\n #include \u003clinux/shmem_fs.h\u003e\n@@ -730,7 +731,8 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,\n \tif (folio_test_swapcache(folio)) {\n \t\tswp_entry_t swap = folio-\u003eswap;\n \n-\t\tif (reclaimed \u0026\u0026 !mapping_exiting(mapping))\n+\t\tif (reclaimed \u0026\u0026 !mapping_exiting(mapping) \u0026\u0026\n+\t\t !zswap_folio_is_writeback_buffer(folio))\n \t\t\tshadow = workingset_eviction(folio, target_memcg);\n \t\t__memcg1_swapout(folio, ci);\n \t\t__swap_cache_del_folio(ci, folio, swap, shadow);\ndiff --git a/mm/workingset.c b/mm/workingset.c\nindex f351798e723ac..7c30cf091cbfb 100644\n--- a/mm/workingset.c\n+++ b/mm/workingset.c\n@@ -577,7 +577,10 @@ void workingset_refault(struct folio *folio, void *shadow)\n \tif (!workingset_test_recent(shadow, file, \u0026workingset, true))\n \t\tgoto out;\n \n-\tfolio_set_active(folio);\n+\tif (unlikely(folio_test_lru(folio)))\n+\t\tfolio_activate(folio);\n+\telse\n+\t\tfolio_set_active(folio);\n \tworkingset_age_nonresident(lruvec, nr);\n \tmod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + file, nr);\n \ndiff --git a/mm/zswap.c b/mm/zswap.c\nindex 761cd699e0a3e..f059206948aba 100644\n--- a/mm/zswap.c\n+++ b/mm/zswap.c\n@@ -972,6 +972,79 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)\n /*********************************\n * writeback code\n **********************************/\n+\n+#define ZSWAP_WRITEBACK_NO_SHADOW xa_mk_value(0)\n+\n+/*\n+ * zswap_folio_is_writeback_buffer - is @folio a zswap writeback buffer?\n+ * @folio: the folio being examined (typically a swap cache folio under reclaim)\n+ *\n+ * A folio is a zswap writeback buffer when every one of its swap offsets holds\n+ * a parked writeback shadow (a real shadow or the ZSWAP_WRITEBACK_NO_SHADOW\n+ * sentinel) in the zswap tree rather than a live zswap entry.\n+ *\n+ * Return: true if @folio is a writeback buffer, in which case the reclaim path\n+ * must not mint a fresh workingset shadow for it.\n+ */\n+bool zswap_folio_is_writeback_buffer(struct folio *folio)\n+{\n+\tswp_entry_t swp = folio-\u003eswap;\n+\tunsigned long nr_pages = folio_nr_pages(folio);\n+\tpgoff_t offset = swp_offset(swp);\n+\tunsigned long i;\n+\n+\tif (zswap_never_enabled())\n+\t\treturn false;\n+\n+\tfor (i = 0; i \u003c nr_pages; i++) {\n+\t\tswp_entry_t e = swp_entry(swp_type(swp), offset + i);\n+\n+\t\tif (!xa_is_value(xa_load(swap_zswap_tree(e), offset + i)))\n+\t\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+/*\n+ * zswap_lookup_and_clear_shadows - retrieve and clear @folio's parked shadow(s)\n+ * @folio: the writeback buffer folio (or the swapin folio that consumed it)\n+ *\n+ * Remove any parked writeback shadows for @folio's swap offset(s) from the\n+ * zswap tree.\n+ *\n+ * Return: the preserved workingset shadow, or NULL if the slot(s) had no shadow\n+ * (sentinel only) or nothing parked. The caller either restores the returned\n+ * shadow into the swap slot (buffer dropped) or feeds it to workingset_refault()\n+ * (buffer consumed by a swapin); clearing here ensures the two paths never\n+ * double-count.\n+ */\n+void *zswap_lookup_and_clear_shadows(struct folio *folio)\n+{\n+\tswp_entry_t swp = folio-\u003eswap;\n+\tunsigned long nr_pages = folio_nr_pages(folio);\n+\tpgoff_t offset = swp_offset(swp);\n+\tvoid *shadow = NULL;\n+\tunsigned long i;\n+\n+\tif (zswap_never_enabled())\n+\t\treturn NULL;\n+\n+\tfor (i = 0; i \u003c nr_pages; i++) {\n+\t\tswp_entry_t e = swp_entry(swp_type(swp), offset + i);\n+\t\tstruct xarray *tree = swap_zswap_tree(e);\n+\t\tvoid *parked = xa_load(tree, offset + i);\n+\n+\t\tif (!xa_is_value(parked))\n+\t\t\tcontinue;\n+\t\txa_erase(tree, offset + i);\n+\t\tif (parked != ZSWAP_WRITEBACK_NO_SHADOW)\n+\t\t\tshadow = parked;\n+\t}\n+\n+\treturn shadow;\n+}\n+\n /*\n * Attempts to free an entry by adding a folio to the swap cache,\n * decompressing the entry data into the folio, and issuing a\n@@ -992,6 +1065,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,\n \tstruct folio *folio;\n \tstruct mempolicy *mpol;\n \tstruct swap_info_struct *si;\n+\tvoid *shadow;\n \tint ret = 0;\n \n \t/* try to allocate swap cache folio */\n@@ -999,6 +1073,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,\n \tif (!si)\n \t\treturn -EEXIST;\n \n+\tshadow = swap_cache_get_shadow(swpentry);\n+\n \tmpol = get_task_policy(current);\n \tfolio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,\n \t\t\t\t NO_INTERLEAVE_INDEX);\n@@ -1034,7 +1110,9 @@ static int zswap_writeback_entry(struct zswap_entry *entry,\n \t\tgoto out;\n \t}\n \n-\txa_erase(tree, offset);\n+\tif (!shadow)\n+\t\tshadow = ZSWAP_WRITEBACK_NO_SHADOW;\n+\txa_store(tree, offset, shadow, GFP_KERNEL);\n \n \tcount_vm_event(ZSWPWB);\n \tif (entry-\u003eobjcg)\n"
} |
| 2/1 | 2026/08/17 15:11 | agent | patch-evaluator |
2mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"SWAP",
"ZSWAP",
"ZSWAP_DEFAULT_ON",
"ZSWAP_SHRINKER_DEFAULT_ON"
],
"FocusSymbols": [
"zswap_folio_is_writeback_buffer",
"zswap_lookup_and_clear_shadows",
"swapin_sync",
"workingset_refault"
],
"Reasoning": "The patch modifies the swap and zswap subsystems to properly handle workingset shadows for zswap writeback buffers. It introduces `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows` to manage parked shadows in the zswap tree during writeback and swap-in. These are core memory management changes that affect page reclaim, swap cache, and zswap, which are reachable and have 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 6c364abd5a072dace2951f5f0f1d1fcabcd8d3e7
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 17 15:11:19 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/zswap.h b/include/linux/zswap.h
index 30c193a1207e1..dfb7153236f5c 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -35,6 +35,8 @@ void zswap_lruvec_state_init(struct lruvec *lruvec);
void zswap_folio_swapin(struct folio *folio);
bool zswap_is_enabled(void);
bool zswap_never_enabled(void);
+bool zswap_folio_is_writeback_buffer(struct folio *folio);
+void *zswap_lookup_and_clear_shadows(struct folio *folio);
#else
struct zswap_lruvec_state {};
@@ -69,6 +71,16 @@ static inline bool zswap_never_enabled(void)
return true;
}
+static inline bool zswap_folio_is_writeback_buffer(struct folio *folio)
+{
+ return false;
+}
+
+static inline void *zswap_lookup_and_clear_shadows(struct folio *folio)
+{
+ return NULL;
+}
+
#endif
#endif /* _LINUX_ZSWAP_H */
diff --git a/mm/memory.c b/mm/memory.c
index 6b8280cfc1dbe..17e904662eb65 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4759,6 +4759,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
unsigned long page_idx;
unsigned long address;
pte_t *ptep;
+ void *shadow;
if (!pte_unmap_same(vmf))
goto out;
@@ -4880,6 +4881,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
goto out_page;
}
+ shadow = zswap_lookup_and_clear_shadows(folio);
+ if (shadow)
+ workingset_refault(folio, shadow);
+
/*
* KSM sometimes has to copy on read faults, for example, if
* folio->index of non-ksm folios would be nonlinear inside the
diff --git a/mm/shmem.c b/mm/shmem.c
index 9001aaf3b7b94..f968b68b60c52 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2250,6 +2250,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
struct folio *folio = NULL;
int error, nr_pages, order;
pgoff_t offset;
+ void *shadow;
VM_BUG_ON(!*foliop || !xa_is_value(*foliop));
index_entry = radix_to_swp_entry(*foliop);
@@ -2361,6 +2362,10 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
*/
arch_swap_restore(folio_swap(swap, folio), folio);
+ shadow = zswap_lookup_and_clear_shadows(folio);
+ if (shadow)
+ workingset_refault(folio, shadow);
+
if (shmem_should_replace_folio(folio, gfp)) {
error = shmem_replace_folio(&folio, gfp, info, index, vma);
if (error)
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 9c3a5cf997786..7ea4a2ba08416 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -22,6 +22,7 @@
#include <linux/vmalloc.h>
#include <linux/huge_mm.h>
#include <linux/shmem_fs.h>
+#include <linux/zswap.h>
#include "internal.h"
#include "swap_table.h"
#include "swap.h"
@@ -257,12 +258,21 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,
unsigned int ci_start, ci_off, ci_end;
bool folio_swapped = false, need_free = false;
unsigned long nr_pages = folio_nr_pages(folio);
+ void *shadow_parked;
VM_WARN_ON_ONCE(__swap_entry_to_cluster(entry) != ci);
VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
VM_WARN_ON_ONCE_FOLIO(!folio_test_swapcache(folio), folio);
VM_WARN_ON_ONCE_FOLIO(folio_test_writeback(folio), folio);
+ /*
+ * A zswap writeback buffer parked the slot's original shadow in the
+ * zswap tree: restore it into the slot for later swap-in.
+ */
+ shadow_parked = zswap_lookup_and_clear_shadows(folio);
+ if (shadow_parked)
+ shadow = shadow_parked;
+
si = __swap_entry_to_info(entry);
ci_start = swp_cluster_offset(entry);
ci_end = ci_start + nr_pages;
@@ -477,8 +487,6 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
/* memsw uncharges swap when folio is added to swap cache */
memcg1_swapin(folio);
- if (shadow)
- workingset_refault(folio, shadow);
node_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);
lruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);
@@ -638,17 +646,26 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
struct swap_iocb **plug, bool readahead)
{
struct folio *folio;
+ void *shadow = NULL;
do {
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
+ /*
+ * Capture the slot's shadow before the allocation overwrites it,
+ * so a fresh swap-in can be evaluated as a refault below.
+ */
+ shadow = swap_cache_get_shadow(entry);
folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR_OR_NULL(folio))
return NULL;
+ if (shadow)
+ workingset_refault(folio, shadow);
+
swap_read_folio(folio, plug);
if (readahead) {
folio_set_readahead(folio);
@@ -678,17 +695,26 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx)
{
struct folio *folio;
+ void *shadow = NULL;
do {
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
+ /*
+ * Capture the slot's shadow before the allocation overwrites it,
+ * so a fresh swap-in can be evaluated as a refault below.
+ */
+ shadow = swap_cache_get_shadow(entry);
folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR(folio))
return folio;
+ if (shadow)
+ workingset_refault(folio, shadow);
+
swap_read_folio(folio, NULL);
return folio;
}
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 56708d1d2dfd5..c27e957322b82 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -48,6 +48,7 @@
#include <linux/prefetch.h>
#include <linux/printk.h>
#include <linux/dax.h>
+#include <linux/zswap.h>
#include <linux/psi.h>
#include <linux/pagewalk.h>
#include <linux/shmem_fs.h>
@@ -730,7 +731,8 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
if (folio_test_swapcache(folio)) {
swp_entry_t swap = folio->swap;
- if (reclaimed && !mapping_exiting(mapping))
+ if (reclaimed && !mapping_exiting(mapping) &&
+ !zswap_folio_is_writeback_buffer(folio))
shadow = workingset_eviction(folio, target_memcg);
__memcg1_swapout(folio, ci);
__swap_cache_del_folio(ci, folio, swap, shadow);
diff --git a/mm/workingset.c b/mm/workingset.c
index f351798e723ac..7c30cf091cbfb 100644
--- a/mm/workingset.c
+++ b/mm/workingset.c
@@ -577,7 +577,10 @@ void workingset_refault(struct folio *folio, void *shadow)
if (!workingset_test_recent(shadow, file, &workingset, true))
goto out;
- folio_set_active(folio);
+ if (unlikely(folio_test_lru(folio)))
+ folio_activate(folio);
+ else
+ folio_set_active(folio);
workingset_age_nonresident(lruvec, nr);
mod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + file, nr);
diff --git a/mm/zswap.c b/mm/zswap.c
index 761cd699e0a3e..f059206948aba 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -972,6 +972,79 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
/*********************************
* writeback code
**********************************/
+
+#define ZSWAP_WRITEBACK_NO_SHADOW xa_mk_value(0)
+
+/*
+ * zswap_folio_is_writeback_buffer - is @folio a zswap writeback buffer?
+ * @folio: the folio being examined (typically a swap cache folio under reclaim)
+ *
+ * A folio is a zswap writeback buffer when every one of its swap offsets holds
+ * a parked writeback shadow (a real shadow or the ZSWAP_WRITEBACK_NO_SHADOW
+ * sentinel) in the zswap tree rather than a live zswap entry.
+ *
+ * Return: true if @folio is a writeback buffer, in which case the reclaim path
+ * must not mint a fresh workingset shadow for it.
+ */
+bool zswap_folio_is_writeback_buffer(struct folio *folio)
+{
+ swp_entry_t swp = folio->swap;
+ unsigned long nr_pages = folio_nr_pages(folio);
+ pgoff_t offset = swp_offset(swp);
+ unsigned long i;
+
+ if (zswap_never_enabled())
+ return false;
+
+ for (i = 0; i < nr_pages; i++) {
+ swp_entry_t e = swp_entry(swp_type(swp), offset + i);
+
+ if (!xa_is_value(xa_load(swap_zswap_tree(e), offset + i)))
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * zswap_lookup_and_clear_shadows - retrieve and clear @folio's parked shadow(s)
+ * @folio: the writeback buffer folio (or the swapin folio that consumed it)
+ *
+ * Remove any parked writeback shadows for @folio's swap offset(s) from the
+ * zswap tree.
+ *
+ * Return: the preserved workingset shadow, or NULL if the slot(s) had no shadow
+ * (sentinel only) or nothing parked. The caller either restores the returned
+ * shadow into the swap slot (buffer dropped) or feeds it to workingset_refault()
+ * (buffer consumed by a swapin); clearing here ensures the two paths never
+ * double-count.
+ */
+void *zswap_lookup_and_clear_shadows(struct folio *folio)
+{
+ swp_entry_t swp = folio->swap;
+ unsigned long nr_pages = folio_nr_pages(folio);
+ pgoff_t offset = swp_offset(swp);
+ void *shadow = NULL;
+ unsigned long i;
+
+ if (zswap_never_enabled())
+ return NULL;
+
+ for (i = 0; i < nr_pages; i++) {
+ swp_entry_t e = swp_entry(swp_type(swp), offset + i);
+ struct xarray *tree = swap_zswap_tree(e);
+ void *parked = xa_load(tree, offset + i);
+
+ if (!xa_is_value(parked))
+ continue;
+ xa_erase(tree, offset + i);
+ if (parked != ZSWAP_WRITEBACK_NO_SHADOW)
+ shadow = parked;
+ }
+
+ return shadow;
+}
+
/*
* Attempts to free an entry by adding a folio to the swap cache,
* decompressing the entry data into the folio, and issuing a
@@ -992,6 +1065,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
struct folio *folio;
struct mempolicy *mpol;
struct swap_info_struct *si;
+ void *shadow;
int ret = 0;
/* try to allocate swap cache folio */
@@ -999,6 +1073,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
if (!si)
return -EEXIST;
+ shadow = swap_cache_get_shadow(swpentry);
+
mpol = get_task_policy(current);
folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
NO_INTERLEAVE_INDEX);
@@ -1034,7 +1110,9 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
goto out;
}
- xa_erase(tree, offset);
+ if (!shadow)
+ shadow = ZSWAP_WRITEBACK_NO_SHADOW;
+ xa_store(tree, offset, shadow, GFP_KERNEL);
count_vm_event(ZSWPWB);
if (entry->objcg)
|
| 3/2 | 2026/08/17 15:11 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 5915 output: 26 thoughts: 245 Thoughts: **My Initial Assessment of the Memory Management Patch** Okay, so I've got a patch here that's poking around in some *very* sensitive areas: `mm/zswap.c`, `mm/workingset.c`, `mm/vmscan.c`, `mm/swap_state.c`, `mm/shmem.c`, `mm/memory.c`, and even the header for zswap, `include/linux/zswap.h`. That's a wide reach, immediately signaling core memory management modifications. Adding `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows` implies they're changing the lifecycle and tracking of these folios, and with modifications to `do_swap_page`, `shmem_swapin_folio`, `__swap_cache_do_del_folio`, `swap_cache_read_folio`, `swapin_sync`, `__remove_mapping`, `workingset_refault`, and `zswap_writeback_entry`, this clearly impacts swapping, the swap cache, and how the working set is handled. Given the scope, it's pretty much guaranteed to have functional implications, and I'd put this patch firmly in the "needs rigorous fuzzing" category. Let's see if we can find out which config this affects. Let me just quickly check that zswap is enabled by the kernel. I should look in `mm/Kconfig` or maybe `mm/Kconfig.zswap` to confirm that `CONFIG_ZSWAP` is defined. This is definitely going to be worth a close examination. |
| 4/2 | 2026/08/17 15:11 | tool | grepper |
0mArgs:{
"Expression": "config ZSWAP",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/Kconfig=12=menuconfig SWAP\n--\nmm/Kconfig-21-\nmm/Kconfig:22:config ZSWAP\nmm/Kconfig-23-\tbool \"Compressed cache for swap pages\"\n--\nmm/Kconfig-34-\nmm/Kconfig:35:config ZSWAP_DEFAULT_ON\nmm/Kconfig-36-\tbool \"Enable the compressed cache for swap pages by default\"\n--\nmm/Kconfig-44-\nmm/Kconfig:45:config ZSWAP_SHRINKER_DEFAULT_ON\nmm/Kconfig-46-\tbool \"Shrink the zswap pool on memory pressure\"\n--\nmm/Kconfig=59=choice\n--\nmm/Kconfig-76-\nmm/Kconfig:77:config ZSWAP_COMPRESSOR_DEFAULT_DEFLATE\nmm/Kconfig-78-\tbool \"Deflate\"\n--\nmm/Kconfig-82-\nmm/Kconfig:83:config ZSWAP_COMPRESSOR_DEFAULT_LZO\nmm/Kconfig-84-\tbool \"LZO\"\n--\nmm/Kconfig-88-\nmm/Kconfig:89:config ZSWAP_COMPRESSOR_DEFAULT_842\nmm/Kconfig-90-\tbool \"842\"\n--\nmm/Kconfig-94-\nmm/Kconfig:95:config ZSWAP_COMPRESSOR_DEFAULT_LZ4\nmm/Kconfig-96-\tbool \"LZ4\"\n--\nmm/Kconfig-100-\nmm/Kconfig:101:config ZSWAP_COMPRESSOR_DEFAULT_LZ4HC\nmm/Kconfig-102-\tbool \"LZ4HC\"\n--\nmm/Kconfig-106-\nmm/Kconfig:107:config ZSWAP_COMPRESSOR_DEFAULT_ZSTD\nmm/Kconfig-108-\tbool \"zstd\"\n--\nmm/Kconfig=112=endchoice\nmm/Kconfig-113-\nmm/Kconfig:114:config ZSWAP_COMPRESSOR_DEFAULT\nmm/Kconfig-115- string\n"
} |
| 5/2 | 2026/08/17 15:11 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 7078 output: 28 thoughts: 39 Thoughts: Okay, here's my take on those thoughts, framed from my perspective as an expert: **ZSWAP Optimization Strategy** Alright, I'm thinking we need to seriously consider enabling `ZSWAP` along with the `ZSWAP_DEFAULT_ON` directive. This seems like a promising optimization, particularly with the current workload characteristics. We know the system needs some swap space, so we'll need to configure standard `SWAP` as well. Before we commit, I'll need to thoroughly review the dependencies for `ZSWAP`. Understanding those dependencies is going to be critical to making sure this configuration doesn't introduce any unforeseen performance bottlenecks or compatibility issues. I need to know precisely what else this change impacts. |
| 6/2 | 2026/08/17 15:12 | tool | grepper |
0mArgs:{
"Expression": "config ZSWAP",
"PathPrefix": "mm/Kconfig"
}Results: {
"Output": "mm/Kconfig=12=menuconfig SWAP\n--\nmm/Kconfig-21-\nmm/Kconfig:22:config ZSWAP\nmm/Kconfig-23-\tbool \"Compressed cache for swap pages\"\n--\nmm/Kconfig-34-\nmm/Kconfig:35:config ZSWAP_DEFAULT_ON\nmm/Kconfig-36-\tbool \"Enable the compressed cache for swap pages by default\"\n--\nmm/Kconfig-44-\nmm/Kconfig:45:config ZSWAP_SHRINKER_DEFAULT_ON\nmm/Kconfig-46-\tbool \"Shrink the zswap pool on memory pressure\"\n--\nmm/Kconfig=59=choice\n--\nmm/Kconfig-76-\nmm/Kconfig:77:config ZSWAP_COMPRESSOR_DEFAULT_DEFLATE\nmm/Kconfig-78-\tbool \"Deflate\"\n--\nmm/Kconfig-82-\nmm/Kconfig:83:config ZSWAP_COMPRESSOR_DEFAULT_LZO\nmm/Kconfig-84-\tbool \"LZO\"\n--\nmm/Kconfig-88-\nmm/Kconfig:89:config ZSWAP_COMPRESSOR_DEFAULT_842\nmm/Kconfig-90-\tbool \"842\"\n--\nmm/Kconfig-94-\nmm/Kconfig:95:config ZSWAP_COMPRESSOR_DEFAULT_LZ4\nmm/Kconfig-96-\tbool \"LZ4\"\n--\nmm/Kconfig-100-\nmm/Kconfig:101:config ZSWAP_COMPRESSOR_DEFAULT_LZ4HC\nmm/Kconfig-102-\tbool \"LZ4HC\"\n--\nmm/Kconfig-106-\nmm/Kconfig:107:config ZSWAP_COMPRESSOR_DEFAULT_ZSTD\nmm/Kconfig-108-\tbool \"zstd\"\n--\nmm/Kconfig=112=endchoice\nmm/Kconfig-113-\nmm/Kconfig:114:config ZSWAP_COMPRESSOR_DEFAULT\nmm/Kconfig-115- string\n"
} |
| 7/2 | 2026/08/17 15:12 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 7837 output: 31 thoughts: 16 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Analyzing mm/Kconfig Line 22** Alright, let's dive into `mm/Kconfig` and zero in on line 22. My initial focus is likely driven by something specific I'm trying to understand, probably related to a memory management configuration option or dependency. I'm hoping line 22 holds the key to clarifying a particular macro, condition, or default setting that's tripping me up. I'm expecting to find a conditional statement (e.g., `if`, `select`, `depends on`) or a definition that directly impacts how a specific memory management feature behaves at compile time. I'll be looking for clues about the scope of the option, any relevant dependencies it has on other features, or how it might interact with the overall kernel configuration. More specifically, I'm probably looking at the dependencies of a specific feature, how its status is determined at compile-time, and whether or not there are any compile-time side-effects (e.g. including or excluding header files) based on its selection. Understanding the context of that line should provide some insight into the broader architecture. |
| 8/2 | 2026/08/17 15:12 | tool | read-file |
0mArgs:{
"File": "mm/Kconfig",
"FirstLine": 12,
"LineCount": 30
}Results: {
"Contents": " 12:\tmenuconfig SWAP\n 13:\t\tbool \"Support for paging of anonymous memory (swap)\"\n 14:\t\tdepends on MMU \u0026\u0026 BLOCK \u0026\u0026 !ARCH_NO_SWAP\n 15:\t\tdefault y\n 16:\t\thelp\n 17:\t\t This option allows you to choose whether you want to have support\n 18:\t\t for so called swap devices or swap files in your kernel that are\n 19:\t\t used to provide more virtual memory than the actual RAM present\n 20:\t\t in your computer. If unsure say Y.\n 21:\t\n 22:\tconfig ZSWAP\n 23:\t\tbool \"Compressed cache for swap pages\"\n 24:\t\tdepends on SWAP\n 25:\t\tselect CRYPTO\n 26:\t\tselect ZSMALLOC\n 27:\t\thelp\n 28:\t\t A lightweight compressed cache for swap pages. It takes\n 29:\t\t pages that are in the process of being swapped out and attempts to\n 30:\t\t compress them into a dynamically allocated RAM-based memory pool.\n 31:\t\t This can result in a significant I/O reduction on swap device and,\n 32:\t\t in the case where decompressing from RAM is faster than swap device\n 33:\t\t reads, can also improve workload performance.\n 34:\t\n 35:\tconfig ZSWAP_DEFAULT_ON\n 36:\t\tbool \"Enable the compressed cache for swap pages by default\"\n 37:\t\tdepends on ZSWAP\n 38:\t\thelp\n 39:\t\t If selected, the compressed cache for swap pages will be enabled\n 40:\t\t at boot, otherwise it will be disabled.\n 41:\t\n"
} |
| 9/2 | 2026/08/17 15:12 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 8565 output: 33 thoughts: 339 Thoughts: **My Analysis of ZSWAP Patch Focus** Okay, so this patch is clearly about `ZSWAP`, and it hinges on the existing `SWAP` functionality. Seems straightforward – we need to enable `ZSWAP`, `ZSWAP_DEFAULT_ON`, and of course, `SWAP` itself to get this working. Now, where are the critical points within this patch? Let's identify the focus symbols. The patch affects these functions: `zswap_folio_is_writeback_buffer`, `zswap_lookup_and_clear_shadows`, `do_swap_page`, `shmem_swapin_folio`, `__swap_cache_do_del_folio`, `swap_cache_read_folio`, `swapin_sync`, `__remove_mapping`, `workingset_refault`, and `zswap_writeback_entry`. `do_swap_page` is definitely a hot path; always a good place to start. `shmem_swapin_folio` is somewhat active too, and it's always useful for some focused fuzzing. But `zswap_writeback_entry` seems like a primary target for analysis. `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows` are newly introduced functions, so they should be scrutinized carefully. `swap_cache_read_folio` and `swapin_sync` are also prime candidates for investigation. Before I go further, I should verify the accessibility of `zswap_writeback_entry`. I need to confirm whether it's static or not. Let me see... yes, it's static within `mm/zswap.c`. Ah, but wait, the prompt reminds me that I should "Prefer non-static, non-inlined API entrypoint functions over internal static helper functions". Right. So let me take a quick look inside `mm/zswap.c` to see how `zswap_writeback_entry` is used and if there's a non-static alternative. |
| 10/2 | 2026/08/17 15:12 | tool | grepper |
0mArgs:{
"Expression": "zswap_writeback_entry",
"PathPrefix": "mm/zswap.c"
}Results: {
"Output": "mm/zswap.c=1022=void *zswap_lookup_and_clear_shadows(struct folio *folio)\n--\nmm/zswap.c-1059- */\nmm/zswap.c:1060:static int zswap_writeback_entry(struct zswap_entry *entry,\nmm/zswap.c-1061-\t\t\t\t swp_entry_t swpentry)\n--\nmm/zswap.c=1166=static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_one *l,\n--\nmm/zswap.c-1189-\t * 1. We extract the swp_entry_t to the stack, allowing\nmm/zswap.c:1190:\t * zswap_writeback_entry() to pin the swap entry and\nmm/zswap.c-1191-\t * then validate the zswap entry against that swap entry's\n--\nmm/zswap.c-1224-\nmm/zswap.c:1225:\twriteback_result = zswap_writeback_entry(entry, swpentry);\nmm/zswap.c-1226-\n"
} |
| 11/2 | 2026/08/17 15:12 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9696 output: 34 thoughts: 148 Thoughts: **Analyzing Zswap Function Visibility** Okay, so I'm digging into the Zswap code and trying to get a handle on the scope of some key functions. I see that `zswap_writeback_entry` is declared static, which means it's only visible within the current compilation unit. That makes sense, keeps things modular. But now I need to check some related functions: `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows`. Interesting, these are *not* static, so they're accessible outside the immediate source file. That suggests a broader role or a call from elsewhere in the kernel. Now let's compare that to `shmem_swapin_folio`, which *is* static. This is also from the swapping code. Then there's `do_swap_page`, `swap_cache_read_folio`, `swapin_sync`, and `workingset_refault`, which are all non-static. This likely means they're core swap operations that are used across different parts of the memory management system. To understand `swap_cache_read_folio` and `swapin_sync` better, it makes sense to go straight to `mm/swap_state.c` and take a closer look there, since they seem related to the more general swap state handling. I'll focus on how they interact with the overall swap caching and synchronization mechanisms. |
| 12/2 | 2026/08/17 15:13 | tool | grepper |
0mArgs:{
"Expression": "swap_cache_read_folio",
"PathPrefix": "mm/swap_state.c"
}Results: {
"Output": "mm/swap_state.c=611=void swap_update_readahead(struct folio *folio, struct vm_area_struct *vma,\n--\nmm/swap_state.c-643-\nmm/swap_state.c:644:static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,\nmm/swap_state.c-645-\t\t\t\t\t struct mempolicy *mpol, pgoff_t ilx,\n--\nmm/swap_state.c=728=struct folio *read_swap_cache_async(swp_entry_t entry, gfp_t gfp_mask,\n--\nmm/swap_state.c-741-\tmpol = get_vma_policy(vma, addr, 0, \u0026ilx);\nmm/swap_state.c:742:\tfolio = swap_cache_read_folio(entry, gfp_mask, mpol, ilx, plug, false);\nmm/swap_state.c-743-\tmpol_cond_put(mpol);\n--\nmm/swap_state.c=828=struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t gfp_mask,\n--\nmm/swap_state.c-856-\t\tra_entry = swp_entry(swp_type(entry), offset);\nmm/swap_state.c:857:\t\tfolio = swap_cache_read_folio(ra_entry, gfp_mask, mpol, ilx,\nmm/swap_state.c-858-\t\t\t\t\t \u0026splug, offset != entry_offset);\n--\nmm/swap_state.c-867-\t/* The page was likely read above, so no need for plugging here */\nmm/swap_state.c:868:\treturn swap_cache_read_folio(entry, gfp_mask, mpol, ilx, NULL, false);\nmm/swap_state.c-869-}\n--\nmm/swap_state.c=925=static struct folio *swap_vma_readahead(swp_entry_t targ_entry, gfp_t gfp_mask,\n--\nmm/swap_state.c-967-\t\t}\nmm/swap_state.c:968:\t\tfolio = swap_cache_read_folio(entry, gfp_mask, mpol, ilx,\nmm/swap_state.c-969-\t\t\t\t\t \u0026splug, addr != vmf-\u003eaddress);\n--\nmm/swap_state.c-982-\t/* The folio was likely read above, so no need for plugging here */\nmm/swap_state.c:983:\tfolio = swap_cache_read_folio(targ_entry, gfp_mask, mpol, targ_ilx,\nmm/swap_state.c-984-\t\t\t\t NULL, false);\n"
} |
| 13/2 | 2026/08/17 15:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10909 output: 31 thoughts: 97 Thoughts: **Analyzing Swap Functions in Linux Kernel** Okay, so I'm looking at these swap-related functions and making some observations. First, I see that `swap_cache_read_folio` is declared `static`. This immediately tells me it's likely an internal helper function, meaning it's only meant to be used within its defining compilation unit – probably within the same file. It's not part of the external API that other parts of the kernel are intended to call directly. Then, I note that `swapin_sync`, `zswap_folio_is_writeback_buffer`, `zswap_lookup_and_clear_shadows`, and `workingset_refault` are all *non-static*. This means they are likely meant to be callable from outside the scope of where they are declared. This suggests they could be used in other kernel modules or other parts of the memory management subsystem. They are, thus, part of a functional interface. I should keep in mind which of these functions I may need to be calling for a given use-case. I should probably dive into `mm/swap_state.c` for some detailed information about `swapin_sync` and how it fits into the whole picture. I'm going to look at the function, its parameters, and where it is called to better understand its role. |
| 14/2 | 2026/08/17 15:13 | tool | grepper |
0mArgs:{
"Expression": "swapin_sync",
"PathPrefix": "mm/swap_state.c"
}Results: {
"Output": "mm/swap_state.c=644=static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,\n--\nmm/swap_state.c-678-/**\nmm/swap_state.c:679: * swapin_sync - swap-in one or multiple entries skipping readahead.\nmm/swap_state.c-680- * @entry: swap entry indicating the target slot\n--\nmm/swap_state.c-693- */\nmm/swap_state.c:694:struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,\nmm/swap_state.c-695-\t\t\t struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx)\n"
} |
| 15/2 | 2026/08/17 15:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11539 output: 33 thoughts: 86 Thoughts: Here's my summary, as I see it: **Focusing In on Key Areas for Optimization** Okay, let's zero in on some key symbols that are likely impacting performance, namely `swapin_sync`, `zswap_folio_is_writeback_buffer`, `zswap_lookup_and_clear_shadows`, and that crucial function, `workingset_refault`. I need to understand how these pieces interact with the larger picture. I'm going to take a closer look at `do_swap_page`, as I know it's frequently called so I need to avoid it. Now, where was that `workingset_refault` function again? Ah, right, that's in `mm/workingset.c`. I'll start there to understand the internals of that one. |
| 16/2 | 2026/08/17 15:13 | tool | grepper |
0mArgs:{
"Expression": "workingset_refault",
"PathPrefix": "mm/workingset.c"
}Results: {
"Output": "mm/workingset.c=430=bool workingset_test_recent(void *shadow, bool file, bool *workingset,\n--\nmm/workingset.c-539-/**\nmm/workingset.c:540: * workingset_refault - Evaluate the refault of a previously evicted folio.\nmm/workingset.c-541- * @folio: The freshly allocated replacement folio.\n--\nmm/workingset.c-547- */\nmm/workingset.c:548:void workingset_refault(struct folio *folio, void *shadow)\nmm/workingset.c-549-{\n"
} |
| 17/2 | 2026/08/17 15:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12010 output: 37 thoughts: 54 Thoughts: Here's my thinking process: **Diving into Memory Management** Okay, so `workingset_refault` isn't static. That immediately makes me think about potential visibility and scope implications. I need to understand its context. Where is it being used? How is it being accessed? Given that this is memory management-related, I'll start digging into the zswap component. Specifically, I should look at `mm/zswap.c`. I'm especially interested in understanding the interaction between `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows`. These functions probably offer crucial insights into how data is handled and potentially re-faulted or cleared within the zswap architecture. I expect that by tracing their usage, I can figure out why the "non-static" characteristic of `workingset_refault` is noteworthy. This will give me a better understanding of the code. |
| 18/2 | 2026/08/17 15:13 | tool | grepper |
0mArgs:{
"Expression": "zswap_folio_is_writeback_buffer",
"PathPrefix": "mm/zswap.c"
}Results: {
"Output": "mm/zswap.c=924=static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)\n--\nmm/zswap.c-978-/*\nmm/zswap.c:979: * zswap_folio_is_writeback_buffer - is @folio a zswap writeback buffer?\nmm/zswap.c-980- * @folio: the folio being examined (typically a swap cache folio under reclaim)\n--\nmm/zswap.c-988- */\nmm/zswap.c:989:bool zswap_folio_is_writeback_buffer(struct folio *folio)\nmm/zswap.c-990-{\n"
} |
| 19/2 | 2026/08/17 15:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12485 output: 37 thoughts: 148 Thoughts: **My Assessment of the Zswap Implementation** Okay, so we're dealing with a system where things aren't just fixed; they're dynamic. My initial instinct is to hone in on a few key symbols that drive the core functionality here. These look like the central pieces: `zswap_folio_is_writeback_buffer`, `zswap_lookup_and_clear_shadows`, `swapin_sync`, and `workingset_refault`. These seem to be the critical functions for managing the zswap process. Now, as an expert, I know that configuration is key, so I'll quickly verify the necessary kernel configurations. I'm expecting `SWAP`, `ZSWAP`, and `ZSWAP_DEFAULT_ON` to be enabled. I may want to poke around and confirm that. It's time to dig a bit deeper. Could we be missing something? Thinking laterally, I'm going to quickly check if `ZSWAP_SHRINKER_DEFAULT_ON` might be pertinent to triggering that writeback. Given my experience, I'll need to double check the Kconfig file, so I'll check `mm/Kconfig`. |
| 20/2 | 2026/08/17 15:13 | tool | grepper |
0mArgs:{
"Expression": "config ZSWAP_SHRINKER_DEFAULT_ON",
"PathPrefix": "mm/Kconfig"
}Results: {
"Output": "mm/Kconfig=35=config ZSWAP_DEFAULT_ON\n--\nmm/Kconfig-44-\nmm/Kconfig:45:config ZSWAP_SHRINKER_DEFAULT_ON\nmm/Kconfig-46-\tbool \"Shrink the zswap pool on memory pressure\"\n"
} |
| 21/2 | 2026/08/17 15:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13009 output: 31 thoughts: 16 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person: **Diving into the Kernel Configuration Landscape** Okay, so I'm digging into `mm/Kconfig`, specifically around line 45. My intuition tells me that specific configuration option or section, likely related to memory management (`mm`), is defined or has some critical logic there. I'm probably looking for something specific – maybe a default setting, a conditional dependency, or an important comment that's influencing some behavior I'm investigating. I need to be precise, I can't just scan the file; my goal is to pinpoint the exact config option, and understand the logic that governs it. I expect that I will be able to form a more complete understanding as I approach line 45. |
| 22/2 | 2026/08/17 15:13 | tool | read-file |
0mArgs:{
"File": "mm/Kconfig",
"FirstLine": 40,
"LineCount": 20
}Results: {
"Contents": " 40:\t\t at boot, otherwise it will be disabled.\n 41:\t\n 42:\t\t The selection made here can be overridden by using the kernel\n 43:\t\t command line 'zswap.enabled=' option.\n 44:\t\n 45:\tconfig ZSWAP_SHRINKER_DEFAULT_ON\n 46:\t\tbool \"Shrink the zswap pool on memory pressure\"\n 47:\t\tdepends on ZSWAP\n 48:\t\tdefault n\n 49:\t\thelp\n 50:\t\t If selected, the zswap shrinker will be enabled, and the pages\n 51:\t\t stored in the zswap pool will become available for reclaim (i.e\n 52:\t\t written back to the backing swap device) on memory pressure.\n 53:\t\n 54:\t\t This means that zswap writeback could happen even if the pool is\n 55:\t\t not yet full, or the cgroup zswap limit has not been reached,\n 56:\t\t reducing the chance that cold pages will reside in the zswap pool\n 57:\t\t and consume memory indefinitely.\n 58:\t\n 59:\tchoice\n"
} |
| 23/2 | 2026/08/17 15:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13524 output: 37 thoughts: 537 Thoughts: **My Analysis of this Patch** Okay, so this patch is all about handling workingset shadows correctly for zswap writeback buffers. Essentially, it's about getting writeback to trigger, which is achieved by utilizing `ZSWAP_SHRINKER_DEFAULT_ON`. So, that's definitely going in. I'm quickly checking if a similar compressor config like `ZSWAP_COMPRESSOR_DEFAULT_LZ4` is necessary, but the default compressor is fine in this instance, so I'll skip it. Let me recap my reasoning: This modifies swap and zswap to manage those pesky workingset shadows during writeback and swap-in. It does this with `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows`, which are crucial for managing parked shadows in the zswap tree. These are core memory management changes, touching page reclaim, swap cache, and zswap directly. They are reachable and have a functional impact, which means this is a significant area. Looking at the configs that are impacted: `SWAP`, `ZSWAP`, `ZSWAP_DEFAULT_ON`, and definitely `ZSWAP_SHRINKER_DEFAULT_ON`. Now, for focus symbols, which is where things get interesting. I'm focusing on key symbols to highlight the area of change. `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows` are the obvious choices and are the core of this patch. I considered `swapin_sync` as a good one, or perhaps `shmem_swapin_folio`, but I dismissed the latter as it is a static function. Now `zswap_writeback_entry` is static, but is essential, so I need to decide if I can use it as a focus point. The prompt said to prefer non-static functions over static, but it doesn't forbid static functions. Okay, let me double-check the definitions. `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows` are *non*-static and declared in `include/linux/zswap.h`, so they are perfect. They're exactly what I need. I'll just confirm `workingset_refault`'s export status. |
| 24/2 | 2026/08/17 15:13 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*workingset_refault",
"PathPrefix": "mm/workingset.c"
}Results: {
"error": "no matches"
} |
| 25/2 | 2026/08/17 15:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14621 output: 35 thoughts: 29 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Checking Code Visibility and Function Scope** Alright, so I'm looking at this function and I'm reminded it's definitely not exported. But, the key thing is that it *isn't* declared as static. This distinction is crucial. Non-static means it might be accessible in ways I need to consider, even if it's not explicitly part of the published API. I have to think about where this thing is used. Is it accessible to linking? Is it in the same compilation unit or a separate one? Therefore, I now need to determine, and I'll quickly check, if `swapin_sync` is actually exported. I need to know for sure if it's being exposed via some other means or if the code is structured in a way that *seems* to be private but actually isn't. The scoping is critical here. It affects everything from build configuration to dependency management, and of course, whether or not a seemingly internal function is going to cause trouble down the line. |
| 26/2 | 2026/08/17 15:13 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*swapin_sync",
"PathPrefix": "mm/swap_state.c"
}Results: {
"error": "no matches"
} |
| 27/2 | 2026/08/17 15:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14932 output: 30 thoughts: 84 Thoughts: Okay, here's my take on those thoughts, framed from my perspective: **Final Patch Review: Checking Configuration Dependencies** Alright, the initial work on this patch, focusing on the selected symbols and configs, looks solid. I'm pleased with the direction we're taking. Before we call this done, though, I need to do a final sweep to make absolutely sure we've accounted for every dependency. It's not exported, but it's *non-static*, so I need to be extra thorough. Specifically, I'm thinking about `CONFIG_MEMCG`. The reason I'm raising this flag is that `shrink_memcg_cb` is in `zswap.c`. I've got a nagging feeling that there might be some interaction there. Let's head over to `mm/zswap.c` and take a look for any dependencies on `MEMCG` that we might have missed. Gotta be meticulous at this stage. |
| 28/2 | 2026/08/17 15:13 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_MEMCG",
"PathPrefix": "mm/zswap.c"
}Results: {
"Output": "mm/zswap.c=617=static int zswap_enabled_param_set(const char *val,\n--\nmm/zswap.c-650-/* should be called under RCU */\nmm/zswap.c:651:#ifdef CONFIG_MEMCG\nmm/zswap.c-652-static inline struct mem_cgroup *mem_cgroup_from_entry(struct zswap_entry *entry)\n"
} |
| 29/2 | 2026/08/17 15:13 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15366 output: 203 thoughts: 237 Thoughts: **My Assessment of the Zswap Writeback Buffer Patch** Okay, so we're looking at integrating this patch, and I'm running through the dependencies in my head. `MEMCG` is usually enabled, and I'd normally just assume it's there, but I think I should explicitly include it, just in case. It's always good to be thorough. Now, `ZSWAP` doesn't strictly depend on `MEMCG` itself. I had to double-check that `ZSWAP_SHRINKER_DEFAULT_ON` might depend on MEMCG, but it doesn't. Alright, let me refresh my memory on the core `ZSWAP` dependencies. It needs `SWAP`, naturally. It *selects* `CRYPTO` and `ZSMALLOC`. Okay, those are straightforward. Given this patch is touching the writeback buffers and the core memory management, I'm going to include `SWAP`, `ZSWAP`, `ZSWAP_DEFAULT_ON`, and `ZSWAP_SHRINKER_DEFAULT_ON`. I think that covers all the bases. This patch, from what I'm reading, modifies how the swap and zswap subsystems work, particularly with `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows`. These are critical additions for managing the shadows within the zswap tree. It's dealing with writeback and swap-in operations, so we're talking about direct effects on page reclaim, the swap cache, and zswap itself. This is core, reachable, and functionally significant. Let's call the `set-results` function to solidify the changes. |
| 30/2 | 2026/08/17 15:13 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"SWAP",
"ZSWAP",
"ZSWAP_DEFAULT_ON",
"ZSWAP_SHRINKER_DEFAULT_ON"
],
"FocusSymbols": [
"zswap_folio_is_writeback_buffer",
"zswap_lookup_and_clear_shadows",
"swapin_sync",
"workingset_refault"
],
"Reasoning": "The patch modifies the swap and zswap subsystems to properly handle workingset shadows for zswap writeback buffers. It introduces `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows` to manage parked shadows in the zswap tree during writeback and swap-in. These are core memory management changes that affect page reclaim, swap cache, and zswap, which are reachable and have functional impact.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"SWAP",
"ZSWAP",
"ZSWAP_DEFAULT_ON",
"ZSWAP_SHRINKER_DEFAULT_ON"
],
"FocusSymbols": [
"zswap_folio_is_writeback_buffer",
"zswap_lookup_and_clear_shadows",
"swapin_sync",
"workingset_refault"
],
"Reasoning": "The patch modifies the swap and zswap subsystems to properly handle workingset shadows for zswap writeback buffers. It introduces `zswap_folio_is_writeback_buffer` and `zswap_lookup_and_clear_shadows` to manage parked shadows in the zswap tree during writeback and swap-in. These are core memory management changes that affect page reclaim, swap cache, and zswap, which are reachable and have functional impact.",
"WorthFuzzing": true
} |
| 31/1 | 2026/08/17 15:13 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 32/2 | 2026/08/17 15:13 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch series modifies the swap and zswap subsystems to handle workingset shadows during zswap writeback. It parks the shadow in the zswap tree and restores it upon swap-in or swap cache deletion. These changes involve pointer manipulation, xarray operations, and folio state management. There are no new data structures allocated and copied to user space, nor any complex structures where uninitialized fields might be read. Any bugs introduced by these changes, such as incorrect pointer dereferences, use-after-free, or memory corruption, would be effectively detected by KASAN and other standard kernel debugging tools. There is no risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"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 6c364abd5a072dace2951f5f0f1d1fcabcd8d3e7
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 17 15:11:19 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/zswap.h b/include/linux/zswap.h
index 30c193a1207e1..dfb7153236f5c 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -35,6 +35,8 @@ void zswap_lruvec_state_init(struct lruvec *lruvec);
void zswap_folio_swapin(struct folio *folio);
bool zswap_is_enabled(void);
bool zswap_never_enabled(void);
+bool zswap_folio_is_writeback_buffer(struct folio *folio);
+void *zswap_lookup_and_clear_shadows(struct folio *folio);
#else
struct zswap_lruvec_state {};
@@ -69,6 +71,16 @@ static inline bool zswap_never_enabled(void)
return true;
}
+static inline bool zswap_folio_is_writeback_buffer(struct folio *folio)
+{
+ return false;
+}
+
+static inline void *zswap_lookup_and_clear_shadows(struct folio *folio)
+{
+ return NULL;
+}
+
#endif
#endif /* _LINUX_ZSWAP_H */
diff --git a/mm/memory.c b/mm/memory.c
index 6b8280cfc1dbe..17e904662eb65 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4759,6 +4759,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
unsigned long page_idx;
unsigned long address;
pte_t *ptep;
+ void *shadow;
if (!pte_unmap_same(vmf))
goto out;
@@ -4880,6 +4881,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
goto out_page;
}
+ shadow = zswap_lookup_and_clear_shadows(folio);
+ if (shadow)
+ workingset_refault(folio, shadow);
+
/*
* KSM sometimes has to copy on read faults, for example, if
* folio->index of non-ksm folios would be nonlinear inside the
diff --git a/mm/shmem.c b/mm/shmem.c
index 9001aaf3b7b94..f968b68b60c52 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2250,6 +2250,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
struct folio *folio = NULL;
int error, nr_pages, order;
pgoff_t offset;
+ void *shadow;
VM_BUG_ON(!*foliop || !xa_is_value(*foliop));
index_entry = radix_to_swp_entry(*foliop);
@@ -2361,6 +2362,10 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
*/
arch_swap_restore(folio_swap(swap, folio), folio);
+ shadow = zswap_lookup_and_clear_shadows(folio);
+ if (shadow)
+ workingset_refault(folio, shadow);
+
if (shmem_should_replace_folio(folio, gfp)) {
error = shmem_replace_folio(&folio, gfp, info, index, vma);
if (error)
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 9c3a5cf997786..7ea4a2ba08416 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -22,6 +22,7 @@
#include <linux/vmalloc.h>
#include <linux/huge_mm.h>
#include <linux/shmem_fs.h>
+#include <linux/zswap.h>
#include "internal.h"
#include "swap_table.h"
#include "swap.h"
@@ -257,12 +258,21 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,
unsigned int ci_start, ci_off, ci_end;
bool folio_swapped = false, need_free = false;
unsigned long nr_pages = folio_nr_pages(folio);
+ void *shadow_parked;
VM_WARN_ON_ONCE(__swap_entry_to_cluster(entry) != ci);
VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
VM_WARN_ON_ONCE_FOLIO(!folio_test_swapcache(folio), folio);
VM_WARN_ON_ONCE_FOLIO(folio_test_writeback(folio), folio);
+ /*
+ * A zswap writeback buffer parked the slot's original shadow in the
+ * zswap tree: restore it into the slot for later swap-in.
+ */
+ shadow_parked = zswap_lookup_and_clear_shadows(folio);
+ if (shadow_parked)
+ shadow = shadow_parked;
+
si = __swap_entry_to_info(entry);
ci_start = swp_cluster_offset(entry);
ci_end = ci_start + nr_pages;
@@ -477,8 +487,6 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
/* memsw uncharges swap when folio is added to swap cache */
memcg1_swapin(folio);
- if (shadow)
- workingset_refault(folio, shadow);
node_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);
lruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);
@@ -638,17 +646,26 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
struct swap_iocb **plug, bool readahead)
{
struct folio *folio;
+ void *shadow = NULL;
do {
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
+ /*
+ * Capture the slot's shadow before the allocation overwrites it,
+ * so a fresh swap-in can be evaluated as a refault below.
+ */
+ shadow = swap_cache_get_shadow(entry);
folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR_OR_NULL(folio))
return NULL;
+ if (shadow)
+ workingset_refault(folio, shadow);
+
swap_read_folio(folio, plug);
if (readahead) {
folio_set_readahead(folio);
@@ -678,17 +695,26 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx)
{
struct folio *folio;
+ void *shadow = NULL;
do {
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
+ /*
+ * Capture the slot's shadow before the allocation overwrites it,
+ * so a fresh swap-in can be evaluated as a refault below.
+ */
+ shadow = swap_cache_get_shadow(entry);
folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR(folio))
return folio;
+ if (shadow)
+ workingset_refault(folio, shadow);
+
swap_read_folio(folio, NULL);
return folio;
}
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 56708d1d2dfd5..c27e957322b82 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -48,6 +48,7 @@
#include <linux/prefetch.h>
#include <linux/printk.h>
#include <linux/dax.h>
+#include <linux/zswap.h>
#include <linux/psi.h>
#include <linux/pagewalk.h>
#include <linux/shmem_fs.h>
@@ -730,7 +731,8 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
if (folio_test_swapcache(folio)) {
swp_entry_t swap = folio->swap;
- if (reclaimed && !mapping_exiting(mapping))
+ if (reclaimed && !mapping_exiting(mapping) &&
+ !zswap_folio_is_writeback_buffer(folio))
shadow = workingset_eviction(folio, target_memcg);
__memcg1_swapout(folio, ci);
__swap_cache_del_folio(ci, folio, swap, shadow);
diff --git a/mm/workingset.c b/mm/workingset.c
index f351798e723ac..7c30cf091cbfb 100644
--- a/mm/workingset.c
+++ b/mm/workingset.c
@@ -577,7 +577,10 @@ void workingset_refault(struct folio *folio, void *shadow)
if (!workingset_test_recent(shadow, file, &workingset, true))
goto out;
- folio_set_active(folio);
+ if (unlikely(folio_test_lru(folio)))
+ folio_activate(folio);
+ else
+ folio_set_active(folio);
workingset_age_nonresident(lruvec, nr);
mod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + file, nr);
diff --git a/mm/zswap.c b/mm/zswap.c
index 761cd699e0a3e..f059206948aba 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -972,6 +972,79 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
/*********************************
* writeback code
**********************************/
+
+#define ZSWAP_WRITEBACK_NO_SHADOW xa_mk_value(0)
+
+/*
+ * zswap_folio_is_writeback_buffer - is @folio a zswap writeback buffer?
+ * @folio: the folio being examined (typically a swap cache folio under reclaim)
+ *
+ * A folio is a zswap writeback buffer when every one of its swap offsets holds
+ * a parked writeback shadow (a real shadow or the ZSWAP_WRITEBACK_NO_SHADOW
+ * sentinel) in the zswap tree rather than a live zswap entry.
+ *
+ * Return: true if @folio is a writeback buffer, in which case the reclaim path
+ * must not mint a fresh workingset shadow for it.
+ */
+bool zswap_folio_is_writeback_buffer(struct folio *folio)
+{
+ swp_entry_t swp = folio->swap;
+ unsigned long nr_pages = folio_nr_pages(folio);
+ pgoff_t offset = swp_offset(swp);
+ unsigned long i;
+
+ if (zswap_never_enabled())
+ return false;
+
+ for (i = 0; i < nr_pages; i++) {
+ swp_entry_t e = swp_entry(swp_type(swp), offset + i);
+
+ if (!xa_is_value(xa_load(swap_zswap_tree(e), offset + i)))
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * zswap_lookup_and_clear_shadows - retrieve and clear @folio's parked shadow(s)
+ * @folio: the writeback buffer folio (or the swapin folio that consumed it)
+ *
+ * Remove any parked writeback shadows for @folio's swap offset(s) from the
+ * zswap tree.
+ *
+ * Return: the preserved workingset shadow, or NULL if the slot(s) had no shadow
+ * (sentinel only) or nothing parked. The caller either restores the returned
+ * shadow into the swap slot (buffer dropped) or feeds it to workingset_refault()
+ * (buffer consumed by a swapin); clearing here ensures the two paths never
+ * double-count.
+ */
+void *zswap_lookup_and_clear_shadows(struct folio *folio)
+{
+ swp_entry_t swp = folio->swap;
+ unsigned long nr_pages = folio_nr_pages(folio);
+ pgoff_t offset = swp_offset(swp);
+ void *shadow = NULL;
+ unsigned long i;
+
+ if (zswap_never_enabled())
+ return NULL;
+
+ for (i = 0; i < nr_pages; i++) {
+ swp_entry_t e = swp_entry(swp_type(swp), offset + i);
+ struct xarray *tree = swap_zswap_tree(e);
+ void *parked = xa_load(tree, offset + i);
+
+ if (!xa_is_value(parked))
+ continue;
+ xa_erase(tree, offset + i);
+ if (parked != ZSWAP_WRITEBACK_NO_SHADOW)
+ shadow = parked;
+ }
+
+ return shadow;
+}
+
/*
* Attempts to free an entry by adding a folio to the swap cache,
* decompressing the entry data into the folio, and issuing a
@@ -992,6 +1065,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
struct folio *folio;
struct mempolicy *mpol;
struct swap_info_struct *si;
+ void *shadow;
int ret = 0;
/* try to allocate swap cache folio */
@@ -999,6 +1073,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
if (!si)
return -EEXIST;
+ shadow = swap_cache_get_shadow(swpentry);
+
mpol = get_task_policy(current);
folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
NO_INTERLEAVE_INDEX);
@@ -1034,7 +1110,9 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
goto out;
}
- xa_erase(tree, offset);
+ if (!shadow)
+ shadow = ZSWAP_WRITEBACK_NO_SHADOW;
+ xa_store(tree, offset, shadow, GFP_KERNEL);
count_vm_event(ZSWPWB);
if (entry->objcg)
|
| 33/3 | 2026/08/17 15:13 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 5912 output: 172 thoughts: 756 Thoughts: **KMSAN Fuzzing Session Assessment** Okay, let's break this down. My initial assessment is that this patch series modifying zswap, swap_state, shmem, memory, vmscan, and workingset likely *doesn't* warrant a dedicated KMSAN fuzzing session. The modifications center around managing workingset shadows during zswap writeback. We're talking about changes to `zswap_folio_is_writeback_buffer`, `zswap_lookup_and_clear_shadows`, the parking and restoring of the workingset shadow in the zswap tree during writeback and swap-in/cache deletion, and hooking this into existing functions like `do_swap_page`, `shmem_swapin_folio`, `__swap_cache_do_del_folio`, `swap_cache_read_folio`, and `swapin_sync`. My immediate concern, as always, is uninitialized memory. However, from what I can see, the shadow variable is a `void *`, which is basically an encoded value. It looks like it's created via `xa_mk_value` (or a similar mechanism in the workingset code) and stored in the xarray (`swap_zswap_tree(e)`). There aren't any new structures being introduced that are passed to userspace or any allocated complex data structures. This means the risk of accidentally reading uninitialized fields is minimal, especially since it doesn't change bounds checks or lengths in a way that allows surrounding code to access uninitialized bytes. Essentially, these changes are deeply involved in memory management, swap cache interactions, and workingset shadows. Any bugs here would likely manifest as use-after-free, null pointer dereferences, or memory corruption. These are *precisely* the types of issues that KASAN and other standard kernel debugging tools are designed to catch. I'm double-checking the initialization path now, just to be sure. I see that `shadow` is initialized to `NULL` in `swap_cache_read_folio` and `swapin_sync`. `zswap_lookup_and_clear_shadows` also initializes the retrieved shadow to `NULL` and updates it if a parked shadow is found. Inside `zswap_writeback_entry`, the `shadow` is either derived from `swap_cache_get_shadow(swpentry)` or set to `ZSWAP_WRITEBACK_NO_SHADOW`. It all looks safe. So, given the nature of the changes (pointer manipulation, xarray, and folio state management), the absence of new, complicated, user-facing data structures, and the high likelihood that any errors would be caught by KASAN, I'm comfortable saying that this doesn't necessitate a special KMSAN fuzzing session. The reasoning is solid: standard debugging tools are more than sufficient. I'm going to set `NeedsKMSAN` to `false` and move on. |
| 34/3 | 2026/08/17 15:14 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series modifies the swap and zswap subsystems to handle workingset shadows during zswap writeback. It parks the shadow in the zswap tree and restores it upon swap-in or swap cache deletion. These changes involve pointer manipulation, xarray operations, and folio state management. There are no new data structures allocated and copied to user space, nor any complex structures where uninitialized fields might be read. Any bugs introduced by these changes, such as incorrect pointer dereferences, use-after-free, or memory corruption, would be effectively detected by KASAN and other standard kernel debugging tools. There is no risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series modifies the swap and zswap subsystems to handle workingset shadows during zswap writeback. It parks the shadow in the zswap tree and restores it upon swap-in or swap cache deletion. These changes involve pointer manipulation, xarray operations, and folio state management. There are no new data structures allocated and copied to user space, nor any complex structures where uninitialized fields might be read. Any bugs introduced by these changes, such as incorrect pointer dereferences, use-after-free, or memory corruption, would be effectively detected by KASAN and other standard kernel debugging tools. There is no risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|