AI Trajectory


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

Results:
{
  "EnableConfigs": [
    "ZSWAP",
    "ZSWAP_DEFAULT_ON"
  ],
  "FocusSymbols": [
    "zswap_store",
    "zswap_load",
    "zswap_invalidate",
    "zswap_writeback_entry",
    "__swap_cache_do_add_folio",
    "__swap_cache_do_del_folio",
    "__swap_cluster_put_entry",
    "__swap_cluster_free_entries",
    "__swap_cluster_dup_entry",
    "zswap_swp_tb_dup_count",
    "zswap_swp_tb_put_count"
  ],
  "Reasoning": "The patch fundamentally changes how zswap tracks compressed pages by embedding `zswap_entry` pointers directly into the core swap table (`swap_cluster_info`) rather than using a separate xarray (`zswap_trees`). This affects core swap allocation, freeing, and lookup paths, requiring fuzzing to ensure no memory leaks, use-after-frees, or swap accounting errors occur during concurrent swap operations.",
  "WorthFuzzing": true
}

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

Results:
{
  "PatchDiff": "commit f201198587631215f708f5886a9810323fbc2399\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Tue Jul 7 15:11:18 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/include/linux/zswap.h b/include/linux/zswap.h\nindex 30c193a1207e16..4f55c8d0c4c542 100644\n--- a/include/linux/zswap.h\n+++ b/include/linux/zswap.h\n@@ -26,7 +26,7 @@ struct zswap_lruvec_state {\n \n unsigned long zswap_total_pages(void);\n bool zswap_store(struct folio *folio);\n-int zswap_load(struct folio *folio);\n+int zswap_load(struct folio *folio, void *zentry);\n void zswap_invalidate(swp_entry_t swp);\n int zswap_swapon(int type, unsigned long nr_pages);\n void zswap_swapoff(int type);\n@@ -44,7 +44,7 @@ static inline bool zswap_store(struct folio *folio)\n \treturn false;\n }\n \n-static inline int zswap_load(struct folio *folio)\n+static inline int zswap_load(struct folio *folio, void *zentry)\n {\n \treturn -ENOENT;\n }\ndiff --git a/mm/page_io.c b/mm/page_io.c\nindex b23f494fcc83da..1ce8201ed86e24 100644\n--- a/mm/page_io.c\n+++ b/mm/page_io.c\n@@ -658,7 +658,7 @@ static void swap_read_folio_bdev_async(struct folio *folio,\n \tsubmit_bio(bio);\n }\n \n-void swap_read_folio(struct folio *folio, struct swap_iocb **plug)\n+void swap_read_folio(struct folio *folio, struct swap_iocb **plug, void *zentry)\n {\n \tstruct swap_info_struct *sis = __swap_entry_to_info(folio-\u003eswap);\n \tbool synchronous = sis-\u003eflags \u0026 SWP_SYNCHRONOUS_IO;\n@@ -686,7 +686,7 @@ void swap_read_folio(struct folio *folio, struct swap_iocb **plug)\n \t\tgoto finish;\n \t}\n \n-\tif (zswap_load(folio) != -ENOENT)\n+\tif (zswap_load(folio, zentry) != -ENOENT)\n \t\tgoto finish;\n \n \t/* We have to read from slower devices. Increase zswap protection. */\ndiff --git a/mm/swap.h b/mm/swap.h\nindex 44ab8e1e595b08..84e13ba6e605c3 100644\n--- a/mm/swap.h\n+++ b/mm/swap.h\n@@ -210,6 +210,41 @@ static inline void swap_cluster_unlock_irq(struct swap_cluster_info *ci)\n \n extern int swap_retry_table_alloc(swp_entry_t entry, gfp_t gfp);\n \n+struct zswap_entry;\n+#ifdef CONFIG_ZSWAP\n+void *zswap_swp_tb_get_shadow(unsigned long swp_tb);\n+unsigned char zswap_swp_tb_get_count(unsigned long swp_tb);\n+unsigned char zswap_swp_tb_get_flags(unsigned long swp_tb);\n+int zswap_swp_tb_dup_count(unsigned long swp_tb,\n+\t\t\t   struct swap_cluster_info *ci, unsigned int ci_off);\n+void zswap_swp_tb_put_count(unsigned long swp_tb,\n+\t\t\t    struct swap_cluster_info *ci, unsigned int ci_off);\n+void zswap_entry_free(struct zswap_entry *entry);\n+#else\n+static inline void *zswap_swp_tb_get_shadow(unsigned long swp_tb)\n+{\n+\treturn NULL;\n+}\n+static inline unsigned char zswap_swp_tb_get_count(unsigned long swp_tb)\n+{\n+\treturn 0;\n+}\n+static inline unsigned char zswap_swp_tb_get_flags(unsigned long swp_tb)\n+{\n+\treturn 0;\n+}\n+static inline int zswap_swp_tb_dup_count(unsigned long swp_tb,\n+\t\t\t\t\t struct swap_cluster_info *ci,\n+\t\t\t\t\t unsigned int ci_off)\n+{\n+\treturn 0;\n+}\n+static inline void zswap_swp_tb_put_count(unsigned long swp_tb,\n+\t\t\t\t\t  struct swap_cluster_info *ci,\n+\t\t\t\t\t  unsigned int ci_off) {}\n+static inline void zswap_entry_free(struct zswap_entry *entry) {}\n+#endif\n+\n /*\n  * Below are the core routines for doing swap for a folio.\n  * All helpers requires the folio to be locked, and a locked folio\n@@ -241,7 +276,7 @@ extern void __swap_cluster_free_entries(struct swap_info_struct *si,\n /* linux/mm/page_io.c */\n int sio_pool_init(void);\n struct swap_iocb;\n-void swap_read_folio(struct folio *folio, struct swap_iocb **plug);\n+void swap_read_folio(struct folio *folio, struct swap_iocb **plug, void *zentry);\n void __swap_read_unplug(struct swap_iocb *plug);\n static inline void swap_read_unplug(struct swap_iocb *plug)\n {\n@@ -303,13 +338,16 @@ static inline bool folio_matches_swap_entry(const struct folio *folio,\n bool swap_cache_has_folio(swp_entry_t entry);\n struct folio *swap_cache_get_folio(swp_entry_t entry);\n void *swap_cache_get_shadow(swp_entry_t entry);\n+bool folio_maybe_swapped(struct folio *folio);\n void swap_cache_del_folio(struct folio *folio);\n struct folio *swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,\n \t\t\t\t     unsigned long orders, struct vm_fault *vmf,\n-\t\t\t\t     struct mempolicy *mpol, pgoff_t ilx);\n+\t\t\t\t     struct mempolicy *mpol, pgoff_t ilx,\n+\t\t\t\t     void **zentry);\n /* Below helpers require the caller to lock and pass in the swap cluster. */\n void __swap_cache_add_folio(struct swap_cluster_info *ci,\n-\t\t\t    struct folio *folio, swp_entry_t entry);\n+\t\t\t    struct folio *folio, swp_entry_t entry,\n+\t\t\t    void **zentry);\n void __swap_cache_del_folio(struct swap_cluster_info *ci,\n \t\t\t    struct folio *folio, swp_entry_t entry, void *shadow);\n void __swap_cache_replace_folio(struct swap_cluster_info *ci,\n@@ -381,7 +419,8 @@ static inline void folio_put_swap(struct folio *folio, struct page *page)\n {\n }\n \n-static inline void swap_read_folio(struct folio *folio, struct swap_iocb **plug)\n+static inline void swap_read_folio(struct folio *folio, struct swap_iocb **plug,\n+\t\t\t\t   void *zentry)\n {\n }\n \n@@ -453,6 +492,11 @@ static inline void *swap_cache_get_shadow(swp_entry_t entry)\n \treturn NULL;\n }\n \n+static inline bool folio_maybe_swapped(struct folio *folio)\n+{\n+\treturn false;\n+}\n+\n static inline void swap_cache_del_folio(struct folio *folio)\n {\n }\ndiff --git a/mm/swap_state.c b/mm/swap_state.c\nindex 6fd6e3415b718b..60d937c6b39224 100644\n--- a/mm/swap_state.c\n+++ b/mm/swap_state.c\n@@ -175,16 +175,29 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci,\n \told_tb = __swap_table_get(ci, ci_off);\n \tif (swp_tb_is_folio(old_tb))\n \t\treturn -EEXIST;\n-\tif (!__swp_tb_get_count(old_tb))\n+\tif (swp_tb_is_pointer(old_tb)) {\n+\t\tif (!zswap_swp_tb_get_count(old_tb))\n+\t\t\treturn -ENOENT;\n+\t} else if (!__swp_tb_get_count(old_tb)) {\n \t\treturn -ENOENT;\n+\t}\n \tif (shadowp \u0026\u0026 swp_tb_is_shadow(old_tb))\n \t\t*shadowp = swp_tb_to_shadow(old_tb);\n+\telse if (shadowp \u0026\u0026 swp_tb_is_pointer(old_tb))\n+\t\t*shadowp = zswap_swp_tb_get_shadow(old_tb);\n \tif (memcg_id)\n \t\t*memcg_id = __swap_cgroup_get(ci, ci_off);\n \n \tif (nr == 1)\n \t\treturn 0;\n \n+\t/*\n+\t * For multi-slot ranges, Pointer entries are not expected since\n+\t * zswap does not support large folios. Return -EBUSY for safety.\n+\t */\n+\tif (swp_tb_is_pointer(old_tb))\n+\t\treturn -EBUSY;\n+\n \tis_zero = __swap_table_test_zero(ci, ci_off);\n \tci_off = round_down(ci_off, nr);\n \tci_end = ci_off + nr;\n@@ -201,7 +214,8 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci,\n }\n \n static void __swap_cache_do_add_folio(struct swap_cluster_info *ci,\n-\t\t\t\t      struct folio *folio, swp_entry_t entry)\n+\t\t\t\t      struct folio *folio, swp_entry_t entry,\n+\t\t\t\t      void **zentry)\n {\n \tunsigned int ci_off = swp_cluster_offset(entry), ci_end;\n \tunsigned long nr_pages = folio_nr_pages(folio);\n@@ -216,7 +230,34 @@ static void __swap_cache_do_add_folio(struct swap_cluster_info *ci,\n \tdo {\n \t\told_tb = __swap_table_get(ci, ci_off);\n \t\tVM_WARN_ON_ONCE(swp_tb_is_folio(old_tb));\n-\t\t__swap_table_set(ci, ci_off, pfn_to_swp_tb(pfn, __swp_tb_get_flags(old_tb)));\n+\t\t/*\n+\t\t * If the old entry is a Pointer (zswap compressed data),\n+\t\t * extract the zswap_entry for the caller and preserve\n+\t\t * its flags. The Pointer entry has no inline flags.\n+\t\t */\n+\t\tif (swp_tb_is_pointer(old_tb)) {\n+\t\t\tunsigned char pflags = zswap_swp_tb_get_flags(old_tb);\n+\t\t\t/*\n+\t\t\t * If swp_tb_val carries SWP_TB_COUNT_MAX, the\n+\t\t\t * real overflow count lives in ci-\u003eextend_table.\n+\t\t\t * Pull it so the new PFN carries the correct count\n+\t\t\t * and won't later crash on a missing extend_table.\n+\t\t\t */\n+\t\t\tif (zswap_swp_tb_get_count(old_tb) == SWP_TB_COUNT_MAX) {\n+\t\t\t\tunsigned char real = SWP_TB_COUNT_MAX - 1;\n+\t\t\t\tif (ci-\u003eextend_table \u0026\u0026 ci-\u003eextend_table[ci_off])\n+\t\t\t\t\treal = ci-\u003eextend_table[ci_off];\n+\t\t\t\tpflags = (real \u003c\u003c (SWP_TB_FLAGS_BITS - SWP_TB_COUNT_BITS)) |\n+\t\t\t\t\t (pflags \u0026 ((1 \u003c\u003c (SWP_TB_FLAGS_BITS - SWP_TB_COUNT_BITS)) - 1));\n+\t\t\t}\n+\t\t\tif (zentry \u0026\u0026 !*zentry)\n+\t\t\t\t*zentry = swp_tb_to_pointer(old_tb);\n+\t\t\t__swap_table_set(ci, ci_off,\n+\t\t\t\tpfn_to_swp_tb(pfn, pflags));\n+\t\t} else {\n+\t\t\t__swap_table_set(ci, ci_off,\n+\t\t\t\tpfn_to_swp_tb(pfn, __swp_tb_get_flags(old_tb)));\n+\t\t}\n \t} while (++ci_off \u003c ci_end);\n \n \tfolio_ref_add(folio, nr_pages);\n@@ -239,11 +280,12 @@ static void __swap_cache_do_add_folio(struct swap_cluster_info *ci,\n  * that holds the entries.\n  */\n void __swap_cache_add_folio(struct swap_cluster_info *ci,\n-\t\t\t    struct folio *folio, swp_entry_t entry)\n+\t\t\t    struct folio *folio, swp_entry_t entry,\n+\t\t\t    void **zentry)\n {\n \tunsigned long nr_pages = folio_nr_pages(folio);\n \n-\t__swap_cache_do_add_folio(ci, folio, entry);\n+\t__swap_cache_do_add_folio(ci, folio, entry, zentry);\n \tnode_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);\n \tlruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);\n }\n@@ -269,15 +311,28 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,\n \tci_off = ci_start;\n \tdo {\n \t\told_tb = __swap_table_get(ci, ci_off);\n-\t\tWARN_ON_ONCE(!swp_tb_is_folio(old_tb) ||\n-\t\t\t     swp_tb_to_folio(old_tb) != folio);\n-\t\tif (__swp_tb_get_count(old_tb))\n-\t\t\tfolio_swapped = true;\n-\t\telse\n-\t\t\tneed_free = true;\n-\t\t/* If shadow is NULL, we set an empty shadow. */\n-\t\t__swap_table_set(ci, ci_off, shadow_to_swp_tb(shadow,\n-\t\t\t\t __swp_tb_get_flags(old_tb)));\n+\t\t/*\n+\t\t * If zswap already stored data for this slot\n+\t\t * (zswap_store wrote a Pointer entry), leave it\n+\t\t * in place. The count and flags are stored in the\n+\t\t * zswap_entry struct.\n+\t\t */\n+\t\tif (swp_tb_is_pointer(old_tb)) {\n+\t\t\tif (zswap_swp_tb_get_count(old_tb))\n+\t\t\t\tfolio_swapped = true;\n+\t\t\telse\n+\t\t\t\tneed_free = true;\n+\t\t} else {\n+\t\t\tWARN_ON_ONCE(!swp_tb_is_folio(old_tb) ||\n+\t\t\t\t     swp_tb_to_folio(old_tb) != folio);\n+\t\t\tif (__swp_tb_get_count(old_tb))\n+\t\t\t\tfolio_swapped = true;\n+\t\t\telse\n+\t\t\t\tneed_free = true;\n+\t\t\t/* If shadow is NULL, we set an empty shadow. */\n+\t\t\t__swap_table_set(ci, ci_off, shadow_to_swp_tb(shadow,\n+\t\t\t\t\t __swp_tb_get_flags(old_tb)));\n+\t\t}\n \t} while (++ci_off \u003c ci_end);\n \n \tfolio-\u003eswap.val = 0;\n@@ -288,7 +343,14 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,\n \t} else if (need_free) {\n \t\tci_off = ci_start;\n \t\tdo {\n-\t\t\tif (!__swp_tb_get_count(__swap_table_get(ci, ci_off)))\n+\t\t\tunsigned long tb = __swap_table_get(ci, ci_off);\n+\t\t\tunsigned char cnt;\n+\n+\t\t\tif (swp_tb_is_pointer(tb))\n+\t\t\t\tcnt = zswap_swp_tb_get_count(tb);\n+\t\t\telse\n+\t\t\t\tcnt = __swp_tb_get_count(tb);\n+\t\t\tif (!cnt)\n \t\t\t\t__swap_cluster_free_entries(si, ci, ci_off, 1);\n \t\t} while (++ci_off \u003c ci_end);\n \t}\n@@ -403,7 +465,8 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,\n static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,\n \t\t\t\t\tswp_entry_t targ_entry, gfp_t gfp,\n \t\t\t\t\tunsigned int order, struct vm_fault *vmf,\n-\t\t\t\t\tstruct mempolicy *mpol, pgoff_t ilx)\n+\t\t\t\t\tstruct mempolicy *mpol, pgoff_t ilx,\n+\t\t\t\t\tvoid **zentry)\n {\n \tint err;\n \tswp_entry_t entry;\n@@ -450,7 +513,7 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,\n \n \t__folio_set_locked(folio);\n \t__folio_set_swapbacked(folio);\n-\t__swap_cache_do_add_folio(ci, folio, entry);\n+\t__swap_cache_do_add_folio(ci, folio, entry, zentry);\n \tspin_unlock(\u0026ci-\u003elock);\n \n \tif (mem_cgroup_swapin_charge_folio(folio, memcg_id,\n@@ -507,7 +570,8 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,\n  */\n struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,\n \t\t\t\t     unsigned long orders, struct vm_fault *vmf,\n-\t\t\t\t     struct mempolicy *mpol, pgoff_t ilx)\n+\t\t\t\t     struct mempolicy *mpol, pgoff_t ilx,\n+\t\t\t\t     void **zentry)\n {\n \tint order, err;\n \tstruct folio *ret;\n@@ -520,9 +584,12 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,\n \tif (WARN_ON_ONCE(!orders || (1UL \u003c\u003c order) \u003e SWAPFILE_CLUSTER))\n \t\treturn ERR_PTR(-EINVAL);\n \n+\tif (zentry)\n+\t\t*zentry = NULL;\n+\n \tdo {\n \t\tret = __swap_cache_alloc(ci, targ_entry, gfp, order,\n-\t\t\t\t\t vmf, mpol, ilx);\n+\t\t\t\t\t vmf, mpol, ilx, zentry);\n \t\tif (!IS_ERR(ret))\n \t\t\tbreak;\n \t\terr = PTR_ERR(ret);\n@@ -638,18 +705,20 @@ 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 *zentry;\n \n \tdo {\n \t\tfolio = swap_cache_get_folio(entry);\n \t\tif (folio)\n \t\t\treturn folio;\n-\t\tfolio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);\n+\t\tfolio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx,\n+\t\t\t\t\t       \u0026zentry);\n \t} while (PTR_ERR(folio) == -EEXIST);\n \n \tif (IS_ERR_OR_NULL(folio))\n \t\treturn NULL;\n \n-\tswap_read_folio(folio, plug);\n+\tswap_read_folio(folio, plug, zentry);\n \tif (readahead) {\n \t\tfolio_set_readahead(folio);\n \t\tcount_vm_event(SWAP_RA);\n@@ -678,18 +747,20 @@ 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 *zentry;\n \n \tdo {\n \t\tfolio = swap_cache_get_folio(entry);\n \t\tif (folio)\n \t\t\treturn folio;\n-\t\tfolio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);\n+\t\tfolio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx,\n+\t\t\t\t\t       \u0026zentry);\n \t} while (PTR_ERR(folio) == -EEXIST);\n \n \tif (IS_ERR(folio))\n \t\treturn folio;\n \n-\tswap_read_folio(folio, NULL);\n+\tswap_read_folio(folio, NULL, zentry);\n \treturn folio;\n }\n \ndiff --git a/mm/swap_table.h b/mm/swap_table.h\nindex e6613e62f8d0f6..6318bf8aa76b0c 100644\n--- a/mm/swap_table.h\n+++ b/mm/swap_table.h\n@@ -28,7 +28,7 @@ struct swap_memcg_table {\n  * NULL:     |---------------- 0 ---------------| - Free slot\n  * Shadow:   |SWAP_COUNT|Z|---- SHADOW_VAL ---|1| - Swapped out slot\n  * PFN:      |SWAP_COUNT|Z|------ PFN -------|10| - Cached slot\n- * Pointer:  |----------- Pointer ----------|100| - (Unused)\n+ * Pointer:  |----------- Pointer ----------|100| - Zswap compressed entry\n  * Bad:      |------------- 1 -------------|1000| - Bad slot\n  *\n  * COUNT is `SWP_TB_COUNT_BITS` long, Z is the `SWP_TB_ZERO_FLAG` bit,\n@@ -49,9 +49,11 @@ struct swap_memcg_table {\n  * - PFN: Swap slot is in use, and cached. Memcg info is recorded on the page\n  *   struct.\n  *\n- * - Pointer: Unused yet. `0b100` is reserved for potential pointer usage\n- *   because only the lower three bits can be used as a marker for 8 bytes\n- *   aligned pointers.\n+ * - Pointer: Stores a direct pointer to a zswap_entry when the swap slot's\n+ *   compressed data resides in zswap. The swap count (if any) is stored in\n+ *   the zswap_entry struct rather than inline, since the pointer occupies\n+ *   all upper bits. `0b100` is used as the marker because zswap_entry\n+ *   pointers are at least 8-byte aligned, leaving the lower three bits free.\n  *\n  * - Bad: Swap slot is reserved, protects swap header or holes on swap devices.\n  */\n@@ -78,6 +80,9 @@ struct swap_memcg_table {\n /* The first flag is zero bit (SWAP_TABLE_HAS_ZEROFLAG) */\n #define SWP_TB_ZERO_FLAG\tBIT(BITS_PER_LONG - SWP_TB_FLAGS_BITS)\n \n+/* Pointer: zswap_entry pointer stored directly, ends with 0b100 */\n+#define SWP_TB_POINTER_MARK\t0b100UL\n+\n /* Bad slot: ends with 0b1000 and rests of bits are all 1 */\n #define SWP_TB_BAD\t\t((~0UL) \u003c\u003c 3)\n \n@@ -166,6 +171,11 @@ static inline bool swp_tb_is_bad(unsigned long swp_tb)\n \treturn swp_tb == SWP_TB_BAD;\n }\n \n+static inline bool swp_tb_is_pointer(unsigned long swp_tb)\n+{\n+\treturn (swp_tb \u0026 (BIT(3) - 1)) == SWP_TB_POINTER_MARK;\n+}\n+\n static inline bool swp_tb_is_countable(unsigned long swp_tb)\n {\n \treturn (swp_tb_is_shadow(swp_tb) || swp_tb_is_folio(swp_tb) ||\n@@ -188,6 +198,23 @@ static inline void *swp_tb_to_shadow(unsigned long swp_tb)\n \treturn (void *)(swp_tb \u0026 ~SWP_TB_FLAGS_MASK);\n }\n \n+static inline unsigned long pointer_to_swp_tb(void *ptr)\n+{\n+\tunsigned long val = (unsigned long)ptr;\n+\n+\tBUILD_BUG_ON(sizeof(unsigned long) != sizeof(void *));\n+\t/* Pointers are 8-byte aligned, low 3 bits must be clear */\n+\tVM_WARN_ON_ONCE(val \u0026 (BIT(3) - 1));\n+\treturn val | SWP_TB_POINTER_MARK;\n+}\n+\n+static inline void *swp_tb_to_pointer(unsigned long swp_tb)\n+{\n+\tVM_WARN_ON(!swp_tb_is_pointer(swp_tb));\n+\t/* Clear the low 3-bit marker to recover the original pointer */\n+\treturn (void *)(swp_tb \u0026 ~((unsigned long)(BIT(3) - 1)));\n+}\n+\n static inline unsigned char __swp_tb_get_count(unsigned long swp_tb)\n {\n \tVM_WARN_ON(!swp_tb_is_countable(swp_tb));\ndiff --git a/mm/swapfile.c b/mm/swapfile.c\nindex a602e5820513a8..18925e462571b1 100644\n--- a/mm/swapfile.c\n+++ b/mm/swapfile.c\n@@ -935,7 +935,8 @@ static bool __swap_cluster_alloc_entries(struct swap_info_struct *si,\n \t\tnr_pages = 1 \u003c\u003c order;\n \t\tswap_cluster_assert_empty(ci, ci_off, nr_pages, false);\n \t\t__swap_cache_add_folio(ci, folio, swp_entry(si-\u003etype,\n-\t\t\t\t\t\t\t    ci_off + cluster_offset(si, ci)));\n+\t\t\t\t\t\t\t    ci_off + cluster_offset(si, ci)),\n+\t\t\t\t       NULL);\n \t} else if (IS_ENABLED(CONFIG_HIBERNATION)) {\n \t\torder = 0;\n \t\tnr_pages = 1;\n@@ -1536,25 +1537,51 @@ static void __swap_cluster_put_entry(struct swap_cluster_info *ci,\n \n \tlockdep_assert_held(\u0026ci-\u003elock);\n \tswp_tb = __swap_table_get(ci, ci_off);\n+\n+\t/* Pointer entries store count in the zswap_entry struct */\n+\tif (swp_tb_is_pointer(swp_tb)) {\n+\t\tzswap_swp_tb_put_count(swp_tb, ci, ci_off);\n+\t\treturn;\n+\t}\n+\n \tcount = __swp_tb_get_count(swp_tb);\n \n \tVM_WARN_ON_ONCE(count \u003c= 0);\n \tVM_WARN_ON_ONCE(count \u003e SWP_TB_COUNT_MAX);\n-\n-\tif (count == SWP_TB_COUNT_MAX) {\n-\t\tcount = ci-\u003eextend_table[ci_off];\n-\t\t/* Overflow starts with SWP_TB_COUNT_MAX */\n-\t\tVM_WARN_ON_ONCE(count \u003c SWP_TB_COUNT_MAX);\n-\t\tcount--;\n-\t\tif (count == (SWP_TB_COUNT_MAX - 1)) {\n-\t\t\tci-\u003eextend_table[ci_off] = 0;\n-\t\t\t__swap_table_set(ci, ci_off, __swp_tb_mk_count(swp_tb, count));\n+\t\tif (count == SWP_TB_COUNT_MAX) {\n+\t\t\tif (!ci-\u003eextend_table) {\n+\t\t\t\t/*\n+\t\t\t\t * MAX without extend_table: MAX is the real\n+\t\t\t\t * count.  Decrement and write back inline.\n+\t\t\t\t */\n+\t\t\t\tcount--;\n+\t\t\t\t__swap_table_set(ci, ci_off,\n+\t\t\t\t\t__swp_tb_mk_count(swp_tb, count));\n+\t\t\t} else {\n+\t\t\t\t/*\n+\t\t\t\t * MAX with extend_table: real count lives in\n+\t\t\t\t * ci-\u003eextend_table[ci_off].  Pull, decrement\n+\t\t\t\t * it, and either move it back inline (if now\n+\t\t\t\t * fits) or write it back to the extend table.\n+\t\t\t\t */\n+\t\t\t\tcount = ci-\u003eextend_table[ci_off];\n+\t\t\t\t/* Overflow starts with SWP_TB_COUNT_MAX */\n+\t\t\t\tVM_WARN_ON_ONCE(count \u003c SWP_TB_COUNT_MAX);\n+\t\t\t\tcount--;\n+\t\t\t\tif (count == (SWP_TB_COUNT_MAX - 1)) {\n+\t\t\t\t\tci-\u003eextend_table[ci_off] = 0;\n+\t\t\t\t\t__swap_table_set(ci, ci_off,\n+\t\t\t\t\t\t__swp_tb_mk_count(swp_tb, count));\n+\t\t\t\t} else {\n+\t\t\t\t\tci-\u003eextend_table[ci_off] = count;\n+\t\t\t\t}\n+\t\t\t}\n \t\t} else {\n-\t\t\tci-\u003eextend_table[ci_off] = count;\n+\t\t\t/* count \u003c MAX: simple inline decrement */\n+\t\t\tcount--;\n+\t\t\t__swap_table_set(ci, ci_off,\n+\t\t\t\t__swp_tb_mk_count(swp_tb, count));\n \t\t}\n-\t} else {\n-\t\t__swap_table_set(ci, ci_off, __swp_tb_mk_count(swp_tb, --count));\n-\t}\n \n \t/*\n \t * `SWP_TB_COUNT_MAX - 1` triggers extend table allocation. If the\n@@ -1596,7 +1623,18 @@ static void swap_put_entries_cluster(struct swap_info_struct *si,\n \tci_end = ci_off + nr;\n \tdo {\n \t\tswp_tb = __swap_table_get(ci, ci_off);\n-\t\tif (swp_tb_get_count(swp_tb) == 1) {\n+\t\t/*\n+\t\t * Pointer entries store count in the zswap_entry;\n+\t\t * handle them before swp_tb_get_count (which returns\n+\t\t * -EINVAL for non-countable types).\n+\t\t */\n+\t\tif (swp_tb_is_pointer(swp_tb)) {\n+\t\t\tif (zswap_swp_tb_get_count(swp_tb) == 1) {\n+\t\t\t\tif (ci_batch == -1)\n+\t\t\t\t\tci_batch = ci_off;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t} else if (swp_tb_get_count(swp_tb) == 1) {\n \t\t\t/* count == 1 and non-cached slots will be batch freed. */\n \t\t\tif (!swp_tb_is_folio(swp_tb)) {\n \t\t\t\tif (ci_batch == -1)\n@@ -1647,6 +1685,11 @@ static int __swap_cluster_dup_entry(struct swap_cluster_info *ci,\n \t/* Bad or special slots can't be handled */\n \tif (WARN_ON_ONCE(swp_tb_is_bad(swp_tb)))\n \t\treturn -EINVAL;\n+\n+\t/* Pointer entries store count in the zswap_entry struct */\n+\tif (swp_tb_is_pointer(swp_tb))\n+\t\treturn zswap_swp_tb_dup_count(swp_tb, ci, ci_off);\n+\n \tcount = __swp_tb_get_count(swp_tb);\n \t/* Must be either cached or have a count already */\n \tif (WARN_ON_ONCE(!count \u0026\u0026 !swp_tb_is_folio(swp_tb)))\n@@ -1923,13 +1966,26 @@ void __swap_cluster_free_entries(struct swap_info_struct *si,\n \t\told_tb = __swap_table_get(ci, ci_off);\n \t\t/*\n \t\t * Freeing is done after release of the last swap count\n-\t\t * ref, or after swap cache is dropped\n+\t\t * ref, or after swap cache is dropped. A Pointer entry\n+\t\t * means zswap has a compressed copy; the xarray still\n+\t\t * holds the entry and zswap_invalidate will free it.\n \t\t */\n-\t\tVM_WARN_ON(!swp_tb_is_shadow(old_tb) || __swp_tb_get_count(old_tb) \u003e 1);\n+\t\tif (swp_tb_is_pointer(old_tb)) {\n+\t\t\tVM_WARN_ON(zswap_swp_tb_get_count(old_tb) \u003e 1);\n+\t\t\t/*\n+\t\t\t * Free the zswap entry now under ci-\u003elock while\n+\t\t\t * the Pointer is still in the swap table.  Once\n+\t\t\t * we null the slot, the entry would be leaked.\n+\t\t\t */\n+\t\t\tzswap_entry_free(swp_tb_to_pointer(old_tb));\n+\t\t} else {\n+\t\t\tVM_WARN_ON(!swp_tb_is_shadow(old_tb) ||\n+\t\t\t\t   __swp_tb_get_count(old_tb) \u003e 1);\n+\t\t}\n \n \t\t/* Resetting the slot to NULL also clears the inline flags. */\n \t\t__swap_table_set(ci, ci_off, null_to_swp_tb());\n-\t\tif (!SWAP_TABLE_HAS_ZEROFLAG)\n+\t\tif (!SWAP_TABLE_HAS_ZEROFLAG \u0026\u0026 !swp_tb_is_pointer(old_tb))\n \t\t\t__swap_table_clear_zero(ci, ci_off);\n \n \t\t/*\n@@ -1961,8 +2017,11 @@ int __swap_count(swp_entry_t entry)\n {\n \tstruct swap_cluster_info *ci = __swap_entry_to_cluster(entry);\n \tunsigned int ci_off = swp_cluster_offset(entry);\n+\tunsigned long swp_tb = __swap_table_get(ci, ci_off);\n \n-\treturn swp_tb_get_count(__swap_table_get(ci, ci_off));\n+\tif (swp_tb_is_pointer(swp_tb))\n+\t\treturn zswap_swp_tb_get_count(swp_tb);\n+\treturn swp_tb_get_count(swp_tb);\n }\n \n /**\n@@ -1980,6 +2039,8 @@ bool swap_entry_swapped(struct swap_info_struct *si, swp_entry_t entry)\n \tswp_tb = swap_table_get(ci, offset % SWAPFILE_CLUSTER);\n \tswap_cluster_unlock(ci);\n \n+\tif (swp_tb_is_pointer(swp_tb))\n+\t\treturn zswap_swp_tb_get_count(swp_tb) \u003e 0;\n \treturn swp_tb_get_count(swp_tb) \u003e 0;\n }\n \n@@ -2020,7 +2081,7 @@ int swp_swapcount(swp_entry_t entry)\n  *\n  * Context: Caller must ensure the folio is locked and in the swap cache.\n  */\n-static bool folio_maybe_swapped(struct folio *folio)\n+bool folio_maybe_swapped(struct folio *folio)\n {\n \tswp_entry_t entry = folio-\u003eswap;\n \tstruct swap_cluster_info *ci;\n@@ -2556,8 +2617,12 @@ static int unuse_pte_range(struct vm_area_struct *vma, pmd_t *pmd,\n \t\tif (!folio) {\n \t\t\tswp_tb = swap_table_get(__swap_entry_to_cluster(entry),\n \t\t\t\t\t\tswp_cluster_offset(entry));\n-\t\t\tif (swp_tb_get_count(swp_tb) \u003c= 0)\n+\t\t\tif (swp_tb_is_pointer(swp_tb)) {\n+\t\t\t\tif (zswap_swp_tb_get_count(swp_tb) \u003c= 0)\n+\t\t\t\t\tcontinue;\n+\t\t\t} else if (swp_tb_get_count(swp_tb) \u003c= 0) {\n \t\t\t\tcontinue;\n+\t\t\t}\n \t\t\treturn -ENOMEM;\n \t\t}\n \ndiff --git a/mm/zswap.c b/mm/zswap.c\nindex 761cd699e0a3e6..f9762d0a596ab4 100644\n--- a/mm/zswap.c\n+++ b/mm/zswap.c\n@@ -38,6 +38,7 @@\n #include \u003clinux/zsmalloc.h\u003e\n \n #include \"swap.h\"\n+#include \"swap_table.h\"\n #include \"internal.h\"\n \n /*********************************\n@@ -182,6 +183,10 @@ static struct shrinker *zswap_shrinker;\n  *              writeback logic. The entry is only reclaimed by the writeback\n  *              logic if referenced is unset. See comments in the shrinker\n  *              section for context.\n+ * swp_tb_val - the original swap table entry value saved when this zswap_entry\n+ *              replaced a Shadow or PFN entry. It preserves the swap count,\n+ *              zero flag, and working set shadow information (SHADOW_VAL)\n+ *              that the Shadow entry carried.\n  * pool - the zswap_pool the entry's data is in\n  * handle - zsmalloc allocation handle that stores the compressed page data\n  * objcg - the obj_cgroup that the compressed memory is charged to\n@@ -191,15 +196,13 @@ struct zswap_entry {\n \tswp_entry_t swpentry;\n \tunsigned int length;\n \tbool referenced;\n+\tunsigned long swp_tb_val;\n \tstruct zswap_pool *pool;\n \tunsigned long handle;\n \tstruct obj_cgroup *objcg;\n \tstruct list_head lru;\n };\n \n-static struct xarray *zswap_trees[MAX_SWAPFILES];\n-static unsigned int nr_zswap_trees[MAX_SWAPFILES];\n-\n /* RCU-protected iteration */\n static LIST_HEAD(zswap_pools);\n /* protects zswap_pools list modification */\n@@ -225,15 +228,6 @@ static bool zswap_has_pool;\n * helpers and fwd declarations\n **********************************/\n \n-/* One swap address space for each 64M swap space */\n-#define ZSWAP_ADDRESS_SPACE_SHIFT 14\n-#define ZSWAP_ADDRESS_SPACE_PAGES (1 \u003c\u003c ZSWAP_ADDRESS_SPACE_SHIFT)\n-static inline struct xarray *swap_zswap_tree(swp_entry_t swp)\n-{\n-\treturn \u0026zswap_trees[swp_type(swp)][swp_offset(swp)\n-\t\t\u003e\u003e ZSWAP_ADDRESS_SPACE_SHIFT];\n-}\n-\n #define zswap_pool_debug(msg, p)\t\t\t\\\n \tpr_debug(\"%s pool %s\\n\", msg, (p)-\u003etfm_name)\n \n@@ -762,7 +756,7 @@ static void zswap_entry_cache_free(struct zswap_entry *entry)\n  * Carries out the common pattern of freeing an entry's zsmalloc allocation,\n  * freeing the entry itself, and decrementing the number of stored pages.\n  */\n-static void zswap_entry_free(struct zswap_entry *entry)\n+void zswap_entry_free(struct zswap_entry *entry)\n {\n \tzswap_lru_del(\u0026zswap_list_lru, entry);\n \tzs_free(entry-\u003epool-\u003ezs_pool, entry-\u003ehandle);\n@@ -987,11 +981,12 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)\n static int zswap_writeback_entry(struct zswap_entry *entry,\n \t\t\t\t swp_entry_t swpentry)\n {\n-\tstruct xarray *tree;\n \tpgoff_t offset = swp_offset(swpentry);\n \tstruct folio *folio;\n \tstruct mempolicy *mpol;\n \tstruct swap_info_struct *si;\n+\tstruct swap_cluster_info *ci;\n+\tunsigned long swp_tb;\n \tint ret = 0;\n \n \t/* try to allocate swap cache folio */\n@@ -999,9 +994,25 @@ static int zswap_writeback_entry(struct zswap_entry *entry,\n \tif (!si)\n \t\treturn -EEXIST;\n \n+\t/*\n+\t * Verify the swap table Pointer entry still references this\n+\t * zswap_entry. If the entry was invalidated or replaced by a\n+\t * concurrent store, the Pointer will be gone or point elsewhere.\n+\t */\n+\tci = __swap_offset_to_cluster(si, offset);\n+\tspin_lock(\u0026ci-\u003elock);\n+\tswp_tb = __swap_table_get(ci, offset % SWAPFILE_CLUSTER);\n+\tif (!swp_tb_is_pointer(swp_tb) ||\n+\t    swp_tb_to_pointer(swp_tb) != entry) {\n+\t\tspin_unlock(\u0026ci-\u003elock);\n+\t\tput_swap_device(si);\n+\t\treturn -ENOMEM;\n+\t}\n+\tspin_unlock(\u0026ci-\u003elock);\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+\t\t\t\t       NO_INTERLEAVE_INDEX, NULL);\n \tput_swap_device(si);\n \n \t/*\n@@ -1015,27 +1026,15 @@ static int zswap_writeback_entry(struct zswap_entry *entry,\n \t\treturn PTR_ERR(folio);\n \n \t/*\n-\t * folio is locked, and the swapcache is now secured against\n-\t * concurrent swapping to and from the slot, and concurrent\n-\t * swapoff so we can safely dereference the zswap tree here.\n-\t * Verify that the swap entry hasn't been invalidated and recycled\n-\t * behind our backs, to avoid overwriting a new swap folio with\n-\t * old compressed data. Only when this is successful can the entry\n-\t * be dereferenced.\n+\t * The folio is now locked and in swap cache, so concurrent\n+\t * stores are blocked. __swap_cache_do_add_folio already\n+\t * overwrote the Pointer entry with a PFN.\n \t */\n-\ttree = swap_zswap_tree(swpentry);\n-\tif (entry != xa_load(tree, offset)) {\n-\t\tret = -ENOMEM;\n-\t\tgoto out;\n-\t}\n-\n \tif (!zswap_decompress(entry, folio)) {\n \t\tret = -EIO;\n \t\tgoto out;\n \t}\n \n-\txa_erase(tree, offset);\n-\n \tcount_vm_event(ZSWPWB);\n \tif (entry-\u003eobjcg)\n \t\tcount_objcg_events(entry-\u003eobjcg, ZSWPWB, 1);\n@@ -1105,36 +1104,22 @@ static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_o\n \t}\n \n \t/*\n-\t * As soon as we drop the LRU lock, the entry can be freed by\n-\t * a concurrent invalidation. This means the following:\n-\t *\n-\t * 1. We extract the swp_entry_t to the stack, allowing\n-\t *    zswap_writeback_entry() to pin the swap entry and\n-\t *    then validate the zswap entry against that swap entry's\n-\t *    tree using pointer value comparison. Only when that\n-\t *    is successful can the entry be dereferenced.\n-\t *\n-\t * 2. Usually, objects are taken off the LRU for reclaim. In\n-\t *    this case this isn't possible, because if reclaim fails\n-\t *    for whatever reason, we have no means of knowing if the\n-\t *    entry is alive to put it back on the LRU.\n+\t * After we drop the LRU lock, the entry may be freed by a\n+\t * concurrent swap slot free.  This is safe because\n+\t * zswap_writeback_entry() rechecks the swap table under\n+\t * ci-\u003elock before dereferencing entry; if the Pointer is\n+\t * gone it bails out.\n \t *\n-\t *    So rotate it before dropping the lock. If the entry is\n-\t *    written back or invalidated, the free path will unlink\n-\t *    it. For failures, rotation is the right thing as well.\n-\t *\n-\t *    Temporary failures, where the same entry should be tried\n-\t *    again immediately, almost never happen for this shrinker.\n-\t *    We don't do any trylocking; -ENOMEM comes closest,\n-\t *    but that's extremely rare and doesn't happen spuriously\n-\t *    either. Don't bother distinguishing this case.\n+\t * Rotate the entry before dropping the lock: if it gets\n+\t * written back or invalidated the free path will unlink\n+\t * it from the LRU.  For failures rotation is fine.\n \t */\n \tlist_move_tail(item, \u0026l-\u003elist);\n \n \t/*\n-\t * Once the lru lock is dropped, the entry might get freed. The\n-\t * swpentry is copied to the stack, and entry isn't deref'd again\n-\t * until the entry is verified to still be alive in the tree.\n+\t * The swpentry is copied to the stack so we can pin the swap\n+\t * device and validate the entry's swap table Pointer under\n+\t * ci-\u003elock in zswap_writeback_entry().\n \t */\n \tswpentry = entry-\u003eswpentry;\n \n@@ -1398,7 +1383,11 @@ static bool zswap_store_page(struct page *page,\n \t\t\t     struct zswap_pool *pool)\n {\n \tswp_entry_t page_swpentry = page_swap_entry(page);\n-\tstruct zswap_entry *entry, *old;\n+\tstruct zswap_entry *entry, *old = NULL;\n+\tstruct swap_cluster_info *ci;\n+\tstruct swap_info_struct *si;\n+\tpgoff_t offset = swp_offset(page_swpentry);\n+\tunsigned long swp_tb;\n \n \t/* allocate entry */\n \tentry = zswap_entry_cache_alloc(GFP_KERNEL, page_to_nid(page));\n@@ -1410,31 +1399,49 @@ static bool zswap_store_page(struct page *page,\n \tif (!zswap_compress(page, entry, pool))\n \t\tgoto compress_failed;\n \n-\told = xa_store(swap_zswap_tree(page_swpentry),\n-\t\t       swp_offset(page_swpentry),\n-\t\t       entry, GFP_KERNEL);\n-\tif (xa_is_err(old)) {\n-\t\tint err = xa_err(old);\n-\n-\t\tWARN_ONCE(err != -ENOMEM, \"unexpected xarray error: %d\\n\", err);\n-\t\tzswap_reject_alloc_fail++;\n-\t\tgoto store_failed;\n+\t/*\n+\t * Store the zswap_entry pointer in the swap table as a\n+\t * Pointer-type entry, replacing the PFN or Shadow entry.\n+\t * The full swap table entry value (including working set\n+\t * shadow) is preserved in entry-\u003eswp_tb_val.\n+\t * Folio lock serializes concurrent stores.\n+\t */\n+\tsi = __swap_entry_to_info(page_swpentry);\n+\tci = __swap_offset_to_cluster(si, offset);\n+\tspin_lock(\u0026ci-\u003elock);\n+\tswp_tb = __swap_table_get(ci, offset % SWAPFILE_CLUSTER);\n+\tif (swp_tb_is_countable(swp_tb)) {\n+\t\t/*\n+\t\t * Save the complete original swap table entry value\n+\t\t * (Shadow or PFN), preserving swap count, zero flag,\n+\t\t * and working set shadow information.\n+\t\t */\n+\t\tentry-\u003eswp_tb_val = swp_tb;\n+\t} else if (swp_tb_is_pointer(swp_tb)) {\n+\t\t/*\n+\t\t * Replacing an existing Pointer entry (e.g. from a\n+\t\t * redirtied folio). Inherit the saved swap table value.\n+\t\t */\n+\t\told = swp_tb_to_pointer(swp_tb);\n+\t\tentry-\u003eswp_tb_val = old-\u003eswp_tb_val;\n \t}\n-\n+\t__swap_table_set(ci, offset % SWAPFILE_CLUSTER,\n+\t\t\t pointer_to_swp_tb(entry));\n+\tspin_unlock(\u0026ci-\u003elock);\n \t/*\n-\t * We may have had an existing entry that became stale when\n-\t * the folio was redirtied and now the new version is being\n-\t * swapped out. Get rid of the old.\n+\t * If we had an existing zswap entry from the swap table\n+\t * (e.g. from a previous redirtied folio), get rid of it\n+\t * outside the lock.\n \t */\n \tif (old)\n \t\tzswap_entry_free(old);\n \n \t/*\n-\t * The entry is successfully compressed and stored in the tree, there is\n-\t * no further possibility of failure. Grab refs to the pool and objcg,\n-\t * charge zswap memory, and increment zswap_stored_pages.\n+\t * The entry is successfully compressed and stored in the swap table,\n+\t * there is no further possibility of failure. Grab refs to the pool\n+\t * and objcg, charge zswap memory, and increment zswap_stored_pages.\n \t * The opposite actions will be performed by zswap_entry_free()\n-\t * when the entry is removed from the tree.\n+\t * when the entry is removed from the swap table.\n \t */\n \tzswap_pool_get(pool);\n \tif (objcg) {\n@@ -1446,8 +1453,8 @@ static bool zswap_store_page(struct page *page,\n \t\tatomic_long_inc(\u0026zswap_stored_incompressible_pages);\n \n \t/*\n-\t * We finish initializing the entry while it's already in xarray.\n-\t * This is safe because:\n+\t * We finish initializing the entry while it's already in the\n+\t * swap table. This is safe because:\n \t *\n \t * 1. Concurrent stores and invalidations are excluded by folio lock.\n \t *\n@@ -1466,8 +1473,6 @@ static bool zswap_store_page(struct page *page,\n \n \treturn true;\n \n-store_failed:\n-\tzs_free(pool-\u003ezs_pool, entry-\u003ehandle);\n compress_failed:\n \tzswap_entry_cache_free(entry);\n \treturn false;\n@@ -1546,11 +1551,31 @@ bool zswap_store(struct folio *folio)\n \t\tunsigned type = swp_type(swp);\n \t\tpgoff_t offset = swp_offset(swp);\n \t\tstruct zswap_entry *entry;\n-\t\tstruct xarray *tree;\n+\t\tstruct swap_cluster_info *ci;\n+\t\tstruct swap_info_struct *sis;\n+\t\tunsigned long swp_tb;\n \n+\t\tsis = __swap_type_to_info(type);\n \t\tfor (index = 0; index \u003c nr_pages; ++index) {\n-\t\t\ttree = swap_zswap_tree(swp_entry(type, offset + index));\n-\t\t\tentry = xa_erase(tree, offset + index);\n+\t\t\tci = __swap_offset_to_cluster(sis, offset + index);\n+\t\t\tspin_lock(\u0026ci-\u003elock);\n+\t\t\tswp_tb = __swap_table_get(ci, (offset + index) % SWAPFILE_CLUSTER);\n+\t\t\tif (swp_tb_is_pointer(swp_tb)) {\n+\t\t\t\tentry = swp_tb_to_pointer(swp_tb);\n+\t\t\t\t/*\n+\t\t\t\t * Restore the original swap table entry\n+\t\t\t\t * (Shadow or PFN) that was saved when the\n+\t\t\t\t * Pointer entry was created, rather than\n+\t\t\t\t * zeroing the slot.  This preserves the swap\n+\t\t\t\t * count and flags so the swap cache folio\n+\t\t\t\t * can be correctly cleaned up later.\n+\t\t\t\t */\n+\t\t\t\t__swap_table_set(ci, (offset + index) % SWAPFILE_CLUSTER,\n+\t\t\t\t\t\t entry-\u003eswp_tb_val);\n+\t\t\t} else {\n+\t\t\t\tentry = NULL;\n+\t\t\t}\n+\t\t\tspin_unlock(\u0026ci-\u003elock);\n \t\t\tif (entry)\n \t\t\t\tzswap_entry_free(entry);\n \t\t}\n@@ -1562,6 +1587,11 @@ bool zswap_store(struct folio *folio)\n /**\n  * zswap_load() - load a folio from zswap\n  * @folio: folio to load\n+ * @zentry: zswap_entry pointer extracted from swap table, or NULL\n+ *\n+ * The zswap_entry was extracted from the swap table Pointer entry by\n+ * __swap_cache_do_add_folio before it overwrote the entry with a PFN.\n+ * If @zentry is NULL, the slot was not backed by zswap.\n  *\n  * Return: 0 on success, with the folio unlocked and marked up-to-date, or one\n  * of the following error codes:\n@@ -1579,12 +1609,9 @@ bool zswap_store(struct folio *folio)\n  *  -ENOENT: if the swapped out content was not in zswap. The folio remains\n  *  locked on return.\n  */\n-int zswap_load(struct folio *folio)\n+int zswap_load(struct folio *folio, void *zentry)\n {\n-\tswp_entry_t swp = folio-\u003eswap;\n-\tpgoff_t offset = swp_offset(swp);\n-\tstruct xarray *tree = swap_zswap_tree(swp);\n-\tstruct zswap_entry *entry;\n+\tstruct zswap_entry *entry = zentry;\n \n \tVM_WARN_ON_ONCE(!folio_test_locked(folio));\n \tVM_WARN_ON_ONCE(!folio_test_swapcache(folio));\n@@ -1602,7 +1629,6 @@ int zswap_load(struct folio *folio)\n \t\treturn -EINVAL;\n \t}\n \n-\tentry = xa_load(tree, offset);\n \tif (!entry)\n \t\treturn -ENOENT;\n \n@@ -1625,7 +1651,6 @@ int zswap_load(struct folio *folio)\n \t * compression work.\n \t */\n \tfolio_mark_dirty(folio);\n-\txa_erase(tree, offset);\n \tzswap_entry_free(entry);\n \n \tfolio_unlock(folio);\n@@ -1635,52 +1660,159 @@ int zswap_load(struct folio *folio)\n void zswap_invalidate(swp_entry_t swp)\n {\n \tpgoff_t offset = swp_offset(swp);\n-\tstruct xarray *tree = swap_zswap_tree(swp);\n+\tstruct swap_cluster_info *ci;\n+\tstruct swap_info_struct *si;\n+\tunsigned long swp_tb;\n \tstruct zswap_entry *entry;\n \n-\tif (xa_empty(tree))\n+\tsi = __swap_type_to_info(swp_type(swp));\n+\tci = __swap_offset_to_cluster(si, offset);\n+\t/*\n+\t * Do a lockless peek first.  If ci-\u003elock is already held by\n+\t * the caller (e.g. __swap_cluster_free_entries), and the\n+\t * Pointer has already been cleared, we can bail out without\n+\t * self-deadlocking.\n+\t */\n+\tswp_tb = __swap_table_get(ci, offset % SWAPFILE_CLUSTER);\n+\tif (!swp_tb_is_pointer(swp_tb))\n \t\treturn;\n \n-\tentry = xa_erase(tree, offset);\n+\tspin_lock(\u0026ci-\u003elock);\n+\tswp_tb = __swap_table_get(ci, offset % SWAPFILE_CLUSTER);\n+\tif (swp_tb_is_pointer(swp_tb)) {\n+\t\tentry = swp_tb_to_pointer(swp_tb);\n+\t\t/*\n+\t\t * Restore the original swap table entry (Shadow or PFN)\n+\t\t * that was saved in swp_tb_val when the Pointer entry\n+\t\t * was created.  This preserves swap count and flags\n+\t\t * for the subsequent slot-free path.\n+\t\t */\n+\t\t__swap_table_set(ci, offset % SWAPFILE_CLUSTER,\n+\t\t\t\t entry-\u003eswp_tb_val);\n+\t} else {\n+\t\tentry = NULL;\n+\t}\n+\tspin_unlock(\u0026ci-\u003elock);\n+\n \tif (entry)\n \t\tzswap_entry_free(entry);\n }\n \n-int zswap_swapon(int type, unsigned long nr_pages)\n+/*\n+ * Extract the working set shadow from a Pointer-type swap table entry.\n+ * The shadow is recovered from zswap_entry-\u003eswp_tb_val which preserves\n+ * the original Shadow entry value. The shadow can then be used by\n+ * workingset_refault() for refault distance calculation.\n+ */\n+void *zswap_swp_tb_get_shadow(unsigned long swp_tb)\n {\n-\tstruct xarray *trees, *tree;\n-\tunsigned int nr, i;\n+\tstruct zswap_entry *entry = swp_tb_to_pointer(swp_tb);\n+\tunsigned long saved = entry-\u003eswp_tb_val;\n \n-\tnr = DIV_ROUND_UP(nr_pages, ZSWAP_ADDRESS_SPACE_PAGES);\n-\ttrees = kvzalloc_objs(*tree, nr);\n-\tif (!trees) {\n-\t\tpr_err(\"alloc failed, zswap disabled for swap type %d\\n\", type);\n-\t\treturn -ENOMEM;\n-\t}\n+\t/*\n+\t * swp_tb_val preserves the original swap table entry.  Only\n+\t * Shadow entries carry workingset information; PFN and NULL\n+\t * entries have no shadow to extract.\n+\t */\n+\tif (!swp_tb_is_shadow(saved))\n+\t\treturn NULL;\n+\n+\treturn (void *)(saved \u0026 ~SWP_TB_FLAGS_MASK);\n+}\n+\n+/*\n+ * Read the swap count from a Pointer-type swap table entry. The count\n+ * and flags are stored in zswap_entry-\u003eswp_tb_val which preserves the\n+ * original swap table entry value (Shadow or PFN format).\n+ */\n+unsigned char zswap_swp_tb_get_count(unsigned long swp_tb)\n+{\n+\tstruct zswap_entry *entry = swp_tb_to_pointer(swp_tb);\n+\n+\treturn __swp_tb_get_count(entry-\u003eswp_tb_val);\n+}\n+\n+/*\n+ * Read the flags from a Pointer-type swap table entry.\n+ */\n+unsigned char zswap_swp_tb_get_flags(unsigned long swp_tb)\n+{\n+\tstruct zswap_entry *entry = swp_tb_to_pointer(swp_tb);\n+\n+\treturn __swp_tb_get_flags(entry-\u003eswp_tb_val);\n+}\n \n-\tfor (i = 0; i \u003c nr; i++)\n-\t\txa_init(trees + i);\n+/*\n+ * Increment the swap count of a Pointer-type swap table entry.\n+ * The count is stored in zswap_entry-\u003eswp_tb_val for counts below\n+ * SWP_TB_COUNT_MAX, and overflows into the cluster's extend_table\n+ * just like normal Shadow/PFN entries do.  Returns 0 on success,\n+ * -ENOMEM if count would overflow and no extend_table is available.\n+ */\n+int zswap_swp_tb_dup_count(unsigned long swp_tb,\n+\t\t\t   struct swap_cluster_info *ci, unsigned int ci_off)\n+{\n+\tstruct zswap_entry *entry = swp_tb_to_pointer(swp_tb);\n+\tunsigned char count = __swp_tb_get_count(entry-\u003eswp_tb_val);\n \n-\tnr_zswap_trees[type] = nr;\n-\tzswap_trees[type] = trees;\n+\tif (count \u003c SWP_TB_COUNT_MAX) {\n+\t\tentry-\u003eswp_tb_val = __swp_tb_mk_count(entry-\u003eswp_tb_val,\n+\t\t\t\t\t\t      count + 1);\n+\t\treturn 0;\n+\t}\n+\t/* count == MAX, overflow into extend_table */\n+\tif (!ci-\u003eextend_table)\n+\t\treturn -ENOMEM;\n+\tcount = ci-\u003eextend_table[ci_off];\n+\tif (count == 0)\n+\t\tcount = SWP_TB_COUNT_MAX;\n+\tcount++;\n+\tci-\u003eextend_table[ci_off] = count;\n \treturn 0;\n }\n \n-void zswap_swapoff(int type)\n+/*\n+ * Decrement the swap count of a Pointer-type swap table entry.\n+ * Handles both inline count (in swp_tb_val) and overflowed count\n+ * (in ci-\u003eextend_table), matching the logic used by normal entries.\n+ */\n+void zswap_swp_tb_put_count(unsigned long swp_tb,\n+\t\t\t    struct swap_cluster_info *ci, unsigned int ci_off)\n {\n-\tstruct xarray *trees = zswap_trees[type];\n-\tunsigned int i;\n+\tstruct zswap_entry *entry = swp_tb_to_pointer(swp_tb);\n+\tunsigned char count = __swp_tb_get_count(entry-\u003eswp_tb_val);\n+\n+\tVM_WARN_ON_ONCE(count == 0);\n \n-\tif (!trees)\n+\tif (count == SWP_TB_COUNT_MAX) {\n+\t\tif (!ci-\u003eextend_table) {\n+\t\t\tcount--;\n+\t\t\tentry-\u003eswp_tb_val = __swp_tb_mk_count(entry-\u003eswp_tb_val, count);\n+\t\t\treturn;\n+\t\t}\n+\t\tcount = ci-\u003eextend_table[ci_off];\n+\t\t/* Overflow starts with SWP_TB_COUNT_MAX */\n+\t\tVM_WARN_ON_ONCE(count \u003c SWP_TB_COUNT_MAX);\n+\t\tcount--;\n+\t\tif (count == (SWP_TB_COUNT_MAX - 1)) {\n+\t\t\tci-\u003eextend_table[ci_off] = 0;\n+\t\t\tentry-\u003eswp_tb_val = __swp_tb_mk_count(entry-\u003eswp_tb_val,\n+\t\t\t\t\t\t\t      count);\n+\t\t} else {\n+\t\t\tci-\u003eextend_table[ci_off] = count;\n+\t\t}\n \t\treturn;\n+\t}\n+\tentry-\u003eswp_tb_val = __swp_tb_mk_count(entry-\u003eswp_tb_val, count - 1);\n+}\n \n-\t/* try_to_unuse() invalidated all the entries already */\n-\tfor (i = 0; i \u003c nr_zswap_trees[type]; i++)\n-\t\tWARN_ON_ONCE(!xa_empty(trees + i));\n+int zswap_swapon(int type, unsigned long nr_pages)\n+{\n+\treturn 0;\n+}\n \n-\tkvfree(trees);\n-\tnr_zswap_trees[type] = 0;\n-\tzswap_trees[type] = NULL;\n+void zswap_swapoff(int type)\n+{\n }\n \n /*********************************\n"
}

