/***************************************************************************
 *
 * 18.atomic.add.cpp - test exercising the __rw_atomic_preincrement()
 *                     and __rw_atomic_predecrement function templates
 *
 * $Id$
 *
 ***************************************************************************
 *
 * Licensed to the Apache Software  Foundation (ASF) under one or more
 * contributor  license agreements.  See  the NOTICE  file distributed
 * with  this  work  for  additional information  regarding  copyright
 * ownership.   The ASF  licenses this  file to  you under  the Apache
 * License, Version  2.0 (the  "License"); you may  not use  this file
 * except in  compliance with the License.   You may obtain  a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the  License is distributed on an  "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY  KIND, either  express or
 * implied.   See  the License  for  the  specific language  governing
 * permissions and limitations under the License.
 *
 * Copyright 2003-2006 Rogue Wave Software.
 * 
 **************************************************************************/

#include <stdio.h>
#include <string.h>

#include <rw/_mutex.h>

#include <rw_any.h>
#include <rw_cmdopt.h>
#include <rw_driver.h>
#include <rw_thread.h>   // for rw_thread_create()

/**************************************************************************/

struct thr_args_base
{
    static enum tag_t {
        Char, SChar, UChar,
        Short, UShort, Int, UInt, Long, ULong,
        LLong, ULLong
    } type_tag_;
};

thr_args_base::tag_t thr_args_base::type_tag_;

template <class intT>
struct thr_args: thr_args_base
{
    unsigned         threadno_;     // thread ordinal number
    static int       inc_;          // increment, decrement, or both
    static unsigned  nincr_;        // number of increments
    static intT     *shared_;       // shared variables
    static unsigned  nthreads_;     // number of threads

    static intT* get_array ();
};

template <class intT>
int thr_args<intT>::inc_;

template <class intT>
unsigned thr_args<intT>::nincr_;

template <class intT>
intT* thr_args<intT>::shared_ = thr_args<intT>::get_array ();

template <class intT>
unsigned thr_args<intT>::nthreads_;

// working around compiler bugs that prevent us from defining
// a static array data member of a class template (PR #30009)
template <class intT>
intT* thr_args<intT>::get_array ()
{
    static intT array [2];
    return array;
}

/**************************************************************************/


template <class T>
T preincrement (T &x)
{
#ifndef _RWSTD_REENTRANT

    return ++x;

#else   // if defined (_RWSTD_REENTRANT)

    return _RW::__rw_atomic_preincrement (x, false);

#endif   // _RWSTD_REENTRANT
}


template <class T>
T predecrement (T &x)
{
#ifndef _RWSTD_REENTRANT

    return --x;

#else   // if defined (_RWSTD_REENTRANT)

    return _RW::__rw_atomic_predecrement (x, false);

#endif   // _RWSTD_REENTRANT
}

/**************************************************************************/


template <class intT>
void* thread_routine (thr_args<intT> *args)
{
    printf ("thread %u starting to %screment\n",
            args->threadno_,
              args->inc_ < 0 ? "de"
            : args->inc_ > 0 ? "in"
            : "decrement and in");

    if (args->inc_ > 0) {

        // exercise atomic_preincrement() in a tight loop

        for (unsigned i = 0; i != args->nincr_; ++i) {
            preincrement (args->shared_ [0]);
            preincrement (args->shared_ [1]);
        }
    }
    else if (args->inc_ < 0) {

        // exercise atomic_predecrement() in a tight loop

        for (unsigned i = 0; i != args->nincr_; ++i) {
            predecrement (args->shared_ [0]);
            predecrement (args->shared_ [1]);
        }
    }
    else {

        // exercise both atomic_preincrement() and atomic_predecrement()

        for (unsigned i = 0; i != args->nincr_; ++i) {
            preincrement (args->shared_ [0]);
            predecrement (args->shared_ [0]);
            preincrement (args->shared_ [1]);
            predecrement (args->shared_ [1]);
        }
    }

    return 0;
}

