/*
    Copyright 2005-2015 Intel Corporation.  All Rights Reserved.

    This file is part of Threading Building Blocks. Threading Building Blocks is free software;
    you can redistribute it and/or modify it under the terms of the GNU General Public License
    version 2  as  published  by  the  Free Software Foundation.  Threading Building Blocks is
    distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    See  the GNU General Public License for more details.   You should have received a copy of
    the  GNU General Public License along with Threading Building Blocks; if not, write to the
    Free Software Foundation, Inc.,  51 Franklin St,  Fifth Floor,  Boston,  MA 02110-1301 USA

    As a special exception,  you may use this file  as part of a free software library without
    restriction.  Specifically,  if other files instantiate templates  or use macros or inline
    functions from this file, or you compile this file and link it with other files to produce
    an executable,  this file does not by itself cause the resulting executable to be covered
    by the GNU General Public License. This exception does not however invalidate any other
    reasons why the executable file might be covered by the GNU General Public License.
*/

#ifndef __TBB__x86_rtm_rw_mutex_impl_H
#define __TBB__x86_rtm_rw_mutex_impl_H

#ifndef __TBB_spin_rw_mutex_H
#error Do not #include this internal file directly; use public TBB headers instead.
#endif

#if __TBB_TSX_AVAILABLE

#include "../tbb_stddef.h"
#include "../tbb_machine.h"
#include "../tbb_profiling.h"
#include "../spin_rw_mutex.h"

namespace tbb {
namespace interface8 {
namespace internal {

enum RTM_type {
    RTM_not_in_mutex,
    RTM_transacting_reader,
    RTM_transacting_writer,
    RTM_real_reader,
    RTM_real_writer
};

static const unsigned long speculation_granularity = 64;

//! Fast, unfair, spinning speculation-enabled reader-writer lock with backoff and
//  writer-preference
/** @ingroup synchronization */
class x86_rtm_rw_mutex: private spin_rw_mutex {
#if __TBB_USE_X86_RTM_RW_MUTEX || __TBB_GCC_VERSION < 40000
// bug in gcc 3.x.x causes syntax error in spite of the friend declaration below.
// Make the scoped_lock public in that case.
public:
#else
private:
#endif
    friend class interface7::internal::padded_mutex<x86_rtm_rw_mutex,true>;
    class scoped_lock;   // should be private 
    friend class scoped_lock;
private:
    //! @cond INTERNAL

    //! Internal construct unacquired mutex.
    void __TBB_EXPORTED_METHOD internal_construct();

    //! Internal acquire write lock.
    // only_speculate == true if we're doing a try_lock, else false.
    void __TBB_EXPORTED_METHOD internal_acquire_writer(x86_rtm_rw_mutex::scoped_lock&, bool only_speculate=false);

    //! Internal acquire read lock.
    // only_speculate == true if we're doing a try_lock, else false.
    void __TBB_EXPORTED_METHOD internal_acquire_reader(x86_rtm_rw_mutex::scoped_lock&, bool only_speculate=false);

    //! Internal upgrade reader to become a writer.
    bool __TBB_EXPORTED_METHOD internal_upgrade( x86_rtm_rw_mutex::scoped_lock& );

    //! Out of line code for downgrading a writer to a reader.
    bool __TBB_EXPORTED_METHOD internal_downgrade( x86_rtm_rw_mutex::scoped_lock& );

    //! Internal try_acquire write lock.
    bool __TBB_EXPORTED_METHOD internal_try_acquire_writer( x86_rtm_rw_mutex::scoped_lock& );

    //! Internal release lock.
    void __TBB_EXPORTED_METHOD internal_release( x86_rtm_rw_mutex::scoped_lock& );

    static x86_rtm_rw_mutex* internal_get_mutex( const spin_rw_mutex::scoped_lock& lock )
    {
        return static_cast<x86_rtm_rw_mutex*>( lock.internal_get_mutex() );
    }
    static void internal_set_mutex( spin_rw_mutex::scoped_lock& lock, spin_rw_mutex* mtx )
    {
        lock.internal_set_mutex( mtx );
    }
    //! @endcond
public:
    //! Construct unacquired mutex.
    x86_rtm_rw_mutex() {
        w_flag = false;
#if TBB_USE_THREADING_TOOLS
        internal_construct();
#endif
    }

#if TBB_USE_ASSERT
    //! Empty destructor.
    ~x86_rtm_rw_mutex() {}
#endif /* TBB_USE_ASSERT */