2/1 2026/07/07 15:11 agent patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "ZSWAP",
    "ZSWAP_DEFAULT_ON"
  ],
  "FocusSymbols": [
    "zswap_store",
    "zswap_load",
    "zswap_invalidate",
    "zswap_writeback_entry",
    "__swap_cache_do_add_folio",
    "__swap_cache_do_del_folio",
    "__swap_cluster_put_entry",
    "__swap_cluster_free_entries",
    "__swap_cluster_dup_entry",
    "zswap_swp_tb_dup_count",
    "zswap_swp_tb_put_count"
  ],
  "Reasoning": "The patch fundamentally changes how zswap tracks compressed pages by embedding `zswap_entry` pointers directly into the core swap table (`swap_cluster_info`) rather than using a separate xarray (`zswap_trees`). This affects core swap allocation, freeing, and lookup paths, requiring fuzzing to ensure no memory leaks, use-after-frees, or swap accounting errors occur during concurrent swap operations.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit f201198587631215f708f5886a9810323fbc2399
Author: syz-cluster <triage@syzkaller.com>
Date:   Tue Jul 7 15:11:18 2026 +0000

    syz-cluster: applied patch under review

diff --git a/include/linux/zswap.h b/include/linux/zswap.h
index 30c193a1207e16..4f55c8d0c4c542 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -26,7 +26,7 @@ struct zswap_lruvec_state {
 
 unsigned long zswap_total_pages(void);
 bool zswap_store(struct folio *folio);
-int zswap_load(struct folio *folio);
+int zswap_load(struct folio *folio, void *zentry);
 void zswap_invalidate(swp_entry_t swp);
 int zswap_swapon(int type, unsigned long nr_pages);
 void zswap_swapoff(int type);
@@ -44,7 +44,7 @@ static inline bool zswap_store(struct folio *folio)
 	return false;
 }
 
