// Copyright (c) 2016, 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.

#include "vm/lockers.h"
#include "platform/assert.h"
#include "vm/heap/safepoint.h"
#include "vm/isolate.h"

namespace dart {

Monitor::WaitResult MonitorLocker::WaitWithSafepointCheck(Thread* thread,
                                                          int64_t millis) {
  ASSERT(thread == Thread::Current());
  ASSERT(thread->execution_state() == Thread::kThreadInVM);
#if defined(DEBUG)
  if (no_safepoint_scope_) {
    thread->DecrementNoSafepointScopeDepth();
  }
#endif
  thread->set_execution_state(Thread::kThreadInBlockedState);
  thread->EnterSafepoint();
  Monitor::WaitResult result = monitor_->Wait(millis);
  // First try a fast update of the thread state to indicate it is not at a
  // safepoint anymore.
  if (!thread->TryExitSafepoint()) {
    // Fast update failed which means we could potentially be in the middle
    // of a safepoint operation and need to block for it.
    monitor_->Exit();
    thread->ExitSafepointUsingLock();
    monitor_->Enter();
  }
  thread->set_execution_state(Thread::kThreadInVM);
#if defined(DEBUG)
  if (no_safepoint_scope_) {
    thread->IncrementNoSafepointScopeDepth();
  }
#endif
  return result;
}

SafepointMutexLocker::SafepointMutexLocker(ThreadState* thread, Mutex* mutex)
    : StackResource(thread), mutex_(mutex) {
  ASSERT(mutex != nullptr);
  if (!mutex_->TryLock()) {
    // We did not get the lock and could potentially block, so transition
    // accordingly.
    Thread* thread = Thread::Current();
    if (thread != nullptr) {
      TransitionVMToBlocked transition(thread);
      mutex->Lock();
    } else {
      mutex->Lock();
    }
  }
}

template <typename M>
void SafepointLocker<M>::AcquireLock() {
  ASSERT(monitor_ != nullptr);
  if (!monitor_->TryEnter()) {
    // We did not get the lock and could potentially block, so transition
    // accordingly.
    Thread* thread = Thread::Current();
    if (thread != nullptr) {
      TransitionVMToBlocked transition(thread);
      monitor_->Enter();
    } else {
      monitor_->Enter();
    }
  }
}

template <typename M>
typename M::WaitResult SafepointLocker<M>::Wait(int64_t millis) {
  Thread* thread = Thread::Current();
  if (thread != nullptr) {
    typename M::WaitResult result;
    {
      TransitionVMToBlocked transition(thread);
      result = monitor_->Wait(millis);
    }
    return result;
  } else {
    return monitor_->Wait(millis);
  }
}

template class SafepointLocker<ReentrantMonitor>;
template class SafepointLocker<Monitor>;

#if defined(DEBUG)
bool SafepointRwLock::IsCurrentThreadReader() {
  ThreadId id = OSThread::GetCurrentThreadId();
  if (IsCurrentThreadWriter()) {
    return true;
  }
  MonitorLocker ml(&monitor_);
  for (intptr_t i = readers_ids_.length() - 1; i >= 0; i--) {
    if (readers_ids_.At(i) == id) {
      return true;
    }
  }
  return false;
}
#endif  // defined(DEBUG)

bool SafepointRwLock::EnterRead() {
  // No need to safepoint if the current thread is not attached.
  auto thread = Thread::Current();
  // Attempt to acquire a lock while owning a safepoint could lead to a deadlock
  // (some other thread might be forced to a safepoint while holding this lock).
  //
  // Though if the lock was already acquired by this thread before entering a
  // safepoint, we do allow the nested acquire (which is a NOP).
  ASSERT(thread == nullptr || thread->execution_state() == Thread::kThreadInVM);
  DEBUG_ASSERT(thread == nullptr || thread->CanAcquireSafepointLocks() ||
               IsCurrentThreadReader());

  const bool can_block_without_safepoint = thread == nullptr;

  bool acquired_read_lock = false;
  if (!TryEnterRead(can_block_without_safepoint, &acquired_read_lock)) {
    // Important: must never hold monitor_ when blocking for safepoint.
    TransitionVMToBlocked transition(thread);
    const bool ok = TryEnterRead(/*can_block=*/true, &acquired_read_lock);
    RELEASE_ASSERT(ok);
    RELEASE_ASSERT(acquired_read_lock);
  }
  return acquired_read_lock;
}

bool SafepointRwLock::TryEnterRead(bool can_block, bool* acquired_read_lock) {
  MonitorLocker ml(&monitor_);
  if (IsCurrentThreadWriter()) {
    *acquired_read_lock = false;
    return true;
  }
  if (can_block) {
    while (state_ < 0) {
      ml.Wait();
    }
  }
  if (state_ >= 0) {
    ++state_;
    DEBUG_ONLY(readers_ids_.Add(OSThread::GetCurrentThreadId()));
    *acquired_read_lock = true;
    return true;
  }
  return false;
}

void SafepointRwLock::LeaveRead() {
  MonitorLocker ml(&monitor_);
  ASSERT(state_ > 0);
#if defined(DEBUG)
  {
    intptr_t i = readers_ids_.length() - 1;
    ThreadId id = OSThread::GetCurrentThreadId();
    while (i >= 0) {
      if (readers_ids_.At(i) == id) {
        readers_ids_.RemoveAt(i);
        break;
      }
      i--;
    }
    ASSERT(i >= 0);
  }
#endif
  if (--state_ == 0) {
    ml.NotifyAll();
  }
}

void SafepointRwLock::EnterWrite() {
  // No need to safepoint if the current thread is not attached.
  auto thread = Thread::Current();
  // Attempt to acquire a lock while owning a safepoint could lead to a deadlock
  // (some other thread might be forced to a safepoint while holding this lock).
  //
  // Though if the lock was already acquired by this thread before entering a
  // safepoint, we do allow the nested acquire (which is a NOP).
  ASSERT(thread == nullptr || thread->execution_state() == Thread::kThreadInVM);
  DEBUG_ASSERT(thread == nullptr || thread->CanAcquireSafepointLocks() ||
               IsCurrentThreadWriter());

  const bool can_block_without_safepoint = thread == nullptr;

  RELEASE_ASSERT(can_block_without_safepoint ||
                 thread->current_safepoint_level() >=
                     expected_safepoint_level_);

  if (!TryEnterWrite(can_block_without_safepoint)) {
    // Important: must never hold monitor_ when blocking for safepoint.
    TransitionVMToBlocked transition(thread);
    const bool ok = TryEnterWrite(/*can_block=*/true);
    RELEASE_ASSERT(ok);
  }
}

void SafepointRwLock::EnterWriteReloadableStealable() {
  // No need to safepoint if the current thread is not attached.
  auto thread = Thread::Current();
  // Attempt to acquire a lock while owning a safepoint could lead to a deadlock
  // (some other thread might be forced to a safepoint while holding this lock).
  //
  // Though if the lock was already acquired by this thread before entering a
  // safepoint, we do allow the nested acquire (which is a NOP).
  ASSERT(thread == nullptr || thread->execution_state() == Thread::kThreadInVM);
  DEBUG_ASSERT(thread == nullptr || thread->CanAcquireSafepointLocks() ||
               IsCurrentThreadWriter());

  const bool can_block_without_safepoint = thread == nullptr;

  RELEASE_ASSERT(can_block_without_safepoint ||
                 thread->current_safepoint_level() >=
                     expected_safepoint_level_);

  if (!TryEnterWrite(can_block_without_safepoint)) {
    // Important: must never hold monitor_ when blocking for safepoint.
    TransitionVMToBlockedReloadableStealable transition(thread);
    const bool ok = TryEnterWrite(/*can_block=*/true);
    RELEASE_ASSERT(ok);
  }
}

bool SafepointRwLock::TryEnterWrite(bool can_block) {
  MonitorLocker ml(&monitor_);
  if (IsCurrentThreadWriter()) {
    state_--;
    return true;
  }
  if (can_block) {
    while (state_ != 0) {
      ml.Wait();
    }
  }
  if (state_ == 0) {
    writer_id_ = OSThread::GetCurrentThreadId();
    state_ = -1;
    return true;
  }
  return false;
}

void SafepointRwLock::LeaveWrite() {
  MonitorLocker ml(&monitor_);
  ASSERT(state_ < 0);
  if (++state_ < 0) {
    return;
  }
  writer_id_ = OSThread::kInvalidThreadId;
  ml.NotifyAll();
}

}  // namespace dart
