[PATCH v5 05/11] mm, swap: enable THP swapin for vswap entries

From: Nhat Pham

Date: Fri Sep 18 2026 - 14:03:27 EST


Swap a large anon folio back in as a unit when its vswap entries share a
contiguous run of physical swap slots on a synchronous IO device,
instead of always falling back to order-0 faults.

A zswap-backed or mixed-backing batch is still refused, and the fault
retries at a smaller order.

Signed-off-by: Nhat Pham <nphamcs@xxxxxxxxx>
---
mm/memory.c | 5 +++--
mm/swap_state.c | 17 +++++++++++++----
mm/zswap.c | 6 +++++-
3 files changed, 21 insertions(+), 7 deletions(-)

diff --git a/mm/memory.c b/mm/memory.c
index e9e05e31c4f8..e052de3b4461 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4882,9 +4882,10 @@ static unsigned long thp_swapin_suitable_orders(struct vm_fault *vmf)
* lack handling for such cases, so fallback to swapping in order-0
* folio.
*
- * THP swapin for vswap is not supported yet either.
+ * Vswap entries are checked later, under the cluster lock in
+ * __swap_cache_add_check().
*/
- if (is_vswap_entry(entry) || !zswap_never_enabled())
+ if (!is_vswap_entry(entry) && !zswap_never_enabled())
return 0;

/*
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 657622cfd7f1..2107d05ae8d5 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -165,6 +165,9 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci,
unsigned int ci_off, ci_end;
unsigned long old_tb;
bool is_zero;
+ struct swap_cluster_info_dynamic *ci_dyn;
+ enum vswap_backing_type type;
+ int ret;

lockdep_assert_held(&ci->lock);

@@ -193,11 +196,17 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci,
return 0;

/*
- * Reject a vswap batch so swap_cache_alloc_folio falls back to
- * order 0.
+ * For a vswap entry batch, reject if the backing is not THP-amenable
+ * (e.g. uniformly ZSWAP, or mixed). The order-fallback loop in
+ * swap_cache_alloc_folio will retry with a smaller order on -EBUSY.
*/
- if (is_vswap_entry(targ_entry))
- return -EBUSY;
+ if (is_vswap_entry(targ_entry)) {
+ ci_dyn = container_of(ci, struct swap_cluster_info_dynamic, ci);
+ ret = __vswap_check_backing(ci_dyn, round_down(ci_off, nr),
+ nr, &type);
+ if (ret != nr || type == VSWAP_ZSWAP)
+ return -EBUSY;
+ }

is_zero = __swap_table_test_zero(ci, ci_off);
ci_off = round_down(ci_off, nr);
diff --git a/mm/zswap.c b/mm/zswap.c
index bbfaeac00355..3e1aa295f9dd 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -1688,9 +1688,13 @@ int zswap_load(struct folio *folio)
* range on the backing device, so scan the range rather than rejecting
* it outright. The caller has pinned every slot, so zswap cannot start
* a store or a writeback into the range while we look.
+ *
+ * A vswap batch is checked when the folio enters the swap cache, and
+ * its backing cannot change after that.
*/
if (folio_test_large(folio)) {
- if (WARN_ON_ONCE(zswap_is_present(swp,
+ if (WARN_ON_ONCE(!swap_is_vswap(si) &&
+ zswap_is_present(swp,
folio_nr_pages(folio)))) {
folio_unlock(folio);
return -EIO;
--
2.53.0-Meta