-static inline int zswap_load(struct folio *folio)
+static inline int zswap_load(struct folio *folio, void *zentry)
 {
 	return -ENOENT;
 }
diff --git a/mm/page_io.c b/mm/page_io.c
index b23f494fcc83da..1ce8201ed86e24 100644
--- a/mm/page_io.c
+++ b/mm/page_io.c
@@ -658,7 +658,7 @@ static void swap_read_folio_bdev_async(struct folio *folio,
 	submit_bio(bio);
 }
 
-void swap_read_folio(struct folio *folio, struct swap_iocb **plug)
+void swap_read_folio(struct folio *folio, struct swap_iocb **plug, void *zentry)
 {
 	struct swap_info_struct *sis = __swap_entry_to_info(folio->swap);
 	bool synchronous = sis->flags & SWP_SYNCHRONOUS_IO;
@@ -686,7 +686,7 @@ void swap_read_folio(struct folio *folio, struct swap_iocb **plug)
 		goto finish;
 	}
 
-	if (zswap_load(folio) != -ENOENT)
+	if (zswap_load(folio, zentry) != -ENOENT)
 		goto finish;
 
 	/* We have to read from slower devices. Increase zswap protection. */
diff --git a/mm/swap.h b/mm/swap.h
index 44ab8e1e595b08..84e13ba6e605c3 100644
--- a/mm/swap.h
+++ b/mm/swap.h
@@ -210,6 +210,41 @@ static inline void swap_cluster_unlock_irq(struct swap_cluster_info *ci)
 
 extern int swap_retry_table_alloc(swp_entry_t entry, gfp_t gfp);
 