    // Mutex traits
    static const bool is_rw_mutex = true;
    static const bool is_recursive_mutex = false;
    static const bool is_fair_mutex = false;

#if __TBB_USE_X86_RTM_RW_MUTEX || __TBB_GCC_VERSION < 40000
#else
    // by default we will not provide the scoped_lock interface.  The user
    // should use the padded version of the mutex.  scoped_lock is used in
    // padded_mutex template.
private:
#endif
    //! The scoped locking pattern
    /** It helps to avoid the common problem of forgetting to release lock.
        It also nicely provides the "node" for queuing locks. */
    // Speculation-enabled scoped lock for spin_rw_mutex
    // The idea is to be able to reuse the acquire/release methods of spin_rw_mutex
    // and its scoped lock wherever possible.  The only way to use a speculative lock is to use
    // a scoped_lock. (because transaction_state must be local)

    class scoped_lock : tbb::internal::no_copy {
        friend class x86_rtm_rw_mutex;
        spin_rw_mutex::scoped_lock my_scoped_lock;

        RTM_type transaction_state;

    public:
        //! Construct lock that has not acquired a mutex.
        /** Equivalent to zero-initialization of *this. */
        scoped_lock() : my_scoped_lock(), transaction_state(RTM_not_in_mutex) {
        }

        //! Acquire lock on given mutex.
        scoped_lock( x86_rtm_rw_mutex& m, bool write = true ) : my_scoped_lock(),
            transaction_state(RTM_not_in_mutex) {
            acquire(m, write);
        }

        //! Release lock (if lock is held).
        ~scoped_lock() {
            if(transaction_state != RTM_not_in_mutex) release();
        }

        //! Acquire lock on given mutex.
        void acquire( x86_rtm_rw_mutex& m, bool write = true ) {
            if( write ) m.internal_acquire_writer(*this);
            else        m.internal_acquire_reader(*this);
        }

        //! Release lock
        void release() {
            x86_rtm_rw_mutex* mutex = x86_rtm_rw_mutex::internal_get_mutex(my_scoped_lock);
            __TBB_ASSERT( mutex, "lock is not acquired" );
            __TBB_ASSERT( transaction_state!=RTM_not_in_mutex, "lock is not acquired" );
            return mutex->internal_release(*this);
        }

        //! Upgrade reader to become a writer.
        /** Returns whether the upgrade happened without releasing and re-acquiring the lock */
        bool upgrade_to_writer() {
            x86_rtm_rw_mutex* mutex = x86_rtm_rw_mutex::internal_get_mutex(my_scoped_lock);
            __TBB_ASSERT( mutex, "lock is not acquired" );
            __TBB_ASSERT( transaction_state==RTM_transacting_reader || transaction_state==RTM_real_reader, "Invalid state for upgrade" );
            return mutex->internal_upgrade(*this);
        }

        //! Downgrade writer to become a reader.
        /** Returns whether the downgrade happened without releasing and re-acquiring the lock */
        bool downgrade_to_reader() {
            x86_rtm_rw_mutex* mutex = x86_rtm_rw_mutex::internal_get_mutex(my_scoped_lock);
            __TBB_ASSERT( mutex, "lock is not acquired" );
            __TBB_ASSERT( transaction_state==RTM_transacting_writer || transaction_state==RTM_real_writer, "Invalid state for downgrade" );
            return mutex->internal_downgrade(*this);
        }

        //! Attempt to acquire mutex.
        /** returns true if successful.  */
        bool try_acquire( x86_rtm_rw_mutex& m, bool write = true ) {
#if TBB_USE_ASSERT
            x86_rtm_rw_mutex* mutex = x86_rtm_rw_mutex::internal_get_mutex(my_scoped_lock);
            __TBB_ASSERT( !mutex, "lock is already acquired" );
#endif
            // have to assign m to our mutex.
            // cannot set the mutex, because try_acquire in spin_rw_mutex depends on it being NULL.
            if(write) return m.internal_try_acquire_writer(*this);
            // speculatively acquire the lock.  If this fails, do try_acquire on the spin_rw_mutex.
            m.internal_acquire_reader(*this, /*only_speculate=*/true);
            if(transaction_state == RTM_transacting_reader) return true;
            if( my_scoped_lock.try_acquire(m, false)) {
                transaction_state = RTM_real_reader;
                return true;
            }
            return false;
        }

        };  // class x86_rtm_rw_mutex::scoped_lock

    // ISO C++0x compatibility methods not provided because we cannot maintain
    // state about whether a thread is in a transaction.

private:
    char pad[speculation_granularity-sizeof(spin_rw_mutex)]; // padding

    // If true, writer holds the spin_rw_mutex.
    tbb::atomic<bool> w_flag;  // want this on a separate cache line

};  // x86_rtm_rw_mutex

}  // namespace internal
}  // namespace interface8
}  // namespace tbb

#endif  /* __TBB_TSX_AVAILABLE */
#endif /* __TBB__x86_rtm_rw_mutex_impl_H */
