// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#ifndef RUNTIME_VM_LOCKERS_H_
#define RUNTIME_VM_LOCKERS_H_

#include "platform/assert.h"
#include "platform/growable_array.h"
#include "vm/allocation.h"
#include "vm/globals.h"
#include "vm/os_thread.h"
#include "vm/thread.h"

namespace dart {

const bool kNoSafepointScope = true;
const bool kDontAssertNoSafepointScope = false;

/*
 * Normal mutex locker :
 * This locker abstraction should only be used when the enclosing code can
 * not trigger a safepoint. In debug mode this class increments the
 * no_safepoint_scope_depth variable for the current thread when the lock is
 * taken and decrements it when the lock is released. NOTE: please do not use
 * the passed in mutex object independent of the locker class, For example the
 * code below will not assert correctly:
 *    {
 *      MutexLocker ml(m);
 *      ....
 *      m->Exit();
 *      ....
 *      m->Enter();
 *      ...
 *    }
 * Always use the locker object even when the lock needs to be released
 * temporarily, e.g:
 *    {
 *      MutexLocker ml(m);
 *      ....
 *      ml.Exit();
 *      ....
 *      ml.Enter();
 *      ...
 *    }
 */
class MutexLocker : public ValueObject {
 public:
  explicit MutexLocker(Mutex* mutex)
      :
#if defined(DEBUG)
        no_safepoint_scope_(true),
#endif
        mutex_(mutex) {
    ASSERT(mutex != nullptr);
#if defined(DEBUG)
    Thread* thread = Thread::Current();
    if ((thread != nullptr) &&
        (thread->execution_state() != Thread::kThreadInNative)) {
      thread->IncrementNoSafepointScopeDepth();
    } else {
      no_safepoint_scope_ = false;
    }
#endif
    mutex_->Lock();
  }

  virtual ~MutexLocker() {
    mutex_->Unlock();
#if defined(DEBUG)
    if (no_safepoint_scope_) {
      Thread::Current()->DecrementNoSafepointScopeDepth();
    }
#endif
  }

  void Lock() const {
#if defined(DEBUG)
    if (no_safepoint_scope_) {
      Thread::Current()->IncrementNoSafepointScopeDepth();
    }
#endif
    mutex_->Lock();
  }
  void Unlock() const {
    mutex_->Unlock();
#if defined(DEBUG)
    if (no_safepoint_scope_) {
      Thread::Current()->DecrementNoSafepointScopeDepth();
    }
#endif
  }

 private:
  DEBUG_ONLY(bool no_safepoint_scope_;)
  Mutex* const mutex_;

  DISALLOW_COPY_AND_ASSIGN(MutexLocker);
};

// Unlocks the given mutex during the scope of the object.
class MutexUnlocker : public ValueObject {
 public:
  explicit MutexUnlocker(MutexLocker* mutex_locker)
      : mutex_locker_(mutex_locker) {
    mutex_locker_->Unlock();
  }

  virtual ~MutexUnlocker() { mutex_locker_->Lock(); }

 private:
  MutexLocker* const mutex_locker_;

  DISALLOW_COPY_AND_ASSIGN(MutexUnlocker);
};

/*
 * Normal monitor locker :
 * This locker abstraction should only be used when the enclosed code can
 * not trigger a safepoint. In debug mode this class increments the
 * no_safepoint_scope_depth variable for the current thread when the lock is
 * taken and decrements it when the lock is released. NOTE: please do not use
 * the passed in mutex object independent of the locker class, For example the
 * code below will not assert correctly:
 *    {
 *      MonitorLocker ml(m);
 *      ....
 *      m->Exit();
 *      ....
 *      m->Enter();
 *      ...
 *    }
 * Always use the locker object even when the lock needs to be released
 * temporarily, e.g:
 *    {
 *      MonitorLocker ml(m);
 *      ....
 *      ml.Exit();
 *      ....
 *      ml.Enter();
 *      ...
 *    }
 */
class MonitorLocker : public ValueObject {
 public:
  explicit MonitorLocker(Monitor* monitor, bool no_safepoint_scope = true)
      : monitor_(monitor), no_safepoint_scope_(no_safepoint_scope) {
    ASSERT(monitor != nullptr);
#if defined(DEBUG)
    if (no_safepoint_scope_) {
      Thread* thread = Thread::Current();
      if (thread != nullptr) {
        thread->IncrementNoSafepointScopeDepth();
      } else {
        no_safepoint_scope_ = false;
      }
    }
#endif
    monitor_->Enter();
  }