+struct zswap_entry;
+#ifdef CONFIG_ZSWAP
+void *zswap_swp_tb_get_shadow(unsigned long swp_tb);
+unsigned char zswap_swp_tb_get_count(unsigned long swp_tb);
+unsigned char zswap_swp_tb_get_flags(unsigned long swp_tb);
+int zswap_swp_tb_dup_count(unsigned long swp_tb,
+			   struct swap_cluster_info *ci, unsigned int ci_off);
+void zswap_swp_tb_put_count(unsigned long swp_tb,
+			    struct swap_cluster_info *ci, unsigned int ci_off);
+void zswap_entry_free(struct zswap_entry *entry);
+#else
+static inline void *zswap_swp_tb_get_shadow(unsigned long swp_tb)
+{
+	return NULL;
+}
+static inline unsigned char zswap_swp_tb_get_count(unsigned long swp_tb)
+{
+	return 0;
+}
+static inline unsigned char zswap_swp_tb_get_flags(unsigned long swp_tb)
+{
+	return 0;
+}
+static inline int zswap_swp_tb_dup_count(unsigned long swp_tb,
+					 struct swap_cluster_info *ci,
+					 unsigned int ci_off)
+{
+	return 0;
+}
+static inline void zswap_swp_tb_put_count(unsigned long swp_tb,
+					  struct swap_cluster_info *ci,
+					  unsigned int ci_off) {}
+static inline void zswap_entry_free(struct zswap_entry *entry) {}
+#endif
+
 /*
  * Below are the core routines for doing swap for a folio.
  * All helpers requires the folio to be locked, and a locked folio
@@ -241,7 +276,7 @@ extern void __swap_cluster_free_entries(struct swap_info_struct *si,
 /* linux/mm/page_io.c */
 int sio_pool_init(void);
 struct swap_iocb;
