From: "Kiryl Shutsemau (Meta)" <[email protected]>

The mTHP collapse cases only run when the caller names both the context
and an order, so a plain ./khugepaged covers the PMD contexts on anon and
nothing else.  run_vmtests.sh pinned order 4 and covered no other.

Run the mTHP cases once per supported anon THP order below the PMD when
-c is absent, and pull that context into both the no-argument invocation
and "all".  Around that:

  - -c still pins one order, and now says what is wrong instead of
    printing the usage text.  An order at or below the -s source order is
    skipped: the sources would already be the size being asked for.

  - Both orders end up as array indices and shift counts, so -s and -c
    are range-checked before they get there.

  - The mTHP context has only anon cases, so a run that names a different
    mem_type -- "all:shmem", say -- drops it again rather than refusing
    to start.  Naming both explicitly still refuses.

  - A case carries the order it was registered at, so a result names it:

      # Run test: collapse_single_mthp (mthp_khugepaged:anon, order 6)

On x86-64 with 4K pages that is orders 2 through 8, and ./khugepaged goes
from 28 results to 77 in 21 seconds, so run_vmtests.sh can drop its
pinned order-4 line.

Assisted-by: Claude-Code:claude-opus-5
Tested-by: Muhammad Usama Anjum <[email protected]>
Signed-off-by: Kiryl Shutsemau (Meta) <[email protected]>
---
 tools/testing/selftests/mm/khugepaged.c   | 105 +++++++++++++++++-----
 tools/testing/selftests/mm/run_vmtests.sh |   2 -
 2 files changed, 85 insertions(+), 22 deletions(-)

diff --git a/tools/testing/selftests/mm/khugepaged.c 
b/tools/testing/selftests/mm/khugepaged.c
index 856decd2950a..172e7307eeee 100644
--- a/tools/testing/selftests/mm/khugepaged.c
+++ b/tools/testing/selftests/mm/khugepaged.c
@@ -31,6 +31,9 @@ static unsigned long page_size;
 static int hpage_pmd_nr;
 static int anon_order;
 static int collapse_order;
+static bool collapse_order_given;
+static int collapse_orders[NR_ORDERS];
+static int nr_collapse_orders;
 static int pagemap_fd = -1;
 static int kpageflags_fd = -1;
 