  virtual ~MonitorLocker() {
    monitor_->Exit();
#if defined(DEBUG)
    if (no_safepoint_scope_) {
      Thread::Current()->DecrementNoSafepointScopeDepth();
    }
#endif
  }

  void Enter() const {
#if defined(DEBUG)
    if (no_safepoint_scope_) {
      Thread::Current()->IncrementNoSafepointScopeDepth();
    }
#endif
    monitor_->Enter();
  }
  void Exit() const {
    monitor_->Exit();
#if defined(DEBUG)
    if (no_safepoint_scope_) {
      Thread::Current()->DecrementNoSafepointScopeDepth();
    }
#endif
  }

  Monitor::WaitResult Wait(int64_t millis = Monitor::kNoTimeout) {
    return monitor_->Wait(millis);
  }

  Monitor::WaitResult WaitWithSafepointCheck(
      Thread* thread,
      int64_t millis = Monitor::kNoTimeout);

  Monitor::WaitResult WaitMicros(int64_t micros = Monitor::kNoTimeout) {
    return monitor_->WaitMicros(micros);
  }

  void Notify() { monitor_->Notify(); }

  void NotifyAll() { monitor_->NotifyAll(); }

 private:
  Monitor* const monitor_;
  bool no_safepoint_scope_;

  DISALLOW_COPY_AND_ASSIGN(MonitorLocker);
};

// Leaves the given monitor during the scope of the object.
class MonitorLeaveScope : public ValueObject {
 public:
  explicit MonitorLeaveScope(MonitorLocker* monitor)
      : monitor_locker_(monitor) {
    monitor_locker_->Exit();
  }

  virtual ~MonitorLeaveScope() { monitor_locker_->Enter(); }

 private:
  MonitorLocker* const monitor_locker_;

  DISALLOW_COPY_AND_ASSIGN(MonitorLeaveScope);
};

/*
 * Safepoint mutex locker :
 * This locker abstraction should be used when the enclosing code could
 * potentially trigger a safepoint.
 * This locker ensures that other threads that try to acquire the same lock
 * will be marked as being at a safepoint if they get blocked trying to
 * acquire the lock.
 * NOTE: please do not use the passed in mutex object independent of the locker
 * class, For example the code below will not work correctly:
 *    {
 *      SafepointMutexLocker ml(m);
 *      ....
 *      m->Exit();
 *      ....
 *      m->Enter();
 *      ...
 *    }
 */
class SafepointMutexLocker : public StackResource {
 public:
  explicit SafepointMutexLocker(Mutex* mutex)
      : SafepointMutexLocker(ThreadState::Current(), mutex) {}
  SafepointMutexLocker(ThreadState* thread, Mutex* mutex);
  virtual ~SafepointMutexLocker() { mutex_->Unlock(); }

 private:
  Mutex* const mutex_;

  DISALLOW_COPY_AND_ASSIGN(SafepointMutexLocker);
};

/*
 * Safepoint monitor locker :
 * This locker abstraction should be used when the enclosing code could
 * potentially trigger a safepoint.
 * This locker ensures that other threads that try to acquire the same lock
 * will be marked as being at a safepoint if they get blocked trying to
 * acquire the lock.
 * NOTE: please do not use the passed in monitor object independent of the
 * locker class, For example the code below will not work correctly:
 *    {
 *      SafepointMonitorLocker ml(m);
 *      ....
 *      m->Exit();
 *      ....
 *      m->Enter();
 *      ...
 *    }
 */
template <typename M>
class SafepointLocker : public ValueObject {
 public:
  explicit SafepointLocker(M* monitor) : monitor_(monitor) { AcquireLock(); }
  virtual ~SafepointLocker() { ReleaseLock(); }

