/*************************************************************************\
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/

#include <cstdlib>
#include <cstdio>
#include <cassert>
#include <typeinfo>

#include "epicsInterrupt.h"
#include "epicsAtomic.h"
#include "epicsTime.h"
#include "epicsUnitTest.h"
#include "testMain.h"

using std :: size_t;
using namespace epics;
using namespace atomic;

class RefCtr {
public:
    RefCtr ();
    ~RefCtr ();
    void reference ();
    void unreference ();
private:
    size_t m_cnt;
};

class Ownership {
public:
    Ownership ();
    Ownership ( RefCtr & refCtr );
    Ownership ( const Ownership & );
    ~Ownership ();
    Ownership & operator = ( const Ownership & );
private:
    RefCtr * _pRefCtr;
    static RefCtr m_noOwnership;
};

inline RefCtr :: RefCtr ()
{
    epicsAtomicSetSizeT ( & m_cnt, 0 );
}

inline RefCtr :: ~RefCtr ()
{
    unsigned cnt = epicsAtomicGetSizeT ( & m_cnt );
    assert ( cnt == 0u );
}

inline void RefCtr :: reference ()
{
    epicsAtomicIncrSizeT ( & m_cnt );
}

inline void RefCtr :: unreference ()
{
    epicsAtomicDecrSizeT ( & m_cnt );
}

RefCtr Ownership :: m_noOwnership;

inline Ownership :: Ownership () :
    _pRefCtr ( & m_noOwnership )
{
    m_noOwnership.reference ();
}

inline Ownership :: Ownership ( RefCtr & refCtr ) :
    _pRefCtr ( & refCtr )
{
    refCtr.reference ();
}

inline Ownership :: Ownership ( const Ownership & ownership ) :
    _pRefCtr ( ownership._pRefCtr )
{
    _pRefCtr->reference ();
}

inline Ownership :: ~Ownership ()
{
    _pRefCtr->unreference ();
}

inline Ownership & Ownership ::
    operator = ( const Ownership & ownership )
{
    RefCtr * const pOldRefCtr = _pRefCtr;
    _pRefCtr = ownership._pRefCtr;
    _pRefCtr->reference ();
    pOldRefCtr->unreference ();
    return *this;
}

inline Ownership retOwnership ( const Ownership & ownership )
{
    return Ownership ( ownership );
}

inline Ownership recurRetOwner10 ( const Ownership & ownershipIn )
{
    Ownership ownership =
        retOwnership (
            retOwnership (
                retOwnership (
                    retOwnership (
                        retOwnership ( ownershipIn ) ) ) ) );
    return retOwnership (
                retOwnership (
                    retOwnership (
                        retOwnership (
                            retOwnership ( ownership ) ) ) ) );
}

inline Ownership recurRetOwner100 ( const Ownership & ownershipIn )
{
    Ownership ownership =
            recurRetOwner10 (
                recurRetOwner10 (
                    recurRetOwner10 (
                        recurRetOwner10 (
                            recurRetOwner10 ( ownershipIn ) ) ) ) );
    return recurRetOwner10 (
            recurRetOwner10 (
                recurRetOwner10 (
                    recurRetOwner10 (
                        recurRetOwner10 ( ownership ) ) ) ) );
}

inline Ownership recurRetOwner1000 ( const Ownership & ownershipIn )
{
    Ownership ownership =
            recurRetOwner100 (
                recurRetOwner100 (
                    recurRetOwner100 (
                        recurRetOwner100 (
                            recurRetOwner100 ( ownershipIn ) ) ) ) );
    return recurRetOwner100 (
            recurRetOwner100 (
                recurRetOwner100 (
                    recurRetOwner100 (
                        recurRetOwner100 ( ownership ) ) ) ) );
}

inline void passRefOwnership ( const Ownership & ownershipIn, Ownership & ownershipOut )
{
    ownershipOut = ownershipIn;
}

inline void passRefOwnership10 ( const Ownership & ownershipIn, Ownership & ownershipOut )
{
    Ownership ownershipTmp0;
    passRefOwnership ( ownershipIn,  ownershipTmp0 );
    Ownership ownershipTmp1;
    passRefOwnership ( ownershipTmp0, ownershipTmp1 );
    Ownership ownershipTmp2;
    passRefOwnership ( ownershipTmp1, ownershipTmp2 );
    Ownership ownershipTmp3;
    passRefOwnership ( ownershipTmp2, ownershipTmp3 );
    Ownership ownershipTmp4;
    passRefOwnership ( ownershipTmp3, ownershipTmp4 );
    Ownership ownershipTmp5;
    passRefOwnership ( ownershipTmp4, ownershipTmp5 );
    Ownership ownershipTmp6;
    passRefOwnership ( ownershipTmp5, ownershipTmp6 );
    Ownership ownershipTmp7;
    passRefOwnership ( ownershipTmp6, ownershipTmp7 );
    Ownership ownershipTmp8;
    passRefOwnership ( ownershipTmp7, ownershipTmp8 );
    passRefOwnership ( ownershipTmp8, ownershipOut );
}

inline void passRefOwnership100 ( const Ownership & ownershipIn, Ownership & ownershipOut )
{
    Ownership ownershipTmp0;
    passRefOwnership10 ( ownershipIn,  ownershipTmp0 );
    Ownership ownershipTmp1;
    passRefOwnership10 ( ownershipTmp0, ownershipTmp1 );
    Ownership ownershipTmp2;
    passRefOwnership10 ( ownershipTmp1, ownershipTmp2 );
    Ownership ownershipTmp3;
    passRefOwnership10 ( ownershipTmp2, ownershipTmp3 );
    Ownership ownershipTmp4;
    passRefOwnership10 ( ownershipTmp3, ownershipTmp4 );
    Ownership ownershipTmp5;
    passRefOwnership10 ( ownershipTmp4, ownershipTmp5 );
    Ownership ownershipTmp6;
    passRefOwnership10 ( ownershipTmp5, ownershipTmp6 );
    Ownership ownershipTmp7;
    passRefOwnership10 ( ownershipTmp6, ownershipTmp7 );
    Ownership ownershipTmp8;
    passRefOwnership10 ( ownershipTmp7, ownershipTmp8 );
    passRefOwnership10 ( ownershipTmp8, ownershipOut );
}

inline void passRefOwnership1000 ( const Ownership & ownershipIn, Ownership & ownershipOut )
{
    Ownership ownershipTmp0;
    passRefOwnership100 ( ownershipIn,  ownershipTmp0 );
    Ownership ownershipTmp1;
    passRefOwnership100 ( ownershipTmp0, ownershipTmp1 );
    Ownership ownershipTmp2;
    passRefOwnership100 ( ownershipTmp1, ownershipTmp2 );
    Ownership ownershipTmp3;
    passRefOwnership100 ( ownershipTmp2, ownershipTmp3 );
    Ownership ownershipTmp4;
    passRefOwnership100 ( ownershipTmp3, ownershipTmp4 );
    Ownership ownershipTmp5;
    passRefOwnership100 ( ownershipTmp4, ownershipTmp5 );
    Ownership ownershipTmp6;
    passRefOwnership100 ( ownershipTmp5, ownershipTmp6 );
    Ownership ownershipTmp7;
    passRefOwnership100 ( ownershipTmp6, ownershipTmp7 );
    Ownership ownershipTmp8;
    passRefOwnership100 ( ownershipTmp7, ownershipTmp8 );
    passRefOwnership100 ( ownershipTmp8, ownershipOut );
}

time_t extTime = 0;

template < class T >
class OrdinaryIncr {
public:
    OrdinaryIncr () : m_target ( 0 ) {}
    void run ();
    void diagnostic ( double delay );
private:
    T m_target;
};

// tests the time it takes to perform a call to an external
// function and also increment an integer word. The
// epicsInterruptIsInterruptContext function is an
// out-of-line function implemented in a shareable library
// so hopefully it wont be optimized away.
template < class T >
inline void OrdinaryIncr < T > :: run ()
{
    m_target += epicsInterruptIsInterruptContext ();
    m_target += epicsInterruptIsInterruptContext ();
    m_target += epicsInterruptIsInterruptContext ();
    m_target += epicsInterruptIsInterruptContext ();
    m_target += epicsInterruptIsInterruptContext ();
    m_target += epicsInterruptIsInterruptContext ();
    m_target += epicsInterruptIsInterruptContext ();
    m_target += epicsInterruptIsInterruptContext ();
    m_target += epicsInterruptIsInterruptContext ();
    m_target += epicsInterruptIsInterruptContext ();
}

template < class T >
void OrdinaryIncr < T > :: diagnostic ( double delay )
{
    delay /= 10.0;
    delay *= 1e6;
    const char * const pName = typeid ( T ) . name ();
    testDiag ( "raw incr of \"%s\" and a NOOP function call takes %f microseconds",
                         pName, delay );
}

template < class T >
class AtomicIncr {
public:
    AtomicIncr () : m_target ( 0 ) {}
    void run ();
    void diagnostic ( double delay );
private:
    T m_target;
};

template < class T >
inline void AtomicIncr < T > :: run ()
{
    increment ( m_target );
    increment ( m_target );
    increment ( m_target );
    increment ( m_target );
    increment ( m_target );
    increment ( m_target );
    increment ( m_target );
    increment ( m_target );
    increment ( m_target );
    increment ( m_target );
}

template < class T >
void AtomicIncr < T > :: diagnostic ( double delay )
{
    delay /= 10.0;
    delay *= 1e6;
    const char * const pName = typeid ( T ) . name ();
    testDiag ( "epicsAtomicIncr \"%s\" takes %f microseconds",
                        pName, delay );
}

template < class T > T trueValue ();
template < class T > T falseValue ();

// int
template <>
inline int trueValue < int > () { return 1; }

template <>
inline int falseValue < int > () { return 0; }

// size_t
template <>
inline size_t trueValue < size_t > () { return 1u; }

template <>
inline size_t falseValue < size_t > () { return 0u; }

// EpicsAtomicPtrT
template <>
inline EpicsAtomicPtrT trueValue < EpicsAtomicPtrT > ()
{ static char c; return & c; }

template <>
inline EpicsAtomicPtrT falseValue < EpicsAtomicPtrT > ()
{ return 0u; }

template < class T >
class AtomicCmpAndSwap {
public:
    AtomicCmpAndSwap () : m_target ( 0 ) {}
    void run ();
    void diagnostic ( double delay );
private:
    T m_target;
};

template < class T >
inline void AtomicCmpAndSwap < T > :: run ()
{
    compareAndSwap ( m_target, falseValue < T > (), trueValue < T > () );
    compareAndSwap ( m_target, falseValue < T > (), trueValue < T > () );
    compareAndSwap ( m_target, falseValue < T > (), trueValue < T > () );
    compareAndSwap ( m_target, falseValue < T > (), trueValue < T > () );
    compareAndSwap ( m_target, falseValue < T > (), trueValue < T > () );
    compareAndSwap ( m_target, falseValue < T > (), trueValue < T > () );
    compareAndSwap ( m_target, falseValue < T > (), trueValue < T > () );
    compareAndSwap ( m_target, falseValue < T > (), trueValue < T > () );
    compareAndSwap ( m_target, falseValue < T > (), trueValue < T > () );
    compareAndSwap ( m_target, falseValue < T > (), trueValue < T > () );
}

template < class T >
void AtomicCmpAndSwap < T > :: diagnostic ( double delay )
{
    delay /= 10.0;
    delay *= 1e6;
    const char * const pName = typeid ( T ) . name ();
    testDiag ( "epicsAtomicCmpAndSwap of \"%s\" takes %f microseconds",
                         pName, delay );
}

template < class T >
class AtomicSet {
public:
    AtomicSet () : m_target ( 0 ) {}
    void run ();
    void diagnostic ( double delay );
private:
    T m_target;
};

template < class T >
inline void AtomicSet < T > :: run ()
{
    set ( m_target, 0 );
    set ( m_target, 0 );
    set ( m_target, 0 );
    set ( m_target, 0 );
    set ( m_target, 0 );
    set ( m_target, 0 );
    set ( m_target, 0 );
    set ( m_target, 0 );
    set ( m_target, 0 );
    set ( m_target, 0 );
}

template < class T >
void AtomicSet < T > :: diagnostic ( double delay )
{
    delay /= 10.0;
    delay *= 1e6;
    const char * const pName = typeid ( T ) . name ();
    testDiag ( "epicsAtomicSet of \"%s\" takes %f microseconds",
            pName, delay );
}

static const unsigned N = 10000;

void recursiveOwnershipRetPerformance ()
{
    RefCtr refCtr;
    epicsTime begin = epicsTime::getMonotonic ();
    for ( size_t i = 0; i < N; i++ ) {
        Ownership ownership ( refCtr );
        recurRetOwner1000 ( ownership );
    }
    double delay = epicsTime::getMonotonic () -  begin;
    delay /= N * 1000u; // convert to delay per call
    delay *= 1e6; // convert to micro seconds
    testDiag ( "retOwnership() takes %f microseconds", delay );
}

void ownershipPassRefPerformance ()
{
    RefCtr refCtr;
    epicsTime begin = epicsTime::getMonotonic ();
    for ( size_t i = 0; i < N; i++ ) {
        Ownership ownershipSrc ( refCtr );
        Ownership ownershipDest;
        passRefOwnership1000 ( ownershipSrc, ownershipDest );
    }
    double delay = epicsTime::getMonotonic () -  begin;
    delay /= N * 1000u; // convert to delay per call
    delay *= 1e6; // convert to micro seconds
    testDiag ( "passRefOwnership() takes %f microseconds", delay );
}

template < class T >
class Ten
{
public:
    void run ();
    void diagnostic ( double delay );
    typedef Ten < Ten < T > > Hundred;
    typedef Ten < Hundred > Thousand;
private:
    T m_target;
};

template < class T >
inline void Ten < T > :: run ()
{
    m_target.run ();
    m_target.run ();
    m_target.run ();
    m_target.run ();
    m_target.run ();
    m_target.run ();
    m_target.run ();
    m_target.run ();
    m_target.run ();
    m_target.run ();
}

template < class T >
void Ten < T > :: diagnostic ( double delay )
{
    m_target.diagnostic ( delay / 10.0 );
}

template < class T >
void measurePerformance ()
{
    epicsTime begin = epicsTime::getMonotonic ();
    T target;
    for ( size_t i = 0; i < N; i++ ) {
        target.run ();
        target.run ();
        target.run ();
        target.run ();
        target.run ();
        target.run ();
        target.run ();
        target.run ();
        target.run ();
        target.run ();
    }
    double delay = epicsTime::getMonotonic () -  begin;
    delay /= ( N * 10u ); // convert to delay per call
    target.diagnostic ( delay );
}

template < class T >
void measure ()
{
    measurePerformance < typename Ten < T > :: Hundred > ();
}


// template instances, needed for vxWorks 5.5.2

#ifdef _MSC_VER
#   pragma warning ( push )
#   pragma warning ( disable:4660 )
#endif

template class AtomicSet < int >;
template class AtomicSet < size_t >;
template class AtomicSet < EpicsAtomicPtrT >;
template class OrdinaryIncr < int >;
template class OrdinaryIncr < size_t >;
template class AtomicIncr < int >;
template class AtomicIncr < size_t >;
template class AtomicCmpAndSwap < int >;
template class AtomicCmpAndSwap < size_t >;
template class AtomicCmpAndSwap < EpicsAtomicPtrT >;

template class Ten<AtomicSet < int > >;
template class Ten<AtomicSet < size_t > >;
template class Ten<AtomicSet < EpicsAtomicPtrT > >;
template class Ten<OrdinaryIncr < int > >;
template class Ten<OrdinaryIncr < size_t > >;
template class Ten<AtomicIncr < int > >;
template class Ten<AtomicIncr < size_t > >;
template class Ten<AtomicCmpAndSwap < int > >;
template class Ten<AtomicCmpAndSwap < size_t > >;
template class Ten<AtomicCmpAndSwap < EpicsAtomicPtrT > >;

template class Ten<Ten<AtomicSet < int > > >;
template class Ten<Ten<AtomicSet < size_t > > >;
template class Ten<Ten<AtomicSet < EpicsAtomicPtrT > > >;
template class Ten<Ten<OrdinaryIncr < int > > >;
template class Ten<Ten<OrdinaryIncr < size_t > > >;
template class Ten<Ten<AtomicIncr < int > > >;
template class Ten<Ten<AtomicIncr < size_t > > >;
template class Ten<Ten<AtomicCmpAndSwap < int > > >;
template class Ten<Ten<AtomicCmpAndSwap < size_t > > >;
template class Ten<Ten<AtomicCmpAndSwap < EpicsAtomicPtrT > > >;

template void measurePerformance < Ten < Ten < AtomicSet < int > > > >(void);
template void measurePerformance < Ten < Ten < AtomicSet < size_t > > > >(void);
template void measurePerformance < Ten < Ten < AtomicSet < EpicsAtomicPtrT > > > >(void);
template void measurePerformance < Ten < Ten < OrdinaryIncr < int > > > >(void);
template void measurePerformance < Ten < Ten < OrdinaryIncr < size_t > > > >(void);
template void measurePerformance < Ten < Ten < AtomicIncr < int > > > >(void);
template void measurePerformance < Ten < Ten < AtomicIncr < size_t > > > >(void);
template void measurePerformance < Ten < Ten < AtomicCmpAndSwap < int > > > >(void);
template void measurePerformance < Ten < Ten < AtomicCmpAndSwap < size_t > > > >(void);
template void measurePerformance < Ten < Ten < AtomicCmpAndSwap < EpicsAtomicPtrT > > > >(void);

template void measure < AtomicSet < int > > (void);
template void measure < AtomicSet < size_t > > (void);
template void measure < AtomicSet < EpicsAtomicPtrT > > (void);
template void measure < OrdinaryIncr < int > > (void);
template void measure < OrdinaryIncr < size_t > > (void);
template void measure < AtomicIncr < int > > (void);
template void measure < AtomicIncr < size_t > > (void);
template void measure < AtomicCmpAndSwap < int > > (void);
template void measure < AtomicCmpAndSwap < size_t > > (void);
template void measure < AtomicCmpAndSwap < EpicsAtomicPtrT > > (void);

#ifdef _MSC_VER
#   pragma warning ( pop )
#endif


MAIN ( epicsAtomicPerform )
{
    testPlan ( 0 );
    //
    // The tests running here are measuring fast
    // functions so they tend to be impacted
    // by where the cache lines are wrt to the
    // virtual pages perhaps
    //
    measure < AtomicSet < int > > ();
    measure < AtomicSet < size_t > > ();
    measure < AtomicSet < void * > > ();
    measure < OrdinaryIncr < int > > ();
    measure < OrdinaryIncr < size_t > > ();
    measure < AtomicIncr < int > > ();
    measure < AtomicIncr < size_t > > ();
    measure < AtomicCmpAndSwap < int > > ();
    measure < AtomicCmpAndSwap < size_t > > ();
    measure < AtomicCmpAndSwap < void * > > ();
    recursiveOwnershipRetPerformance ();
    ownershipPassRefPerformance ();
    return testDone();
}
