
#include <pthread.h>

#include <stdio.h>
#include <unistd.h>
#include <my_assert.h>

int __dfwmalloc_get_n_arenas (void);
void __dfwmalloc_get_stat_adjust (struct dfwmalloc_stats *s);

static long get_blocks (void)
{
    long r;
    struct dfwmalloc_stats st_adjust;
    r = dfwmalloc_alloced_blocks ();
    __dfwmalloc_get_stat_adjust (&st_adjust);
#if defined(WITH_BTANDCHECK) && !defined(NULL_MODE)
    r += st_adjust.blocks_small + st_adjust.blocks;
#endif
    return r;
}

static pthread_attr_t my_thread_attr;
static pthread_attr_t my_thread_attr_nonjoinable;

static int my_pthread_create (pthread_t * thread, void *(*start_routine) (void *), void *arg)
{
    int r;
    r = pthread_create (thread, &my_thread_attr, start_routine, arg);
    return r;
}

struct cmd_args {
    void *retcheck;
    char *p[1000];
};

static void puts_flush (const char *s)
{
    printf ("%s", s);
    fflush (stdout);
}

#define HASH(c)         ((((c) + 9UL) * ((c) + 2UL) * 401UL) >> 1)

static unsigned int p2hashI(ULONG p)
{
    unsigned int r = 0;
    r ^= HASH(p >> 33);
    r ^= HASH(p >> 22);
    r ^= HASH(p >> 11);
    r ^= HASH(p);
    r ^= HASH(p << 5);
    return r;
}

static unsigned char p2hashC(ULONG p)
{
    unsigned char r = 0;
    r ^= HASH(p >> 30);
    r ^= HASH(p >> 20);
    r ^= HASH(p >> 10);
    r ^= HASH(p);
    return r;
}

static void thrash_fn (struct cmd_args *arg)
{
    long i;
#ifdef USE_TLS
    long iters = 2000000;
#else
    long iters = 200000;
#endif
    for (i = 0; i < iters; i++) {
        int j;
        char **p;
        if (!(i % (iters / 100)))
            puts_flush (".");
        j = (i % 1000L);
        p = &arg->p[j];
        if (!*p) {
            int *q;
            char *u;
            int l;
#define TEST_BLOCK              50000
            l = 5 + (random () % TEST_BLOCK);
            q = (int *) malloc (l);

            assert (q);
            assert (dfwmalloc_check (q) >= l);

            u = (char *) q;
            q[0] = l;
            if (l >= 9)
                q[1] = p2hashI ((ULONG) q);
            u[l - 1] = p2hashC ((ULONG) q);
            asm volatile ("":::"memory");
            *p = u;
        }
    }
    puts_flush ("#");
}


static pthread_mutex_t thread_count_mutex = PTHREAD_MUTEX_INITIALIZER;
static int thread_count;
static int ready_to_start;

static void *thread_fn (void *arg_)
{
    struct cmd_args *arg = (struct cmd_args *) arg_;

    while (!ready_to_start)    /* wait for all threads to start so that system mallocs are at the start of the heap */
        usleep (1000);

    thrash_fn (arg);

    pthread_mutex_lock (&thread_count_mutex);
    thread_count--;
    pthread_mutex_unlock (&thread_count_mutex);

    puts_flush ("%");

#ifdef NO_PTHREAD_EXIT_OVERRIDE
#ifdef USE_TLS
    dfwmalloc_pthread_exit ();
#endif
#endif

    return arg->retcheck;
}




