root/tools/testing/radix-tree/tag_check.c
// SPDX-License-Identifier: GPL-2.0
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>

#include <linux/slab.h>
#include <linux/radix-tree.h>

#include "test.h"


static void
__simple_checks(struct radix_tree_root *tree, unsigned long index, int tag)
{
        unsigned long first = 0;
        int ret;

        item_check_absent(tree, index);
        assert(item_tag_get(tree, index, tag) == 0);

        item_insert(tree, index);
        assert(item_tag_get(tree, index, tag) == 0);
        item_tag_set(tree, index, tag);
        ret = item_tag_get(tree, index, tag);
        assert(ret != 0);
        ret = tag_tagged_items(tree, first, ~0UL, 10, tag, !tag);
        assert(ret == 1);
        ret = item_tag_get(tree, index, !tag);
        assert(ret != 0);
        ret = item_delete(tree, index);
        assert(ret != 0);
        item_insert(tree, index);
        ret = item_tag_get(tree, index, tag);
        assert(ret == 0);
        ret = item_delete(tree, index);
        assert(ret != 0);
        ret = item_delete(tree, index);
        assert(ret == 0);
}

void simple_checks(void)
{
        unsigned long index;
        RADIX_TREE(tree, GFP_KERNEL);

        for (index = 0; index < 10000; index++) {
                __simple_checks(&tree, index, 0);
                __simple_checks(&tree, index, 1);
        }
        verify_tag_consistency(&tree, 0);
        verify_tag_consistency(&tree, 1);
        printv(2, "before item_kill_tree: %d allocated\n", nr_allocated);
        item_kill_tree(&tree);
        rcu_barrier();
        printv(2, "after item_kill_tree: %d allocated\n", nr_allocated);
}

/*
 * Check that tags propagate correctly when extending a tree.
 */
static void extend_checks(void)
{
        RADIX_TREE(tree, GFP_KERNEL);

        item_insert(&tree, 43);
        assert(item_tag_get(&tree, 43, 0) == 0);
        item_tag_set(&tree, 43, 0);
        assert(item_tag_get(&tree, 43, 0) == 1);
        item_insert(&tree, 1000000);
        assert(item_tag_get(&tree, 43, 0) == 1);

        item_insert(&tree, 0);
        item_tag_set(&tree, 0, 0);
        item_delete(&tree, 1000000);
        assert(item_tag_get(&tree, 43, 0) != 0);
        item_delete(&tree, 43);
        assert(item_tag_get(&tree, 43, 0) == 0);        /* crash */
        assert(item_tag_get(&tree, 0, 0) == 1);

        verify_tag_consistency(&tree, 0);

        item_kill_tree(&tree);
}

/*
 * Check that tags propagate correctly when contracting a tree.
 */
static void contract_checks(void)
{
        struct item *item;
        int tmp;
        RADIX_TREE(tree, GFP_KERNEL);

        tmp = 1<<RADIX_TREE_MAP_SHIFT;
        item_insert(&tree, tmp);
        item_insert(&tree, tmp+1);
        item_tag_set(&tree, tmp, 0);
        item_tag_set(&tree, tmp, 1);
        item_tag_set(&tree, tmp+1, 0);
        item_delete(&tree, tmp+1);
        item_tag_clear(&tree, tmp, 1);

        assert(radix_tree_gang_lookup_tag(&tree, (void **)&item, 0, 1, 0) == 1);
        assert(radix_tree_gang_lookup_tag(&tree, (void **)&item, 0, 1, 1) == 0);

        assert(item_tag_get(&tree, tmp, 0) == 1);
        assert(item_tag_get(&tree, tmp, 1) == 0);

        verify_tag_consistency(&tree, 0);
        item_kill_tree(&tree);
}

/*
 * Stupid tag thrasher
 *
 * Create a large linear array corresponding to the tree.   Each element in
 * the array is coherent with each node in the tree
 */

enum {
        NODE_ABSENT = 0,
        NODE_PRESENT = 1,
        NODE_TAGGED = 2,
};

#define THRASH_SIZE             (1000 * 1000)
#define N 127
#define BATCH   33

static void gang_check(struct radix_tree_root *tree,
                        char *thrash_state, int tag)
{
        struct item *items[BATCH];
        int nr_found;
        unsigned long index = 0;
        unsigned long last_index = 0;

        while ((nr_found = radix_tree_gang_lookup_tag(tree, (void **)items,
                                        index, BATCH, tag))) {
                int i;

                for (i = 0; i < nr_found; i++) {
                        struct item *item = items[i];

                        while (last_index < item->index) {
                                assert(thrash_state[last_index] != NODE_TAGGED);
                                last_index++;
                        }
                        assert(thrash_state[last_index] == NODE_TAGGED);
                        last_index++;
                }
                index = items[nr_found - 1]->index + 1;
        }
}