-void swap_read_folio(struct folio *folio, struct swap_iocb **plug);
+void swap_read_folio(struct folio *folio, struct swap_iocb **plug, void *zentry);
 void __swap_read_unplug(struct swap_iocb *plug);
 static inline void swap_read_unplug(struct swap_iocb *plug)
 {
@@ -303,13 +338,16 @@ static inline bool folio_matches_swap_entry(const struct folio *folio,
 bool swap_cache_has_folio(swp_entry_t entry);
 struct folio *swap_cache_get_folio(swp_entry_t entry);
 void *swap_cache_get_shadow(swp_entry_t entry);
+bool folio_maybe_swapped(struct folio *folio);
 void swap_cache_del_folio(struct folio *folio);
 struct folio *swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
 				     unsigned long orders, struct vm_fault *vmf,
-				     struct mempolicy *mpol, pgoff_t ilx);
+				     struct mempolicy *mpol, pgoff_t ilx,
+				     void **zentry);
 /* Below helpers require the caller to lock and pass in the swap cluster. */
 void __swap_cache_add_folio(struct swap_cluster_info *ci,
-			    struct folio *folio, swp_entry_t entry);
+			    struct folio *folio, swp_entry_t entry,
+			    void **zentry);
 void __swap_cache_del_folio(struct swap_cluster_info *ci,
 			    struct folio *folio, swp_entry_t entry, void *shadow);
 void __swap_cache_replace_folio(struct swap_cluster_info *ci,
@@ -381,7 +419,8 @@ static inline void folio_put_swap(struct folio *folio, struct page *page)
 {
 }
 
-static inline void swap_read_folio(struct folio *folio, struct swap_iocb **plug)
+static inline void swap_read_folio(struct folio *folio, struct swap_iocb **plug,
+				   void *zentry)
 {
 }
 
@@ -453,6 +492,11 @@ static inline void *swap_cache_get_shadow(swp_entry_t entry)
 	return NULL;
 }
 
+static inline bool folio_maybe_swapped(struct folio *folio)
+{
+	return false;
+}
+
 static inline void swap_cache_del_folio(struct folio *folio)
 {
 }
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 6fd6e3415b718b..60d937c6b39224 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -175,16 +175,29 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci,
 	old_tb = __swap_table_get(ci, ci_off);
 	if (swp_tb_is_folio(old_tb))
 		return -EEXIST;
-	if (!__swp_tb_get_count(old_tb))
+	if (swp_tb_is_pointer(old_tb)) {
+		if (!zswap_swp_tb_get_count(old_tb))
+			return -ENOENT;
+	} else if (!__swp_tb_get_count(old_tb)) {
 		return -ENOENT;
+	}
 	if (shadowp && swp_tb_is_shadow(old_tb))
 		*shadowp = swp_tb_to_shadow(old_tb);
+	else if (shadowp && swp_tb_is_pointer(old_tb))
+		*shadowp = zswap_swp_tb_get_shadow(old_tb);
 	if (memcg_id)
 		*memcg_id = __swap_cgroup_get(ci, ci_off);
 
 	if (nr == 1)
 		return 0;
 
+	/*
+	 * For multi-slot ranges, Pointer entries are not expected since
+	 * zswap does not support large folios. Return -EBUSY for safety.
+	 */
+	if (swp_tb_is_pointer(old_tb))
+		return -EBUSY;
+
 	is_zero = __swap_table_test_zero(ci, ci_off);
 	ci_off = round_down(ci_off, nr);
 	ci_end = ci_off + nr;
@@ -201,7 +214,8 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci,
 }
 
 static void __swap_cache_do_add_folio(struct swap_cluster_info *ci,
-				      struct folio *folio, swp_entry_t entry)
+				      struct folio *folio, swp_entry_t entry,
+				      void **zentry)
 {
 	unsigned int ci_off = swp_cluster_offset(entry), ci_end;
 	unsigned long nr_pages = folio_nr_pages(folio);
@@ -216,7 +230,34 @@ static void __swap_cache_do_add_folio(struct swap_cluster_info *ci,
 	do {
 		old_tb = __swap_table_get(ci, ci_off);
 		VM_WARN_ON_ONCE(swp_tb_is_folio(old_tb));
-		__swap_table_set(ci, ci_off, pfn_to_swp_tb(pfn, __swp_tb_get_flags(old_tb)));
+		/*
+		 * If the old entry is a Pointer (zswap compressed data),
+		 * extract the zswap_entry for the caller and preserve
+		 * its flags. The Pointer entry has no inline flags.
+		 */
+		if (swp_tb_is_pointer(old_tb)) {
+			unsigned char pflags = zswap_swp_tb_get_flags(old_tb);
+			/*
+			 * If swp_tb_val carries SWP_TB_COUNT_MAX, the
+			 * real overflow count lives in ci->extend_table.
+			 * Pull it so the new PFN carries the correct count
+			 * and won't later crash on a missing extend_table.
+			 */
+			if (zswap_swp_tb_get_count(old_tb) == SWP_TB_COUNT_MAX) {
+				unsigned char real = SWP_TB_COUNT_MAX - 1;
+				if (ci->extend_table && ci->extend_table[ci_off])
+					real = ci->extend_table[ci_off];
+				pflags = (real << (SWP_TB_FLAGS_BITS - SWP_TB_COUNT_BITS)) |
+					 (pflags & ((1 << (SWP_TB_FLAGS_BITS - SWP_TB_COUNT_BITS)) - 1));
+			}
+			if (zentry && !*zentry)
+				*zentry = swp_tb_to_pointer(old_tb);
+			__swap_table_set(ci, ci_off,
+				pfn_to_swp_tb(pfn, pflags));
+		} else {
+			__swap_table_set(ci, ci_off,
+				pfn_to_swp_tb(pfn, __swp_tb_get_flags(old_tb)));
+		}
 	} while (++ci_off < ci_end);
 
 	folio_ref_add(folio, nr_pages);
@@ -239,11 +280,12 @@ static void __swap_cache_do_add_folio(struct swap_cluster_info *ci,
  * that holds the entries.
  */
 void __swap_cache_add_folio(struct swap_cluster_info *ci,
-			    struct folio *folio, swp_entry_t entry)
+			    struct folio *folio, swp_entry_t entry,
+			    void **zentry)
 {
 	unsigned long nr_pages = folio_nr_pages(folio);
 
-	__swap_cache_do_add_folio(ci, folio, entry);
+	__swap_cache_do_add_folio(ci, folio, entry, zentry);
 	node_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);
 	lruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);
 }
@@ -269,15 +311,28 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,
 	ci_off = ci_start;
 	do {
 		old_tb = __swap_table_get(ci, ci_off);
-		WARN_ON_ONCE(!swp_tb_is_folio(old_tb) ||
-			     swp_tb_to_folio(old_tb) != folio);
-		if (__swp_tb_get_count(old_tb))
-			folio_swapped = true;
-		else
-			need_free = true;
-		/* If shadow is NULL, we set an empty shadow. */
-		__swap_table_set(ci, ci_off, shadow_to_swp_tb(shadow,
-				 __swp_tb_get_flags(old_tb)));
+		/*
+		 * If zswap already stored data for this slot
+		 * (zswap_store wrote a Pointer entry), leave it
+		 * in place. The count and flags are stored in the
+		 * zswap_entry struct.
+		 */
+		if (swp_tb_is_pointer(old_tb)) {
+			if (zswap_swp_tb_get_count(old_tb))
+				folio_swapped = true;
+			else
+				need_free = true;
+		} else {
+			WARN_ON_ONCE(!swp_tb_is_folio(old_tb) ||
+				     swp_tb_to_folio(old_tb) != folio);
+			if (__swp_tb_get_count(old_tb))
+				folio_swapped = true;
+			else
+				need_free = true;
+			/* If shadow is NULL, we set an empty shadow. */
+			__swap_table_set(ci, ci_off, shadow_to_swp_tb(shadow,
+					 __swp_tb_get_flags(old_tb)));
+		}
 	} while (++ci_off < ci_end);
 
 	folio->swap.val = 0;
@@ -288,7 +343,14 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,
 	} else if (need_free) {
 		ci_off = ci_start;
 		do {
-			if (!__swp_tb_get_count(__swap_table_get(ci, ci_off)))
+			unsigned long tb = __swap_table_get(ci, ci_off);
+			unsigned char cnt;
+
+			if (swp_tb_is_pointer(tb))
+				cnt = zswap_swp_tb_get_count(tb);
+			else
+				cnt = __swp_tb_get_count(tb);
+			if (!cnt)
 				__swap_cluster_free_entries(si, ci, ci_off, 1);
 		} while (++ci_off < ci_end);
 	}
@@ -403,7 +465,8 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
 static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
 					swp_entry_t targ_entry, gfp_t gfp,
 					unsigned int order, struct vm_fault *vmf,