/**************************************************************************/

extern "C" void* thread_routine (void *arg)
{
    thr_args_base* const args = (thr_args_base*)arg;

    switch (args->type_tag_) {

    case thr_args_base::Char:
        return thread_routine ((thr_args<char>*)(arg));
    case thr_args_base::SChar:
        return thread_routine ((thr_args<signed char>*)(arg));
    case thr_args_base::UChar:
        return thread_routine ((thr_args<unsigned char>*)(arg));

    case thr_args_base::Short:
        return thread_routine ((thr_args<short>*)(arg));
    case thr_args_base::UShort:
        return thread_routine ((thr_args<unsigned short>*)(arg));

    case thr_args_base::Int:
        return thread_routine ((thr_args<int>*)(arg));
    case thr_args_base::UInt:
        return thread_routine ((thr_args<unsigned int>*)(arg));

    case thr_args_base::Long:
        return thread_routine ((thr_args<long>*)(arg));
    case thr_args_base::ULong:
        return thread_routine ((thr_args<unsigned long>*)(arg));

#ifdef _RWSTD_LONG_LONG

    case thr_args_base::LLong:
        return thread_routine ((thr_args<_RWSTD_LONG_LONG>*)(arg));
    case thr_args_base::ULLong:
        return thread_routine ((thr_args<unsigned _RWSTD_LONG_LONG>*)(arg));

#endif   // _RWSTD_LONG_LONG

    };

    return 0;
}

/**************************************************************************/

/* extern */ int rw_opt_nloops   = 1024 * 1024;
/* extern */ int rw_opt_nthreads = 4;

#define MAX_THREADS   32


template <class intT>
void run_test (intT, thr_args_base::tag_t tag, int inc)
{
    static const char* const tname = rw_any_t (intT ()).type_name ();

    if (!rw_enabled (tname)) {
        rw_note (0, 0, 0, "%s test disabled", tname);
        return;
    }

#if defined (_RWSTD_REENTRANT)

    static const char* const fun =
          inc < 0 ? "__rw_atomic_predecrement"
        : inc > 0 ? ":__rw_atomic_preincrement"
        : "__rw_atomic_pre{in,de}crement";

    rw_info (0, 0, 0, "__rw::%s (%s&): %d iterations in %d threads",
             fun, tname, rw_opt_nloops, rw_opt_nthreads);

    rw_thread_t tid [MAX_THREADS];

    typedef thr_args<intT> Args;

    Args::nthreads_   = rw_opt_nthreads;
    Args::type_tag_   = tag;
    Args::inc_        = inc;
    Args::nincr_      = unsigned (rw_opt_nloops);
    Args::shared_ [0] = intT ();
    Args::shared_ [1] = intT ();

    _RWSTD_ASSERT (Args::nthreads_ < sizeof tid / sizeof *tid);

    Args args [sizeof tid / sizeof *tid];

    memset (args, 0, sizeof args);

    for (unsigned i = 0; i != Args::nthreads_; ++i) {

        args [i].threadno_ = i;

        rw_fatal (0 == rw_thread_create (tid + i, 0, thread_routine, args + i),
                  0, __LINE__, "thread_create() failed");
    }
            
    for (unsigned i = 0; i != Args::nthreads_; ++i) {

        rw_error (0 == rw_thread_join (tid [i], 0), 0, __LINE__,
                  "thread_join() failed");
    }

    // compute the expected result
    intT expect = intT ();

    if (inc < 0) {
        for (unsigned i = 0; i != Args::nthreads_ * Args::nincr_; ++i)
            --expect;
    }
    else if (inc > 0) {
        for (unsigned i = 0; i != Args::nthreads_ * Args::nincr_; ++i)
            ++expect;
    }

    // verify that the final value of the variable shared among all
    // threads equals the number of increments or decrements performed
    // by all threads
    rw_assert (Args::shared_ [0] == expect, 0, __LINE__,
               "1. %s (%s&); %s == %s failed",
               fun, tname, TOSTR (Args::shared_ [0]), TOSTR (expect));

    rw_assert (Args::shared_ [1] == expect, 0, __LINE__,
               "2. %s (%s&); %s == %s failed",
               fun, tname, TOSTR (Args::shared_ [1]), TOSTR (expect));

#else   // if !defined (_RWSTD_REENTRANT)

    _RWSTD_UNUSED (tag);
    _RWSTD_UNUSED (inc);

#endif   // _RWSTD_REENTRANT
}