int thrash_alloc_in_one_thread_free_in_another (void)
{
    pthread_t *thread;
    int i, thread_start_count;
    struct cmd_args **a;

    thread_count = 20;
    ready_to_start = 0;

    thread_start_count = thread_count;

    a = (struct cmd_args **) calloc (1, thread_start_count * sizeof (struct cmd_args *));
    assert (a);
    thread = (pthread_t *) calloc (1, sizeof (*thread) * thread_start_count);

    for (i = 0; i < thread_start_count; i++) {
        a[i] = (struct cmd_args *) calloc (1, sizeof (struct cmd_args));
        assert (a[i]);
        a[i]->retcheck = (void *) (unsigned long) i;
        my_pthread_create (&thread[i], thread_fn, a[i]);
        printf ("thrash started thread %p\n", (void *) thread);
    }

    asm volatile ("":::"memory");
    ready_to_start = 1;
    asm volatile ("":::"memory");

    while (thread_count > 0) {
        for (i = 0; i < thread_start_count; i++) {
            int j;
            for (j = 0; j < 1000; j++) {
                char **p;
                p = &a[i]->p[random() % 1000];
                if (*p) {
                    int *q;
                    char *u;
                    int l;
                    q = (int *) *p;
                    u = *p;
                    l = *q;
                    (void) l;
                    (void) u;
                    assert ((unsigned char) u[l - 1] == p2hashC ((ULONG) q));  /* <==  crash was here */
                    if (l >= 9)
                        assert ((unsigned int) q[1] == p2hashI ((ULONG) q));
                    if ((random () & 1)) {
                        u[l - 1] = 0xAA;
                        if (l >= 9)
                            q[1] = 0x55555555;
                        free (q);
                    } else {
                        int l2;
                        unsigned int ti;
                        unsigned char tc;
#if !defined(FEATHERWEIGHT) && !defined(FOR_VALGRIND)
                        l2 = 5 + (random () % TEST_BLOCK);
#else
#ifdef USE_TLS
                        l2 = 5 + ((random () % 100));
#else
                        l2 = 5 + ((random () % 1000) & 0x55555555);
#endif
#endif
                        tc = p2hashC ((ULONG) q);
                        ti = p2hashI ((ULONG) q);
                        q = (int *) realloc (q, l2);
                        assert (q);
                        u = (char *) q;
                        if (l2 >= l) {
                            assert ((unsigned char) u[l - 1] == tc);
                            u[l - 1] = 0xAA;
                        }
                        if (l >= 9 && l2 >= 9) {
                            assert ((unsigned int) q[1] == ti);
                            q[1] = 0x55555555;
                        }
                        free (q);
                    }
                    asm volatile ("":::"memory");
                    *p = NULL;
                }
            }
        }
    }

    puts_flush ("|");

    for (i = 0; i < thread_start_count; i++) {
        int j;
        void *retval;
        puts_flush ("A");
        pthread_join (thread[i], &retval);
        puts_flush ("B");
        assert (retval == (void *) (unsigned long) i);
        puts_flush ("C");
        for (j = 0; j < 1000; j++) {
            char **p;
            p = &a[i]->p[j];
            if (*p) {
                int *q;
                char *u;
                int l;
                q = (int *) *p;
                u = *p;
                l = q[0];
                (void) l;
                (void) u;
                assert ((unsigned char) u[l - 1] == p2hashC ((ULONG) q));
                if (l >= 9)
                    assert ((unsigned int) q[1] == p2hashI ((ULONG) q));
                free (q);
                asm volatile ("":::"memory");
                *p = NULL;
            }
        }
        puts_flush ("D");
        puts_flush ("E");
    }

    free (thread);

    for (i = 0; i < thread_start_count; i++)
        free (a[i]);

    free (a);

    puts_flush ("\n");

    return 0;
}


void entry (void)
{
    int pr;
    int joinable = -1;
    int leaks;

    pr = pthread_attr_init(&my_thread_attr);
    assert (!pr);
    pr = pthread_attr_setstacksize(&my_thread_attr, 16384);
    assert (!pr);

    pr = pthread_attr_init(&my_thread_attr_nonjoinable);
    assert (!pr);
    pr = pthread_attr_setstacksize(&my_thread_attr_nonjoinable, 16384);
    assert (!pr);
    pr = pthread_attr_setdetachstate(&my_thread_attr_nonjoinable, PTHREAD_CREATE_DETACHED);
    assert (!pr);
    pr = pthread_attr_getdetachstate(&my_thread_attr_nonjoinable, &joinable);
    assert (!pr);
    assert (joinable == PTHREAD_CREATE_DETACHED);

    printf ("blocks=%d\n", (int) get_blocks ());

    thrash_alloc_in_one_thread_free_in_another ();

    printf ("blocks=%d\n", (int) get_blocks ());

    dfwmalloc_enable_backtrace ();

    leaks = get_blocks ();

    thrash_alloc_in_one_thread_free_in_another ();

    printf ("blocks => %d - %d = %d\n", (int) get_blocks (), leaks, (int) get_blocks () - leaks);
    assert (get_blocks () - leaks == 0);

    dfwmalloc_log_leaks ();
}