-					struct mempolicy *mpol, pgoff_t ilx)
+					struct mempolicy *mpol, pgoff_t ilx,
+					void **zentry)
 {
 	int err;
 	swp_entry_t entry;
@@ -450,7 +513,7 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
 
 	__folio_set_locked(folio);
 	__folio_set_swapbacked(folio);
-	__swap_cache_do_add_folio(ci, folio, entry);
+	__swap_cache_do_add_folio(ci, folio, entry, zentry);
 	spin_unlock(&ci->lock);
 
 	if (mem_cgroup_swapin_charge_folio(folio, memcg_id,
@@ -507,7 +570,8 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
  */
 struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
 				     unsigned long orders, struct vm_fault *vmf,
-				     struct mempolicy *mpol, pgoff_t ilx)
+				     struct mempolicy *mpol, pgoff_t ilx,
+				     void **zentry)
 {
 	int order, err;
 	struct folio *ret;
@@ -520,9 +584,12 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
 	if (WARN_ON_ONCE(!orders || (1UL << order) > SWAPFILE_CLUSTER))
 		return ERR_PTR(-EINVAL);
 
+	if (zentry)
+		*zentry = NULL;
+
 	do {
 		ret = __swap_cache_alloc(ci, targ_entry, gfp, order,
-					 vmf, mpol, ilx);
+					 vmf, mpol, ilx, zentry);
 		if (!IS_ERR(ret))
 			break;
 		err = PTR_ERR(ret);
@@ -638,18 +705,20 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
 					   struct swap_iocb **plug, bool readahead)
 {
 	struct folio *folio;
+	void *zentry;
 
 	do {
 		folio = swap_cache_get_folio(entry);
 		if (folio)
 			return folio;
-		folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
+		folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx,
+					       &zentry);
 	} while (PTR_ERR(folio) == -EEXIST);
 
 	if (IS_ERR_OR_NULL(folio))
 		return NULL;
 
-	swap_read_folio(folio, plug);
+	swap_read_folio(folio, plug, zentry);
 	if (readahead) {
 		folio_set_readahead(folio);
 		count_vm_event(SWAP_RA);
@@ -678,18 +747,20 @@ 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 *zentry;
 
 	do {
 		folio = swap_cache_get_folio(entry);
 		if (folio)
 			return folio;
-		folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
+		folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx,
+					       &zentry);
 	} while (PTR_ERR(folio) == -EEXIST);
 
 	if (IS_ERR(folio))
 		return folio;
 
-	swap_read_folio(folio, NULL);
+	swap_read_folio(folio, NULL, zentry);
 	return folio;
 }
 