  typename M::WaitResult Wait(int64_t millis = Monitor::kNoTimeout);

  void NotifyAll() { monitor_->NotifyAll(); }
  void Notify() { monitor_->Notify(); }

 private:
  friend class SafepointMonitorUnlockScope;

  void AcquireLock();

  void ReleaseLock() { monitor_->Exit(); }

  M* const monitor_;

  DISALLOW_COPY_AND_ASSIGN(SafepointLocker);
};

typedef SafepointLocker<Monitor> SafepointMonitorLocker;

class SafepointMonitorUnlockScope : public ValueObject {
 public:
  explicit SafepointMonitorUnlockScope(SafepointMonitorLocker* locker)
      : locker_(locker) {
    locker_->ReleaseLock();
  }
  ~SafepointMonitorUnlockScope() { locker_->AcquireLock(); }

 private:
  SafepointMonitorLocker* locker_;
};

class RwLock {
 public:
  RwLock() {}
  ~RwLock() {}

  bool IsCurrentThreadWriter() {
    return writer_id_ == OSThread::GetCurrentThreadId();
  }

 private:
  friend class ReadRwLocker;
  friend class WriteRwLocker;

  void EnterRead() {
    MonitorLocker ml(&monitor_);
    while (state_ == -1) {
      ml.Wait();
    }
    ++state_;
  }
  void LeaveRead() {
    MonitorLocker ml(&monitor_);
    ASSERT(state_ > 0);
    if (--state_ == 0) {
      ml.NotifyAll();
    }
  }

  void EnterWrite() {
    MonitorLocker ml(&monitor_);
    while (state_ != 0) {
      ml.Wait();
    }
    state_ = -1;
    writer_id_ = OSThread::GetCurrentThreadId();
  }

  void LeaveWrite() {
    MonitorLocker ml(&monitor_);
    ASSERT(state_ == -1);
    state_ = 0;
    writer_id_ = OSThread::kInvalidThreadId;
    ml.NotifyAll();
  }

  Monitor monitor_;
  // [state_] > 0  : The lock is held by multiple readers.
  // [state_] == 0 : The lock is free (no readers/writers).
  // [state_] == -1: The lock is held by a single writer.
  intptr_t state_ = 0;
  ThreadId writer_id_ = OSThread::kInvalidThreadId;
};

class SafepointRwLock {
 public:
  explicit SafepointRwLock(
      SafepointLevel expected_safepoint_level = SafepointLevel::kGC)
      : expected_safepoint_level_(expected_safepoint_level) {}
  ~SafepointRwLock() {}

  DEBUG_ONLY(bool IsCurrentThreadReader());

  bool IsCurrentThreadWriter() {
    return writer_id_ == OSThread::GetCurrentThreadId();
  }

 private:
  friend class SafepointReadRwLocker;
  friend class SafepointWriteRwLocker;
  friend class ReloadableStealableWriteRwLocker;

  // returns [true] if read lock was acquired,
  // returns [false] if the thread didn't have to acquire read lock due
  // to the thread already holding write lock
  bool EnterRead();
  bool TryEnterRead(bool can_block, bool* acquired_read_lock);
  void LeaveRead();

  void EnterWrite();
  void EnterWriteReloadableStealable();
  bool TryEnterWrite(bool can_block);
  void LeaveWrite();

  const SafepointLevel expected_safepoint_level_;

  // We maintain an invariant that this monitor is never locked for long periods
  // of time: Any thread that acquired this monitor must always be able to do
  // it's work and release it (or wait on the monitor which will also release
  // it).
  //
  // In particular we must ensure the monitor is never held and then a potential
  // safepoint operation is triggered, since another thread could try to acquire
  // the lock and it would deadlock.
  Monitor monitor_;