/**************************************************************************/

static int
run_test (int, char**)
{
    // exercise atomic subtract
    run_test ((char)0, thr_args_base::Char, -1);
    run_test ((signed char)0, thr_args_base::SChar, -1);
    run_test ((unsigned char)0, thr_args_base::UChar, -1);

    run_test ((short)0, thr_args_base::Short, -1);
    run_test ((unsigned short)0, thr_args_base::UShort, -1);

    run_test ((int)0, thr_args_base::Int, -1);
    run_test ((unsigned int)0, thr_args_base::UInt, -1);

    run_test ((long)0, thr_args_base::Long, -1);
    run_test ((unsigned long)0, thr_args_base::ULong, -1);

#ifdef _RWSTD_LONG_LONG

    run_test ((_RWSTD_LONG_LONG)0, thr_args_base::LLong, -1);
    run_test ((unsigned _RWSTD_LONG_LONG)0, thr_args_base::ULLong, -1);

#endif    // _RWSTD_LONG_LONG

    // exercise atomic add
    run_test ((char)0, thr_args_base::Char, +1);
    run_test ((signed char)0, thr_args_base::SChar, +1);
    run_test ((unsigned char)0, thr_args_base::UChar, +1);

    run_test ((short)0, thr_args_base::Short, +1);
    run_test ((unsigned short)0, thr_args_base::UShort, +1);

    run_test ((int)0, thr_args_base::Int, +1);
    run_test ((unsigned int)0, thr_args_base::UInt, +1);

    run_test ((long)0, thr_args_base::Long, +1);
    run_test ((unsigned long)0, thr_args_base::ULong, +1);

#ifdef _RWSTD_LONG_LONG

    run_test ((_RWSTD_LONG_LONG)0, thr_args_base::LLong, +1);
    run_test ((unsigned _RWSTD_LONG_LONG)0, thr_args_base::ULLong, +1);

#endif    // _RWSTD_LONG_LONG


    // exercise both atomic add and subtract
    run_test ((char)0, thr_args_base::Char, 0);
    run_test ((signed char)0, thr_args_base::SChar, 0);
    run_test ((unsigned char)0, thr_args_base::UChar, 0);

    run_test ((short)0, thr_args_base::Short, 0);
    run_test ((unsigned short)0, thr_args_base::UShort, 0);

    run_test ((int)0, thr_args_base::Int, 0);
    run_test ((unsigned int)0, thr_args_base::UInt, 0);

    run_test ((long)0, thr_args_base::Long, 0);
    run_test ((unsigned long)0, thr_args_base::ULong, 0);

#ifdef _RWSTD_LONG_LONG

    run_test ((_RWSTD_LONG_LONG)0, thr_args_base::LLong, 0);
    run_test ((unsigned _RWSTD_LONG_LONG)0, thr_args_base::ULLong, 0);

#endif    // _RWSTD_LONG_LONG

    return 0;
}

/**************************************************************************/

int main (int argc, char *argv[])
{
    return rw_test (argc, argv, __FILE__,
                    0 /* no clause */,
                    0 /* no comment */,
                    run_test,
                    "|-nloops#0 "        // must be non-negative
                    "|-nthreads#0-*" ,   // must be in [0, MAX_THREADS]
                    &rw_opt_nloops,
                    int (MAX_THREADS),
                    &rw_opt_nthreads);
}