diff --git a/mm/swap_table.h b/mm/swap_table.h
index e6613e62f8d0f6..6318bf8aa76b0c 100644
--- a/mm/swap_table.h
+++ b/mm/swap_table.h
@@ -28,7 +28,7 @@ struct swap_memcg_table {
  * NULL:     |---------------- 0 ---------------| - Free slot
  * Shadow:   |SWAP_COUNT|Z|---- SHADOW_VAL ---|1| - Swapped out slot
  * PFN:      |SWAP_COUNT|Z|------ PFN -------|10| - Cached slot
- * Pointer:  |----------- Pointer ----------|100| - (Unused)
+ * Pointer:  |----------- Pointer ----------|100| - Zswap compressed entry
  * Bad:      |------------- 1 -------------|1000| - Bad slot
  *
  * COUNT is `SWP_TB_COUNT_BITS` long, Z is the `SWP_TB_ZERO_FLAG` bit,
@@ -49,9 +49,11 @@ struct swap_memcg_table {
  * - PFN: Swap slot is in use, and cached. Memcg info is recorded on the page
  *   struct.
  *
- * - Pointer: Unused yet. `0b100` is reserved for potential pointer usage
- *   because only the lower three bits can be used as a marker for 8 bytes
- *   aligned pointers.
+ * - Pointer: Stores a direct pointer to a zswap_entry when the swap slot's
+ *   compressed data resides in zswap. The swap count (if any) is stored in
+ *   the zswap_entry struct rather than inline, since the pointer occupies
+ *   all upper bits. `0b100` is used as the marker because zswap_entry
+ *   pointers are at least 8-byte aligned, leaving the lower three bits free.
  *
  * - Bad: Swap slot is reserved, protects swap header or holes on swap devices.
  */
@@ -78,6 +80,9 @@ struct swap_memcg_table {
 /* The first flag is zero bit (SWAP_TABLE_HAS_ZEROFLAG) */
 #define SWP_TB_ZERO_FLAG	BIT(BITS_PER_LONG - SWP_TB_FLAGS_BITS)
 
+/* Pointer: zswap_entry pointer stored directly, ends with 0b100 */
+#define SWP_TB_POINTER_MARK	0b100UL
+
 /* Bad slot: ends with 0b1000 and rests of bits are all 1 */
 #define SWP_TB_BAD		((~0UL) << 3)
 
@@ -166,6 +171,11 @@ static inline bool swp_tb_is_bad(unsigned long swp_tb)
 	return swp_tb == SWP_TB_BAD;
 }
 
+static inline bool swp_tb_is_pointer(unsigned long swp_tb)
+{
+	return (swp_tb & (BIT(3) - 1)) == SWP_TB_POINTER_MARK;
+}
+
 static inline bool swp_tb_is_countable(unsigned long swp_tb)
 {
 	return (swp_tb_is_shadow(swp_tb) || swp_tb_is_folio(swp_tb) ||
@@ -188,6 +198,23 @@ static inline void *swp_tb_to_shadow(unsigned long swp_tb)
 	return (void *)(swp_tb & ~SWP_TB_FLAGS_MASK);
 }
 
+static inline unsigned long pointer_to_swp_tb(void *ptr)
+{
+	unsigned long val = (unsigned long)ptr;
+
+	BUILD_BUG_ON(sizeof(unsigned long) != sizeof(void *));
+	/* Pointers are 8-byte aligned, low 3 bits must be clear */
+	VM_WARN_ON_ONCE(val & (BIT(3) - 1));
+	return val | SWP_TB_POINTER_MARK;
+}
+
+static inline void *swp_tb_to_pointer(unsigned long swp_tb)
+{
+	VM_WARN_ON(!swp_tb_is_pointer(swp_tb));
+	/* Clear the low 3-bit marker to recover the original pointer */
+	return (void *)(swp_tb & ~((unsigned long)(BIT(3) - 1)));
+}
+
 static inline unsigned char __swp_tb_get_count(unsigned long swp_tb)
 {
 	VM_WARN_ON(!swp_tb_is_countable(swp_tb));
diff --git a/mm/swapfile.c b/mm/swapfile.c
index a602e5820513a8..18925e462571b1 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -935,7 +935,8 @@ static bool __swap_cluster_alloc_entries(struct swap_info_struct *si,
 		nr_pages = 1 << order;
 		swap_cluster_assert_empty(ci, ci_off, nr_pages, false);
 		__swap_cache_add_folio(ci, folio, swp_entry(si->type,
-							    ci_off + cluster_offset(si, ci)));
+							    ci_off + cluster_offset(si, ci)),
+				       NULL);
 	} else if (IS_ENABLED(CONFIG_HIBERNATION)) {
 		order = 0;
 		nr_pages = 1;
@@ -1536,25 +1537,51 @@ static void __swap_cluster_put_entry(struct swap_cluster_info *ci,
 
 	lockdep_assert_held(&ci->lock);
 	swp_tb = __swap_table_get(ci, ci_off);
+
+	/* Pointer entries store count in the zswap_entry struct */
+	if (swp_tb_is_pointer(swp_tb)) {
+		zswap_swp_tb_put_count(swp_tb, ci, ci_off);
+		return;
+	}
+
 	count = __swp_tb_get_count(swp_tb);
 
 	VM_WARN_ON_ONCE(count <= 0);
 	VM_WARN_ON_ONCE(count > SWP_TB_COUNT_MAX);
-
-	if (count == SWP_TB_COUNT_MAX) {
-		count = ci->extend_table[ci_off];
-		/* Overflow starts with SWP_TB_COUNT_MAX */
-		VM_WARN_ON_ONCE(count < SWP_TB_COUNT_MAX);
-		count--;
-		if (count == (SWP_TB_COUNT_MAX - 1)) {
-			ci->extend_table[ci_off] = 0;
-			__swap_table_set(ci, ci_off, __swp_tb_mk_count(swp_tb, count));
+		if (count == SWP_TB_COUNT_MAX) {
+			if (!ci->extend_table) {
+				/*
+				 * MAX without extend_table: MAX is the real
+				 * count.  Decrement and write back inline.
+				 */
+				count--;
+				__swap_table_set(ci, ci_off,
+					__swp_tb_mk_count(swp_tb, count));
+			} else {
+				/*
+				 * MAX with extend_table: real count lives in
+				 * ci->extend_table[ci_off].  Pull, decrement
+				 * it, and either move it back inline (if now
+				 * fits) or write it back to the extend table.
+				 */
+				count = ci->extend_table[ci_off];
+				/* Overflow starts with SWP_TB_COUNT_MAX */
+				VM_WARN_ON_ONCE(count < SWP_TB_COUNT_MAX);
+				count--;
+				if (count == (SWP_TB_COUNT_MAX - 1)) {
+					ci->extend_table[ci_off] = 0;
+					__swap_table_set(ci, ci_off,
+						__swp_tb_mk_count(swp_tb, count));
+				} else {
+					ci->extend_table[ci_off] = count;
+				}
+			}
 		} else {
-			ci->extend_table[ci_off] = count;
+			/* count < MAX: simple inline decrement */
+			count--;
+			__swap_table_set(ci, ci_off,
+				__swp_tb_mk_count(swp_tb, count));
 		}
-	} else {
-		__swap_table_set(ci, ci_off, __swp_tb_mk_count(swp_tb, --count));
-	}
 
 	/*
 	 * `SWP_TB_COUNT_MAX - 1` triggers extend table allocation. If the
@@ -1596,7 +1623,18 @@ static void swap_put_entries_cluster(struct swap_info_struct *si,
 	ci_end = ci_off + nr;
 	do {
 		swp_tb = __swap_table_get(ci, ci_off);
-		if (swp_tb_get_count(swp_tb) == 1) {
+		/*
+		 * Pointer entries store count in the zswap_entry;
+		 * handle them before swp_tb_get_count (which returns
+		 * -EINVAL for non-countable types).
+		 */
+		if (swp_tb_is_pointer(swp_tb)) {
+			if (zswap_swp_tb_get_count(swp_tb) == 1) {
+				if (ci_batch == -1)
+					ci_batch = ci_off;
+				continue;
+			}
+		} else if (swp_tb_get_count(swp_tb) == 1) {
 			/* count == 1 and non-cached slots will be batch freed. */
 			if (!swp_tb_is_folio(swp_tb)) {
 				if (ci_batch == -1)
@@ -1647,6 +1685,11 @@ static int __swap_cluster_dup_entry(struct swap_cluster_info *ci,
 	/* Bad or special slots can't be handled */
 	if (WARN_ON_ONCE(swp_tb_is_bad(swp_tb)))
 		return -EINVAL;
+
+	/* Pointer entries store count in the zswap_entry struct */
+	if (swp_tb_is_pointer(swp_tb))
+		return zswap_swp_tb_dup_count(swp_tb, ci, ci_off);
+
 	count = __swp_tb_get_count(swp_tb);
 	/* Must be either cached or have a count already */
 	if (WARN_ON_ONCE(!count && !swp_tb_is_folio(swp_tb)))
@@ -1923,13 +1966,26 @@ void __swap_cluster_free_entries(struct swap_info_struct *si,
 		old_tb = __swap_table_get(ci, ci_off);
 		/*
 		 * Freeing is done after release of the last swap count
-		 * ref, or after swap cache is dropped
+		 * ref, or after swap cache is dropped. A Pointer entry
+		 * means zswap has a compressed copy; the xarray still
+		 * holds the entry and zswap_invalidate will free it.
 		 */
-		VM_WARN_ON(!swp_tb_is_shadow(old_tb) || __swp_tb_get_count(old_tb) > 1);
+		if (swp_tb_is_pointer(old_tb)) {
+			VM_WARN_ON(zswap_swp_tb_get_count(old_tb) > 1);
+			/*
+			 * Free the zswap entry now under ci->lock while
+			 * the Pointer is still in the swap table.  Once
+			 * we null the slot, the entry would be leaked.
+			 */
+			zswap_entry_free(swp_tb_to_pointer(old_tb));
+		} else {
+			VM_WARN_ON(!swp_tb_is_shadow(old_tb) ||
+				   __swp_tb_get_count(old_tb) > 1);
+		}
 
 		/* Resetting the slot to NULL also clears the inline flags. */
 		__swap_table_set(ci, ci_off, null_to_swp_tb());
-		if (!SWAP_TABLE_HAS_ZEROFLAG)
+		if (!SWAP_TABLE_HAS_ZEROFLAG && !swp_tb_is_pointer(old_tb))
 			__swap_table_clear_zero(ci, ci_off);
 
 		/*
@@ -1961,8 +2017,11 @@ int __swap_count(swp_entry_t entry)
 {
 	struct swap_cluster_info *ci = __swap_entry_to_cluster(entry);
 	unsigned int ci_off = swp_cluster_offset(entry);
+	unsigned long swp_tb = __swap_table_get(ci, ci_off);
 
-	return swp_tb_get_count(__swap_table_get(ci, ci_off));
+	if (swp_tb_is_pointer(swp_tb))
+		return zswap_swp_tb_get_count(swp_tb);
+	return swp_tb_get_count(swp_tb);
 }
 
 /**
@@ -1980,6 +2039,8 @@ bool swap_entry_swapped(struct swap_info_struct *si, swp_entry_t entry)
 	swp_tb = swap_table_get(ci, offset % SWAPFILE_CLUSTER);
 	swap_cluster_unlock(ci);
 
+	if (swp_tb_is_pointer(swp_tb))
+		return zswap_swp_tb_get_count(swp_tb) > 0;
 	return swp_tb_get_count(swp_tb) > 0;
 }
 
@@ -2020,7 +2081,7 @@ int swp_swapcount(swp_entry_t entry)
  *
  * Context: Caller must ensure the folio is locked and in the swap cache.
  */
-static bool folio_maybe_swapped(struct folio *folio)
+bool folio_maybe_swapped(struct folio *folio)
 {
 	swp_entry_t entry = folio->swap;
 	struct swap_cluster_info *ci;
@@ -2556,8 +2617,12 @@ static int unuse_pte_range(struct vm_area_struct *vma, pmd_t *pmd,
 		if (!folio) {
 			swp_tb = swap_table_get(__swap_entry_to_cluster(entry),
 						swp_cluster_offset(entry));
-			if (swp_tb_get_count(swp_tb) <= 0)
+			if (swp_tb_is_pointer(swp_tb)) {
+				if (zswap_swp_tb_get_count(swp_tb) <= 0)
+					continue;
+			} else if (swp_tb_get_count(swp_tb) <= 0) {
 				continue;
+			}
 			return -ENOMEM;
 		}
 
diff --git a/mm/zswap.c b/mm/zswap.c
index 761cd699e0a3e6..f9762d0a596ab4 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -38,6 +38,7 @@
 #include <linux/zsmalloc.h>
 
 #include "swap.h"
+#include "swap_table.h"
 #include "internal.h"
 
 /*********************************
@@ -182,6 +183,10 @@ static struct shrinker *zswap_shrinker;
  *              writeback logic. The entry is only reclaimed by the writeback
  *              logic if referenced is unset. See comments in the shrinker
  *              section for context.
+ * swp_tb_val - the original swap table entry value saved when this zswap_entry
+ *              replaced a Shadow or PFN entry. It preserves the swap count,
+ *              zero flag, and working set shadow information (SHADOW_VAL)
+ *              that the Shadow entry carried.
  * pool - the zswap_pool the entry's data is in
  * handle - zsmalloc allocation handle that stores the compressed page data
  * objcg - the obj_cgroup that the compressed memory is charged to
@@ -191,15 +196,13 @@ struct zswap_entry {
 	swp_entry_t swpentry;
 	unsigned int length;
 	bool referenced;
+	unsigned long swp_tb_val;
 	struct zswap_pool *pool;
 	unsigned long handle;
 	struct obj_cgroup *objcg;
 	struct list_head lru;
 };
 
-static struct xarray *zswap_trees[MAX_SWAPFILES];
-static unsigned int nr_zswap_trees[MAX_SWAPFILES];
-
 /* RCU-protected iteration */
 static LIST_HEAD(zswap_pools);
 /* protects zswap_pools list modification */
@@ -225,15 +228,6 @@ static bool zswap_has_pool;
 * helpers and fwd declarations
 **********************************/
 
-/* One swap address space for each 64M swap space */
-#define ZSWAP_ADDRESS_SPACE_SHIFT 14
-#define ZSWAP_ADDRESS_SPACE_PAGES (1 << ZSWAP_ADDRESS_SPACE_SHIFT)
-static inline struct xarray *swap_zswap_tree(swp_entry_t swp)
-{
-	return &zswap_trees[swp_type(swp)][swp_offset(swp)
-		>> ZSWAP_ADDRESS_SPACE_SHIFT];
-}
-
 #define zswap_pool_debug(msg, p)			\
 	pr_debug("%s pool %s\n", msg, (p)->tfm_name)
 
@@ -762,7 +756,7 @@ static void zswap_entry_cache_free(struct zswap_entry *entry)
  * Carries out the common pattern of freeing an entry's zsmalloc allocation,
  * freeing the entry itself, and decrementing the number of stored pages.
  */
-static void zswap_entry_free(struct zswap_entry *entry)
+void zswap_entry_free(struct zswap_entry *entry)
 {
 	zswap_lru_del(&zswap_list_lru, entry);
 	zs_free(entry->pool->zs_pool, entry->handle);
@@ -987,11 +981,12 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
 static int zswap_writeback_entry(struct zswap_entry *entry,
 				 swp_entry_t swpentry)
 {
-	struct xarray *tree;
 	pgoff_t offset = swp_offset(swpentry);
 	struct folio *folio;
 	struct mempolicy *mpol;
 	struct swap_info_struct *si;
+	struct swap_cluster_info *ci;
+	unsigned long swp_tb;
 	int ret = 0;
 
 	/* try to allocate swap cache folio */
@@ -999,9 +994,25 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
 	if (!si)
 		return -EEXIST;
 
+	/*
+	 * Verify the swap table Pointer entry still references this
+	 * zswap_entry. If the entry was invalidated or replaced by a
+	 * concurrent store, the Pointer will be gone or point elsewhere.
+	 */
+	ci = __swap_offset_to_cluster(si, offset);
+	spin_lock(&ci->lock);
+	swp_tb = __swap_table_get(ci, offset % SWAPFILE_CLUSTER);
+	if (!swp_tb_is_pointer(swp_tb) ||
+	    swp_tb_to_pointer(swp_tb) != entry) {
+		spin_unlock(&ci->lock);
+		put_swap_device(si);
+		return -ENOMEM;
+	}
+	spin_unlock(&ci->lock);
+
 	mpol = get_task_policy(current);
 	folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
-				       NO_INTERLEAVE_INDEX);
+				       NO_INTERLEAVE_INDEX, NULL);
 	put_swap_device(si);
 
 	/*
@@ -1015,27 +1026,15 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
 		return PTR_ERR(folio);
 
 	/*
-	 * folio is locked, and the swapcache is now secured against
-	 * concurrent swapping to and from the slot, and concurrent
-	 * swapoff so we can safely dereference the zswap tree here.
-	 * Verify that the swap entry hasn't been invalidated and recycled
-	 * behind our backs, to avoid overwriting a new swap folio with
-	 * old compressed data. Only when this is successful can the entry
-	 * be dereferenced.
+	 * The folio is now locked and in swap cache, so concurrent
+	 * stores are blocked. __swap_cache_do_add_folio already
+	 * overwrote the Pointer entry with a PFN.
 	 */
-	tree = swap_zswap_tree(swpentry);
-	if (entry != xa_load(tree, offset)) {
-		ret = -ENOMEM;
-		goto out;
-	}
-
 	if (!zswap_decompress(entry, folio)) {
 		ret = -EIO;
 		goto out;
 	}
 
-	xa_erase(tree, offset);
-
 	count_vm_event(ZSWPWB);
 	if (entry->objcg)
 		count_objcg_events(entry->objcg, ZSWPWB, 1);
@@ -1105,36 +1104,22 @@ static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_o
 	}
 
 	/*
-	 * As soon as we drop the LRU lock, the entry can be freed by
-	 * a concurrent invalidation. This means the following:
-	 *
-	 * 1. We extract the swp_entry_t to the stack, allowing
-	 *    zswap_writeback_entry() to pin the swap entry and
-	 *    then validate the zswap entry against that swap entry's
-	 *    tree using pointer value comparison. Only when that
-	 *    is successful can the entry be dereferenced.
-	 *
-	 * 2. Usually, objects are taken off the LRU for reclaim. In
-	 *    this case this isn't possible, because if reclaim fails
-	 *    for whatever reason, we have no means of knowing if the
-	 *    entry is alive to put it back on the LRU.
+	 * After we drop the LRU lock, the entry may be freed by a
+	 * concurrent swap slot free.  This is safe because
+	 * zswap_writeback_entry() rechecks the swap table under
+	 * ci->lock before dereferencing entry; if the Pointer is
+	 * gone it bails out.
 	 *
-	 *    So rotate it before dropping the lock. If the entry is
-	 *    written back or invalidated, the free path will unlink
-	 *    it. For failures, rotation is the right thing as well.
-	 *
-	 *    Temporary failures, where the same entry should be tried
-	 *    again immediately, almost never happen for this shrinker.
-	 *    We don't do any trylocking; -ENOMEM comes closest,
-	 *    but that's extremely rare and doesn't happen spuriously
-	 *    either. Don't bother distinguishing this case.
+	 * Rotate the entry before dropping the lock: if it gets
+	 * written back or invalidated the free path will unlink
+	 * it from the LRU.  For failures rotation is fine.
 	 */
 	list_move_tail(item, &l->list);
 
 	/*
-	 * Once the lru lock is dropped, the entry might get freed. The
-	 * swpentry is copied to the stack, and entry isn't deref'd again
-	 * until the entry is verified to still be alive in the tree.
+	 * The swpentry is copied to the stack so we can pin the swap
+	 * device and validate the entry's swap table Pointer under
+	 * ci->lock in zswap_writeback_entry().
 	 */
 	swpentry = entry->swpentry;
 
@@ -1398,7 +1383,11 @@ static bool zswap_store_page(struct page *page,
 			     struct zswap_pool *pool)
 {
 	swp_entry_t page_swpentry = page_swap_entry(page);
-	struct zswap_entry *entry, *old;
+	struct zswap_entry *entry, *old = NULL;
+	struct swap_cluster_info *ci;
+	struct swap_info_struct *si;
+	pgoff_t offset = swp_offset(page_swpentry);
+	unsigned long swp_tb;
 
 	/* allocate entry */
 	entry = zswap_entry_cache_alloc(GFP_KERNEL, page_to_nid(page));
@@ -1410,31 +1399,49 @@ static bool zswap_store_page(struct page *page,
 	if (!zswap_compress(page, entry, pool))
 		goto compress_failed;
 
-	old = xa_store(swap_zswap_tree(page_swpentry),
-		       swp_offset(page_swpentry),
-		       entry, GFP_KERNEL);
-	if (xa_is_err(old)) {
-		int err = xa_err(old);
-
-		WARN_ONCE(err != -ENOMEM, "unexpected xarray error: %d\n", err);
-		zswap_reject_alloc_fail++;
-		goto store_failed;
+	/*
+	 * Store the zswap_entry pointer in the swap table as a
+	 * Pointer-type entry, replacing the PFN or Shadow entry.
+	 * The full swap table entry value (including working set
+	 * shadow) is preserved in entry->swp_tb_val.
+	 * Folio lock serializes concurrent stores.
+	 */
+	si = __swap_entry_to_info(page_swpentry);
+	ci = __swap_offset_to_cluster(si, offset);
+	spin_lock(&ci->lock);
+	swp_tb = __swap_table_get(ci, offset % SWAPFILE_CLUSTER);
+	if (swp_tb_is_countable(swp_tb)) {
+		/*
+		 * Save the complete original swap table entry value
+		 * (Shadow or PFN), preserving swap count, zero flag,
+		 * and working set shadow information.
+		 */
+		entry->swp_tb_val = swp_tb;
+	} else if (swp_tb_is_pointer(swp_tb)) {
+		/*
+		 * Replacing an existing Pointer entry (e.g. from a
+		 * redirtied folio). Inherit the saved swap table value.
+		 */
+		old = swp_tb_to_pointer(swp_tb);
+		entry->swp_tb_val = old->swp_tb_val;
 	}
-
+	__swap_table_set(ci, offset % SWAPFILE_CLUSTER,
+			 pointer_to_swp_tb(entry));
+	spin_unlock(&ci->lock);
 	/*
-	 * We may have had an existing entry that became stale when
-	 * the folio was redirtied and now the new version is being
-	 * swapped out. Get rid of the old.
+	 * If we had an existing zswap entry from the swap table
+	 * (e.g. from a previous redirtied folio), get rid of it
+	 * outside the lock.
 	 */
 	if (old)
 		zswap_entry_free(old);
 
 	/*
-	 * The entry is successfully compressed and stored in the tree, there is
-	 * no further possibility of failure. Grab refs to the pool and objcg,
-	 * charge zswap memory, and increment zswap_stored_pages.
+	 * The entry is successfully compressed and stored in the swap table,
+	 * there is no further possibility of failure. Grab refs to the pool
+	 * and objcg, charge zswap memory, and increment zswap_stored_pages.
 	 * The opposite actions will be performed by zswap_entry_free()
-	 * when the entry is removed from the tree.
+	 * when the entry is removed from the swap table.
 	 */
 	zswap_pool_get(pool);
 	if (objcg) {
@@ -1446,8 +1453,8 @@ static bool zswap_store_page(struct page *page,
 		atomic_long_inc(&zswap_stored_incompressible_pages);
 
 	/*
-	 * We finish initializing the entry while it's already in xarray.
-	 * This is safe because:
+	 * We finish initializing the entry while it's already in the
+	 * swap table. This is safe because:
 	 *
 	 * 1. Concurrent stores and invalidations are excluded by folio lock.
 	 *
@@ -1466,8 +1473,6 @@ static bool zswap_store_page(struct page *page,
 
 	return true;
 
-store_failed:
-	zs_free(pool->zs_pool, entry->handle);
 compress_failed:
 	zswap_entry_cache_free(entry);
 	return false;
@@ -1546,11 +1551,31 @@ bool zswap_store(struct folio *folio)
 		unsigned type = swp_type(swp);
 		pgoff_t offset = swp_offset(swp);
 		struct zswap_entry *entry;
-		struct xarray *tree;
+		struct swap_cluster_info *ci;
+		struct swap_info_struct *sis;
+		unsigned long swp_tb;
 
+		sis = __swap_type_to_info(type);
 		for (index = 0; index < nr_pages; ++index) {
-			tree = swap_zswap_tree(swp_entry(type, offset + index));
-			entry = xa_erase(tree, offset + index);
+			ci = __swap_offset_to_cluster(sis, offset + index);
+			spin_lock(&ci->lock);
+			swp_tb = __swap_table_get(ci, (offset + index) % SWAPFILE_CLUSTER);
+			if (swp_tb_is_pointer(swp_tb)) {
+				entry = swp_tb_to_pointer(swp_tb);
+				/*
+				 * Restore the original swap table entry
+				 * (Shadow or PFN) that was saved when the
+				 * Pointer entry was created, rather than
+				 * zeroing the slot.  This preserves the swap
+				 * count and flags so the swap cache folio
+				 * can be correctly cleaned up later.
+				 */
+				__swap_table_set(ci, (offset + index) % SWAPFILE_CLUSTER,
+						 entry->swp_tb_val);
+			} else {
+				entry = NULL;
+			}
+			spin_unlock(&ci->lock);
 			if (entry)
 				zswap_entry_free(entry);
 		}
@@ -1562,6 +1587,11 @@ bool zswap_store(struct folio *folio)
 /**
  * zswap_load() - load a folio from zswap
  * @folio: folio to load
+ * @zentry: zswap_entry pointer extracted from swap table, or NULL
+ *
+ * The zswap_entry was extracted from the swap table Pointer entry by
+ * __swap_cache_do_add_folio before it overwrote the entry with a PFN.
+ * If @zentry is NULL, the slot was not backed by zswap.
  *
  * Return: 0 on success, with the folio unlocked and marked up-to-date, or one
  * of the following error codes:
@@ -1579,12 +1609,9 @@ bool zswap_store(struct folio *folio)
  *  -ENOENT: if the swapped out content was not in zswap. The folio remains
  *  locked on return.
  */
-int zswap_load(struct folio *folio)
+int zswap_load(struct folio *folio, void *zentry)
 {
-	swp_entry_t swp = folio->swap;
-	pgoff_t offset = swp_offset(swp);
-	struct xarray *tree = swap_zswap_tree(swp);
-	struct zswap_entry *entry;
+	struct zswap_entry *entry = zentry;
 
 	VM_WARN_ON_ONCE(!folio_test_locked(folio));
 	VM_WARN_ON_ONCE(!folio_test_swapcache(folio));
@@ -1602,7 +1629,6 @@ int zswap_load(struct folio *folio)
 		return -EINVAL;
 	}
 
-	entry = xa_load(tree, offset);
 	if (!entry)
 		return -ENOENT;
 
@@ -1625,7 +1651,6 @@ int zswap_load(struct folio *folio)
 	 * compression work.
 	 */
 	folio_mark_dirty(folio);
-	xa_erase(tree, offset);
 	zswap_entry_free(entry);
 
 	folio_unlock(folio);
@@ -1635,52 +1660,159 @@ int zswap_load(struct folio *folio)
 void zswap_invalidate(swp_entry_t swp)
 {
 	pgoff_t offset = swp_offset(swp);
-	struct xarray *tree = swap_zswap_tree(swp);
+	struct swap_cluster_info *ci;
+	struct swap_info_struct *si;
+	unsigned long swp_tb;
 	struct zswap_entry *entry;
 
-	if (xa_empty(tree))
+	si = __swap_type_to_info(swp_type(swp));
+	ci = __swap_offset_to_cluster(si, offset);
+	/*
+	 * Do a lockless peek first.  If ci->lock is already held by
+	 * the caller (e.g. __swap_cluster_free_entries), and the
+	 * Pointer has already been cleared, we can bail out without
+	 * self-deadlocking.
+	 */
+	swp_tb = __swap_table_get(ci, offset % SWAPFILE_CLUSTER);
+	if (!swp_tb_is_pointer(swp_tb))
 		return;
 
-	entry = xa_erase(tree, offset);
+	spin_lock(&ci->lock);
+	swp_tb = __swap_table_get(ci, offset % SWAPFILE_CLUSTER);
+	if (swp_tb_is_pointer(swp_tb)) {
+		entry = swp_tb_to_pointer(swp_tb);
+		/*
+		 * Restore the original swap table entry (Shadow or PFN)
+		 * that was saved in swp_tb_val when the Pointer entry
+		 * was created.  This preserves swap count and flags
+		 * for the subsequent slot-free path.
+		 */
+		__swap_table_set(ci, offset % SWAPFILE_CLUSTER,
+				 entry->swp_tb_val);
+	} else {
+		entry = NULL;
+	}
+	spin_unlock(&ci->lock);
+
 	if (entry)
 		zswap_entry_free(entry);
 }
 
-int zswap_swapon(int type, unsigned long nr_pages)
+/*
+ * Extract the working set shadow from a Pointer-type swap table entry.
+ * The shadow is recovered from zswap_entry->swp_tb_val which preserves
+ * the original Shadow entry value. The shadow can then be used by
+ * workingset_refault() for refault distance calculation.
+ */
+void *zswap_swp_tb_get_shadow(unsigned long swp_tb)
 {
-	struct xarray *trees, *tree;
-	unsigned int nr, i;
+	struct zswap_entry *entry = swp_tb_to_pointer(swp_tb);
+	unsigned long saved = entry->swp_tb_val;
 
-	nr = DIV_ROUND_UP(nr_pages, ZSWAP_ADDRESS_SPACE_PAGES);
-	trees = kvzalloc_objs(*tree, nr);
-	if (!trees) {
-		pr_err("alloc failed, zswap disabled for swap type %d\n", type);
-		return -ENOMEM;
-	}
+	/*
+	 * swp_tb_val preserves the original swap table entry.  Only
+	 * Shadow entries carry workingset information; PFN and NULL
+	 * entries have no shadow to extract.
+	 */
+	if (!swp_tb_is_shadow(saved))
+		return NULL;
+
+	return (void *)(saved & ~SWP_TB_FLAGS_MASK);
+}
+
+/*
+ * Read the swap count from a Pointer-type swap table entry. The count
+ * and flags are stored in zswap_entry->swp_tb_val which preserves the
+ * original swap table entry value (Shadow or PFN format).
+ */
+unsigned char zswap_swp_tb_get_count(unsigned long swp_tb)
+{
+	struct zswap_entry *entry = swp_tb_to_pointer(swp_tb);
+
+	return __swp_tb_get_count(entry->swp_tb_val);
+}
+
+/*
+ * Read the flags from a Pointer-type swap table entry.
+ */
+unsigned char zswap_swp_tb_get_flags(unsigned long swp_tb)
+{
+	struct zswap_entry *entry = swp_tb_to_pointer(swp_tb);
+
+	return __swp_tb_get_flags(entry->swp_tb_val);
+}
 
-	for (i = 0; i < nr; i++)
-		xa_init(trees + i);
+/*
+ * Increment the swap count of a Pointer-type swap table entry.
+ * The count is stored in zswap_entry->swp_tb_val for counts below
+ * SWP_TB_COUNT_MAX, and overflows into the cluster's extend_table
+ * just like normal Shadow/PFN entries do.  Returns 0 on success,
+ * -ENOMEM if count would overflow and no extend_table is available.
+ */
+int zswap_swp_tb_dup_count(unsigned long swp_tb,
+			   struct swap_cluster_info *ci, unsigned int ci_off)
+{
+	struct zswap_entry *entry = swp_tb_to_pointer(swp_tb);
+	unsigned char count = __swp_tb_get_count(entry->swp_tb_val);
 
-	nr_zswap_trees[type] = nr;
-	zswap_trees[type] = trees;
+	if (count < SWP_TB_COUNT_MAX) {
+		entry->swp_tb_val = __swp_tb_mk_count(entry->swp_tb_val,
+						      count + 1);
+		return 0;
+	}
+	/* count == MAX, overflow into extend_table */
+	if (!ci->extend_table)
+		return -ENOMEM;
+	count = ci->extend_table[ci_off];
+	if (count == 0)
+		count = SWP_TB_COUNT_MAX;
+	count++;
+	ci->extend_table[ci_off] = count;
 	return 0;
 }
 
-void zswap_swapoff(int type)
+/*
+ * Decrement the swap count of a Pointer-type swap table entry.
+ * Handles both inline count (in swp_tb_val) and overflowed count
+ * (in ci->extend_table), matching the logic used by normal entries.
+ */
+void zswap_swp_tb_put_count(unsigned long swp_tb,
+			    struct swap_cluster_info *ci, unsigned int ci_off)
 {
-	struct xarray *trees = zswap_trees[type];
-	unsigned int i;
+	struct zswap_entry *entry = swp_tb_to_pointer(swp_tb);
+	unsigned char count = __swp_tb_get_count(entry->swp_tb_val);
+
+	VM_WARN_ON_ONCE(count == 0);
 
-	if (!trees)
+	if (count == SWP_TB_COUNT_MAX) {
+		if (!ci->extend_table) {
+			count--;
+			entry->swp_tb_val = __swp_tb_mk_count(entry->swp_tb_val, count);
+			return;
+		}
+		count = ci->extend_table[ci_off];
+		/* Overflow starts with SWP_TB_COUNT_MAX */
+		VM_WARN_ON_ONCE(count < SWP_TB_COUNT_MAX);
+		count--;
+		if (count == (SWP_TB_COUNT_MAX - 1)) {
+			ci->extend_table[ci_off] = 0;
+			entry->swp_tb_val = __swp_tb_mk_count(entry->swp_tb_val,
+							      count);
+		} else {
+			ci->extend_table[ci_off] = count;
+		}
 		return;
+	}
+	entry->swp_tb_val = __swp_tb_mk_count(entry->swp_tb_val, count - 1);
+}
 
-	/* try_to_unuse() invalidated all the entries already */
-	for (i = 0; i < nr_zswap_trees[type]; i++)
-		WARN_ON_ONCE(!xa_empty(trees + i));
+int zswap_swapon(int type, unsigned long nr_pages)
+{
+	return 0;
+}
 
-	kvfree(trees);
-	nr_zswap_trees[type] = 0;
-	zswap_trees[type] = NULL;
+void zswap_swapoff(int type)
+{
 }
 
 /*********************************

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

Args:
null

Results:
null

Tokens:
							input: 17884
							output: 24
							thoughts: 663
						

Thoughts:
**Analyzing the Zswap Entry Storage Patch**

Okay, so we've got a patch here that's fundamentally changing how `zswap` manages its entries. Instead of using that separate `xarray` we're used to (`zswap_trees`), it's now directly linking a pointer to the `zswap_entry` structure within the swap tables themselves (`swap_map` or `swap_cluster_info`). This is a significant architectural shift. I need to figure out if this is robust.

My initial thought is to verify its functionality. This is a complete paradigm shift in zswap's entry tracking. Is zswap even reachable if this fails? Yes, it's a core feature, so that's a check. Is this change worthy of fuzzing? Absolutely; it's a radical modification to how swap and zswap interact. That necessitates fuzzing to root out any subtle bugs.

Now, I need to pinpoint the critical functions this patch touches. Looks like the core `mm/zswap.c` and `mm/swap_state.c` are where the bulk of the changes reside. From a quick scan, it looks like these are the functions that have been modified: `zswap_store`, `zswap_load`, `zswap_invalidate`, `zswap_writeback_entry`, `zswap_store_page`, `__swap_cache_add_folio`, `__swap_cache_do_add_folio`, `__swap_cache_do_del_folio`, `__swap_cluster_put_entry`, `__swap_cluster_free_entries`, `swap_put_entries_cluster`, and `__swap_cluster_dup_entry`. I need to dig into those functions. Let me review `mm/zswap.c` and `mm/swap_state.c`. Looks like it's a few more than the list I initially constructed: `zswap_store`, `zswap_load`, `zswap_invalidate`, `zswap_writeback_entry`, `zswap_swp_tb_dup_count`, `zswap_swp_tb_put_count`, `zswap_swp_tb_get_shadow`, `zswap_swp_tb_get_count`, `zswap_swp_tb_get_flags`. `__swap_cache_add_check`, `__swap_cache_do_add_folio`, `__swap_cache_do_del_folio`, `__swap_cache_alloc`, `swap_cache_alloc_folio`, `swap_cache_read_folio`, `swapin_sync`. `__swap_cluster_alloc_entries`, `__swap_cluster_put_entry`, `swap_put_entries_cluster`, `__swap_cluster_dup_entry`, `__swap_cluster_free_entries`, `__swap_count`, `swap_entry_swapped`, `unuse_pte_range`.

Next, I need to identify any relevant configuration options. `CONFIG_ZSWAP` is obviously essential. I should check `mm/Kconfig` or even `mm/zswap.c` itself to confirm if there are any other configuration dependencies or implications. `CONFIG_SWAP` is implicitly enabled when `CONFIG_ZSWAP` is, so I'll keep an eye out for any others that might be required or affected.



4/2 2026/07/07 15:11 tool grepper
0m Args:
{
  "Expression": "config ZSWAP",
  "PathPrefix": ""
}

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/07/07 15:11 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19837
							output: 28
							thoughts: 35
						

Thoughts:
Okay, here's my take on those thoughts, as if they were my own:

**Enabling ZSWAP: A Streamlined Path to Performance**

Right, so I'm thinking, given the context, enabling `ZSWAP` and setting `ZSWAP_DEFAULT_ON` seems like the sensible next step.  We're looking for performance improvements, and this is a classic approach.

Before I jump to any conclusions, I'd better double-check the kernel configuration. So, let's head over to `mm/Kconfig` and make sure there are no obvious conflicts or prerequisites I've missed. I need to refresh my memory about the configuration options in that area - ensure there is nothing preventing or requiring further configuration options. Gotta make sure there aren't any gotchas in the setup, or dependencies I need to be aware of. Better safe than sorry when making these kinds of changes.  Then, we can evaluate the broader implications.



6/2 2026/07/07 15:11 tool grepper
0m Args:
{
  "Expression": "config ZSWAP_DEFAULT_ON",
  "PathPrefix": ""
}

Results:
{
  "Output": "mm/Kconfig=22=config ZSWAP\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"
}

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

Args:
null

Results:
null

Tokens:
							input: 20179
							output: 245
							thoughts: 718
						

Thoughts:
**My Assessment of the Zswap Changes**

Okay, so I'm diving into this zswap patch, and here's my train of thought. First, I need to pinpoint the key functions and data structures that have been touched. I started by identifying a list of symbols I see as potentially important. Let's see... I've got `zswap_store`, `zswap_load`, `zswap_invalidate`, `zswap_writeback_entry`, `__swap_cache_do_add_folio`, `__swap_cache_do_del_folio`, `__swap_cluster_put_entry`, `__swap_cluster_free_entries`, `swap_put_entries_cluster`, and `__swap_cluster_dup_entry`. I need to figure out which of these are actually *significant* in terms of changes.

Next, I'm checking their scope. Is it static or exported? I've filtered the list down, focusing on functions and globals that are exposed or have substantial logic involved. My revised key list now looks like: `zswap_store`, `zswap_load`, `zswap_invalidate`, `zswap_writeback_entry`, `__swap_cache_do_add_folio`, `__swap_cache_do_del_folio`, `__swap_cluster_put_entry`, `__swap_cluster_free_entries`, and `__swap_cluster_dup_entry`. I then identified that `zswap_swp_tb_dup_count` and `zswap_swp_tb_put_count` are also global, meaning they need to be watched.

Now I understand the core of the change: the patch is fundamentally altering how zswap manages compressed pages. It's embedding the `zswap_entry` pointers directly into the `swap_cluster_info` structures. This is a big deal because it means the core swap allocation, freeing, and lookup paths are being reshaped. This is where I start thinking about the potential pitfalls and the areas I need to focus on.

This approach will introduce memory management considerations, and I need to ensure that concurrent swap operations don't lead to memory leaks, use-after-frees, or errors in swap accounting. I'm already mentally flagging this for thorough fuzzing and rigorous testing.

Finally, I need to make sure the relevant configurations are set. I need `ZSWAP` enabled, and, for default usage, `ZSWAP_DEFAULT_ON` as well. With this done, I'm ready to call the next step.



8/2 2026/07/07 15:11 tool set-results
0m Args:
{
  "EnableConfigs": [
    "ZSWAP",
    "ZSWAP_DEFAULT_ON"
  ],
  "FocusSymbols": [
    "zswap_store",
    "zswap_load",
    "zswap_invalidate",
    "zswap_writeback_entry",
    "__swap_cache_do_add_folio",
    "__swap_cache_do_del_folio",
    "__swap_cluster_put_entry",
    "__swap_cluster_free_entries",
    "__swap_cluster_dup_entry",
    "zswap_swp_tb_dup_count",
    "zswap_swp_tb_put_count"
  ],
  "Reasoning": "The patch fundamentally changes how zswap tracks compressed pages by embedding `zswap_entry` pointers directly into the core swap table (`swap_cluster_info`) rather than using a separate xarray (`zswap_trees`). This affects core swap allocation, freeing, and lookup paths, requiring fuzzing to ensure no memory leaks, use-after-frees, or swap accounting errors occur during concurrent swap operations.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "ZSWAP",
    "ZSWAP_DEFAULT_ON"
  ],
  "FocusSymbols": [
    "zswap_store",
    "zswap_load",
    "zswap_invalidate",
    "zswap_writeback_entry",
    "__swap_cache_do_add_folio",
    "__swap_cache_do_del_folio",
    "__swap_cluster_put_entry",
    "__swap_cluster_free_entries",
    "__swap_cluster_dup_entry",
    "zswap_swp_tb_dup_count",
    "zswap_swp_tb_put_count"
  ],
  "Reasoning": "The patch fundamentally changes how zswap tracks compressed pages by embedding `zswap_entry` pointers directly into the core swap table (`swap_cluster_info`) rather than using a separate xarray (`zswap_trees`). This affects core swap allocation, freeing, and lookup paths, requiring fuzzing to ensure no memory leaks, use-after-frees, or swap accounting errors occur during concurrent swap operations.",
  "WorthFuzzing": true
}

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