  // [state_] > 0  : The lock is held by multiple readers.
  // [state_] == 0 : The lock is free (no readers/writers).
  // [state_] < 0  : The lock is held by a single writer (possibly nested).
  intptr_t state_ = 0;

  DEBUG_ONLY(MallocGrowableArray<ThreadId> readers_ids_);
  ThreadId writer_id_ = OSThread::kInvalidThreadId;
};

/*
 * Locks a given [RwLock] for reading purposes.
 *
 * It will block while the lock is held by a writer.
 *
 * If this locker is long'jmped over (e.g. on a background compiler thread) the
 * lock will be freed.
 *
 * NOTE: If the locking operation blocks (due to a writer) it will not check
 * for a pending safepoint operation.
 */
class ReadRwLocker : public StackResource {
 public:
  ReadRwLocker(ThreadState* thread_state, RwLock* rw_lock)
      : StackResource(thread_state), rw_lock_(rw_lock) {
    rw_lock_->EnterRead();
  }
  ~ReadRwLocker() { rw_lock_->LeaveRead(); }

 private:
  RwLock* rw_lock_;
};

/*
 * In addition to what [ReadRwLocker] does, this implementation also gets into a
 * safepoint if necessary.
 */
class SafepointReadRwLocker : public StackResource {
 public:
  SafepointReadRwLocker(ThreadState* thread_state, SafepointRwLock* rw_lock)
      : StackResource(thread_state), rw_lock_(rw_lock) {
    ASSERT(rw_lock_ != nullptr);
    if (!rw_lock_->EnterRead()) {
      // if lock didn't have to be acquired, it doesn't have to be released.
      rw_lock_ = nullptr;
    }
  }
  ~SafepointReadRwLocker() {
    if (rw_lock_ != nullptr) {
      rw_lock_->LeaveRead();
    }
  }

 private:
  SafepointRwLock* rw_lock_;
};

/*
 * Locks a given [RwLock] for writing purposes.
 *
 * It will block while the lock is held by one or more readers.
 *
 * If this locker is long'jmped over (e.g. on a background compiler thread) the
 * lock will be freed.
 *
 * NOTE: If the locking operation blocks (due to a writer) it will not check
 * for a pending safepoint operation.
 */
class WriteRwLocker : public StackResource {
 public:
  WriteRwLocker(ThreadState* thread_state, RwLock* rw_lock)
      : StackResource(thread_state), rw_lock_(rw_lock) {
    rw_lock_->EnterWrite();
  }

  ~WriteRwLocker() { rw_lock_->LeaveWrite(); }

 private:
  RwLock* rw_lock_;
};

/*
 * In addition to what [WriteRwLocker] does, this implementation also gets into a
 * safepoint if necessary.
 */
class SafepointWriteRwLocker : public StackResource {
 public:
  SafepointWriteRwLocker(ThreadState* thread_state, SafepointRwLock* rw_lock)
      : StackResource(thread_state), rw_lock_(rw_lock) {
    rw_lock_->EnterWrite();
  }

  ~SafepointWriteRwLocker() { rw_lock_->LeaveWrite(); }

 private:
  SafepointRwLock* rw_lock_;
};

/*
 * Allows reload safepoint and active mutator slot stealing while blocked.
 */
class ReloadableStealableWriteRwLocker : public StackResource {
 public:
  ReloadableStealableWriteRwLocker(ThreadState* thread_state,
                                   SafepointRwLock* rw_lock)
      : StackResource(thread_state), rw_lock_(rw_lock) {
    rw_lock_->EnterWriteReloadableStealable();
  }

  ~ReloadableStealableWriteRwLocker() { rw_lock_->LeaveWrite(); }

 private:
  SafepointRwLock* rw_lock_;
};

}  // namespace dart

#endif  // RUNTIME_VM_LOCKERS_H_