static void do_thrash(struct radix_tree_root *tree, char *thrash_state, int tag)
{
        int insert_chunk;
        int delete_chunk;
        int tag_chunk;
        int untag_chunk;
        int total_tagged = 0;
        int total_present = 0;

        for (insert_chunk = 1; insert_chunk < THRASH_SIZE; insert_chunk *= N)
        for (delete_chunk = 1; delete_chunk < THRASH_SIZE; delete_chunk *= N)
        for (tag_chunk = 1; tag_chunk < THRASH_SIZE; tag_chunk *= N)
        for (untag_chunk = 1; untag_chunk < THRASH_SIZE; untag_chunk *= N) {
                int i;
                unsigned long index;
                int nr_inserted = 0;
                int nr_deleted = 0;
                int nr_tagged = 0;
                int nr_untagged = 0;
                int actual_total_tagged;
                int actual_total_present;

                for (i = 0; i < insert_chunk; i++) {
                        index = rand() % THRASH_SIZE;
                        if (thrash_state[index] != NODE_ABSENT)
                                continue;
                        item_check_absent(tree, index);
                        item_insert(tree, index);
                        assert(thrash_state[index] != NODE_PRESENT);
                        thrash_state[index] = NODE_PRESENT;
                        nr_inserted++;
                        total_present++;
                }

                for (i = 0; i < delete_chunk; i++) {
                        index = rand() % THRASH_SIZE;
                        if (thrash_state[index] == NODE_ABSENT)
                                continue;
                        item_check_present(tree, index);
                        if (item_tag_get(tree, index, tag)) {
                                assert(thrash_state[index] == NODE_TAGGED);
                                total_tagged--;
                        } else {
                                assert(thrash_state[index] == NODE_PRESENT);
                        }
                        item_delete(tree, index);
                        assert(thrash_state[index] != NODE_ABSENT);
                        thrash_state[index] = NODE_ABSENT;
                        nr_deleted++;
                        total_present--;
                }

                for (i = 0; i < tag_chunk; i++) {
                        index = rand() % THRASH_SIZE;
                        if (thrash_state[index] != NODE_PRESENT) {
                                if (item_lookup(tree, index))
                                        assert(item_tag_get(tree, index, tag));
                                continue;
                        }
                        item_tag_set(tree, index, tag);
                        item_tag_set(tree, index, tag);
                        assert(thrash_state[index] != NODE_TAGGED);
                        thrash_state[index] = NODE_TAGGED;
                        nr_tagged++;
                        total_tagged++;
                }

                for (i = 0; i < untag_chunk; i++) {
                        index = rand() % THRASH_SIZE;
                        if (thrash_state[index] != NODE_TAGGED)
                                continue;
                        item_check_present(tree, index);
                        assert(item_tag_get(tree, index, tag));
                        item_tag_clear(tree, index, tag);
                        item_tag_clear(tree, index, tag);
                        assert(thrash_state[index] != NODE_PRESENT);
                        thrash_state[index] = NODE_PRESENT;
                        nr_untagged++;
                        total_tagged--;
                }

                actual_total_tagged = 0;
                actual_total_present = 0;
                for (index = 0; index < THRASH_SIZE; index++) {
                        switch (thrash_state[index]) {
                        case NODE_ABSENT:
                                item_check_absent(tree, index);
                                break;
                        case NODE_PRESENT:
                                item_check_present(tree, index);
                                assert(!item_tag_get(tree, index, tag));
                                actual_total_present++;
                                break;
                        case NODE_TAGGED:
                                item_check_present(tree, index);
                                assert(item_tag_get(tree, index, tag));
                                actual_total_present++;
                                actual_total_tagged++;
                                break;
                        }
                }

                gang_check(tree, thrash_state, tag);

                printv(2, "%d(%d) %d(%d) %d(%d) %d(%d) / "
                                "%d(%d) present, %d(%d) tagged\n",
                        insert_chunk, nr_inserted,
                        delete_chunk, nr_deleted,
                        tag_chunk, nr_tagged,
                        untag_chunk, nr_untagged,
                        total_present, actual_total_present,
                        total_tagged, actual_total_tagged);
        }
}

static void thrash_tags(void)
{
        RADIX_TREE(tree, GFP_KERNEL);
        char *thrash_state;

        thrash_state = malloc(THRASH_SIZE);
        memset(thrash_state, 0, THRASH_SIZE);

        do_thrash(&tree, thrash_state, 0);

        verify_tag_consistency(&tree, 0);
        item_kill_tree(&tree);
        free(thrash_state);
}

static void leak_check(void)
{
        RADIX_TREE(tree, GFP_KERNEL);

        item_insert(&tree, 1000000);
        item_delete(&tree, 1000000);
        item_kill_tree(&tree);
}

static void __leak_check(void)
{
        RADIX_TREE(tree, GFP_KERNEL);

        printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
        item_insert(&tree, 1000000);
        printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
        item_delete(&tree, 1000000);
        printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
        item_kill_tree(&tree);
        printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
}

static void single_check(void)
{
        struct item *items[BATCH];
        RADIX_TREE(tree, GFP_KERNEL);
        int ret;
        unsigned long first = 0;

        item_insert(&tree, 0);
        item_tag_set(&tree, 0, 0);
        ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 0, BATCH, 0);
        assert(ret == 1);
        ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 1, BATCH, 0);
        assert(ret == 0);
        verify_tag_consistency(&tree, 0);
        verify_tag_consistency(&tree, 1);
        ret = tag_tagged_items(&tree, first, 10, 10, XA_MARK_0, XA_MARK_1);
        assert(ret == 1);
        ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 0, BATCH, 1);
        assert(ret == 1);
        item_tag_clear(&tree, 0, 0);
        ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 0, BATCH, 0);
        assert(ret == 0);
        item_kill_tree(&tree);
}

void tag_check(void)
{
        single_check();
        extend_checks();
        contract_checks();
        rcu_barrier();
        printv(2, "after extend_checks: %d allocated\n", nr_allocated);
        __leak_check();
        leak_check();
        rcu_barrier();
        printv(2, "after leak_check: %d allocated\n", nr_allocated);
        simple_checks();
        rcu_barrier();
        printv(2, "after simple_checks: %d allocated\n", nr_allocated);
        thrash_tags();
        rcu_barrier();
        printv(2, "after thrash_tags: %d allocated\n", nr_allocated);
}