@@ -1550,6 +1553,7 @@ static void usage(void)
        fprintf(stderr, "\t\t-s: mTHP size, expressed as page order.\n");
        fprintf(stderr, "\t\t    Defaults to 0. Use this size for anon or shmem 
allocations.\n");
        fprintf(stderr, "\t\t-c: collapse order for mTHP collapse, expressed as 
page order.\n");
+       fprintf(stderr, "\t\t    Defaults to every supported order below the 
PMD.\n");
        fprintf(stderr, "\t\t    With -s, -s names the mTHP source order for 
the\n");
        fprintf(stderr, "\t\t    mixed-source case (source order below the 
target).\n");
        exit(1);
@@ -1557,6 +1561,7 @@ static void usage(void)
 
 static void parse_test_type(int argc, char **argv)
 {
+       bool mthp_context_implied = false;
        int opt;
        char *buf;
        const char *token;
@@ -1568,6 +1573,7 @@ static void parse_test_type(int argc, char **argv)
                        break;
                case 'c':
                        collapse_order = atoi(optarg);
+                       collapse_order_given = true;
                        break;
                case 'h':
                default:
@@ -1575,12 +1581,25 @@ static void parse_test_type(int argc, char **argv)
                }
        }
 
+       /*
+        * Both orders end up as array indices and shift counts, so neither
+        * can be negative, and a zero collapse order asks for base pages.
+        */
+       if (anon_order < 0 || anon_order > hpage_pmd_order)
+               ksft_exit_fail_msg("-s takes an order in 0..%d, not %d\n",
+                                  hpage_pmd_order, anon_order);
+       if (collapse_order_given &&
+           (collapse_order <= 0 || collapse_order >= hpage_pmd_order))
+               ksft_exit_fail_msg("-c takes an order in 1..%d, not %d\n",
+                                  hpage_pmd_order - 1, collapse_order);
+
        argv += optind;
        argc -= optind;
 
        if (argc == 0) {
-               /* Backwards compatibility */
+               /* Everything that needs no argument of its own: anon, every 
context */
                khugepaged_context =  &__khugepaged_context;
+               mthp_khugepaged_context =  &__mthp_khugepaged_context;
                madvise_context =  &__madvise_context;
                anon_ops = &__anon_ops;
                return;
@@ -1591,13 +1610,19 @@ static void parse_test_type(int argc, char **argv)
 
        if (!strcmp(token, "all")) {
                khugepaged_context =  &__khugepaged_context;
+               mthp_khugepaged_context =  &__mthp_khugepaged_context;
                madvise_context =  &__madvise_context;
+
+               /*
+                * "all" sweeps the mTHP context in, but it only has anon
+                * cases: step it aside for the other mem_types rather than
+                * refusing the whole run.
+                */
+               mthp_context_implied = true;
        } else if (!strcmp(token, "khugepaged")) {
                khugepaged_context =  &__khugepaged_context;
        } else if (!strcmp(token, "mthp_khugepaged")) {
                mthp_khugepaged_context =  &__mthp_khugepaged_context;
-               if (collapse_order <= 0 || collapse_order >= hpage_pmd_order)
-                       usage();
        } else if (!strcmp(token, "madvise")) {
                madvise_context =  &__madvise_context;
        } else {
@@ -1613,20 +1638,20 @@ static void parse_test_type(int argc, char **argv)
                read_write_file_write_ops =  &__read_write_file_write_ops;
                anon_ops = &__anon_ops;
                shmem_ops = &__shmem_ops;
-               if (mthp_khugepaged_context)
-                       usage();
        } else if (!strcmp(buf, "anon")) {
                anon_ops = &__anon_ops;
        } else if (!strcmp(buf, "file")) {
                read_only_file_ops =  &__read_only_file_ops;
                read_write_file_read_ops =  &__read_write_file_read_ops;
                read_write_file_write_ops =  &__read_write_file_write_ops;
-               if (mthp_khugepaged_context)
+               if (mthp_khugepaged_context && !mthp_context_implied)
                        usage();
+               mthp_khugepaged_context = NULL;
        } else if (!strcmp(buf, "shmem")) {
                shmem_ops = &__shmem_ops;
-               if (mthp_khugepaged_context)
+               if (mthp_khugepaged_context && !mthp_context_implied)
                        usage();
+               mthp_khugepaged_context = NULL;
        } else {
                usage();
        }
@@ -1648,6 +1673,7 @@ struct test_case {
        struct mem_ops *ops;
        const char *desc;
        test_fn fn;
+       int order;              /* mTHP contexts: the collapse order */
 };
 
 #define MAX_TEST_CASES 256
@@ -1663,6 +1689,7 @@ static int nr_test_cases;
                        .ops    = o,                                    \
                        .desc   = #t,                                   \
                        .fn     = t,                                    \
+                       .order  = collapse_order,                       \
                };                                                      \
        }                                                               \
        } while (0)
@@ -1703,10 +1730,37 @@ int main(int argc, char **argv)
 
        parse_test_type(argc, argv);
 
-       if (mthp_khugepaged_context &&
-           !(thp_supported_orders() & (1UL << collapse_order)))
-               ksft_exit_skip("Order %d is not a supported anon THP order\n",
-                              collapse_order);
+       if (mthp_khugepaged_context) {
+               unsigned long orders = thp_supported_orders();
+
+               if (collapse_order_given) {
+                       /* -c pins one order; it has to be one we can build */
+                       if (!(orders & (1UL << collapse_order)))
+                               ksft_exit_skip("Order %d is not a supported 
anon THP order\n",
+                                              collapse_order);
+                       if (collapse_order <= anon_order)
+                               ksft_exit_skip("-c %d needs a source order 
below it, -s says %d\n",
+                                              collapse_order, anon_order);
+                       collapse_orders[nr_collapse_orders++] = collapse_order;
+               } else {
+                       /*
+                        * Otherwise every order a collapse could produce.  -s
+                        * makes the fault path hand out folios of that order,
+                        * so a target at or below it has nothing to collapse:
+                        * the sources are already the size being asked for.
+                        */
+                       int first = anon_order + 1;
+
+                       if (first < MIN_MTHP_ORDER)
+                               first = MIN_MTHP_ORDER;
+                       for (int i = first; i < hpage_pmd_order; i++) {
+                               if (orders & (1UL << i))
+                                       collapse_orders[nr_collapse_orders++] = 
i;
+                       }
+                       if (!nr_collapse_orders)
+                               ksft_print_msg("mTHP cases skipped: no order 
above the source\n");
+               }
+       }
 
        if (mthp_khugepaged_context) {
                pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
@@ -1760,7 +1814,17 @@ int main(int argc, char **argv)
        TEST(collapse_full, khugepaged_context, read_write_file_read_ops);
        TEST(collapse_full, khugepaged_context, read_write_file_write_ops);
        TEST(collapse_full, khugepaged_context, shmem_ops);
-       TEST(collapse_full, mthp_khugepaged_context, anon_ops);
+       for (int i = 0; i < nr_collapse_orders; i++) {
+               collapse_order = collapse_orders[i];
+               TEST(collapse_full, mthp_khugepaged_context, anon_ops);
+               TEST(collapse_empty, mthp_khugepaged_context, anon_ops);
+               TEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops);
+               TEST(collapse_order_single_window, mthp_khugepaged_context, 
anon_ops);
+               TEST(collapse_order_partial_window, mthp_khugepaged_context, 
anon_ops);
+               TEST(collapse_order_max_ptes_none, mthp_khugepaged_context, 
anon_ops);
+               TEST(collapse_order_mixed_sources, mthp_khugepaged_context, 
anon_ops);
+       }
+
        TEST(collapse_full, madvise_context, anon_ops);
        TEST(collapse_full, madvise_context, read_only_file_ops);
        TEST(collapse_full, madvise_context, read_write_file_read_ops);
@@ -1768,15 +1832,8 @@ int main(int argc, char **argv)
        TEST(collapse_full, madvise_context, shmem_ops);
 
        TEST(collapse_empty, khugepaged_context, anon_ops);
-       TEST(collapse_empty, mthp_khugepaged_context, anon_ops);
        TEST(collapse_empty, madvise_context, anon_ops);
 
-       TEST(collapse_single_mthp, mthp_khugepaged_context, anon_ops);
-       TEST(collapse_order_single_window, mthp_khugepaged_context, anon_ops);
-       TEST(collapse_order_partial_window, mthp_khugepaged_context, anon_ops);
-       TEST(collapse_order_max_ptes_none, mthp_khugepaged_context, anon_ops);
-       TEST(collapse_order_mixed_sources, mthp_khugepaged_context, anon_ops);
-
        TEST(collapse_single_pte_entry, khugepaged_context, anon_ops);
        TEST(collapse_single_pte_entry, khugepaged_context, read_only_file_ops);
        TEST(collapse_single_pte_entry, khugepaged_context, 
read_write_file_read_ops);
@@ -1849,7 +1906,15 @@ int main(int argc, char **argv)
        for (int i = 0; i < nr_test_cases; i++) {
                struct test_case *t = &test_cases[i];
 
-               ksft_print_msg("\n# Run test: %s (%s:%s)\n", t->desc, 
t->ctx->name, t->ops->name);
+               if (t->ctx == &__mthp_khugepaged_context) {
+                       collapse_order = t->order;
+                       ksft_print_msg("\n# Run test: %s (%s:%s, order %d)\n",
+                                      t->desc, t->ctx->name, t->ops->name,
+                                      t->order);
+               } else {
+                       ksft_print_msg("\n# Run test: %s (%s:%s)\n", t->desc,
+                                      t->ctx->name, t->ops->name);
+               }
                t->fn(t->ctx, t->ops);
        }
 
diff --git a/tools/testing/selftests/mm/run_vmtests.sh 
b/tools/testing/selftests/mm/run_vmtests.sh
index 2652a7920b80..8bf898b71350 100755
--- a/tools/testing/selftests/mm/run_vmtests.sh
+++ b/tools/testing/selftests/mm/run_vmtests.sh
@@ -412,8 +412,6 @@ CATEGORY="thp" run_test ./khugepaged all:shmem
 
 CATEGORY="thp" run_test ./khugepaged -s 4 all:shmem
 
-CATEGORY="thp" run_test ./khugepaged -c 4 mthp_khugepaged:anon
-
 # Try to create XFS if not provided
 if [ -z "${SPLIT_HUGE_PAGE_TEST_XFS_PATH}" ]; then
     if test_selected "thp"; then
-- 
2.54.0


Reply via email to