// Copyright (c) 2015, 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/os_thread.h"

#include "platform/address_sanitizer.h"
#include "platform/atomic.h"
#include "platform/memory_sanitizer.h"
#include "platform/thread_sanitizer.h"
#include "vm/lockers.h"
#include "vm/log.h"
#include "vm/profiler.h"
#include "vm/thread_interrupter.h"
#include "vm/timeline.h"

namespace dart {

OSThread* OSThread::thread_list_head_ = nullptr;
Mutex* OSThread::thread_list_lock_ = nullptr;
bool OSThread::creation_enabled_ = false;
thread_local OSThreadPtr OSThread::current_os_thread_ = {};

#if defined(SUPPORT_TIMELINE)
inline void UpdateTimelineTrackMetadata(const OSThread& thread) {
  RecorderSynchronizationLockScope ls;
  if (!ls.IsActive()) {
    return;
  }
  TimelineEventRecorder* recorder = Timeline::recorder();
  if (recorder != nullptr) {
    recorder->AddTrackMetadataBasedOnThread(
        OS::ProcessId(), OSThread::ThreadIdToIntPtr(thread.trace_id()),
        thread.name());
  }
}
#endif  // defined(SUPPORT_TIMELINE)

OSThread::OSThread()
    : BaseThread(true),
      id_(OSThread::GetCurrentThreadId()),
#ifdef SUPPORT_TIMELINE
      trace_id_(OSThread::GetCurrentThreadTraceId()),
#endif
      name_(OSThread::GetCurrentThreadName()),
      timeline_block_lock_(),
      log_(new class Log()) {
  // Try to get accurate stack bounds from pthreads, etc.
  if (!GetCurrentStackBounds(&stack_limit_, &stack_base_)) {
    FATAL("Failed to retrieve stack bounds");
  }

  stack_headroom_ = CalculateHeadroom(stack_base_ - stack_limit_);

#if defined(USING_THREAD_SANITIZER)
  // The Mac default stack size of 8 MB results in TSAN's stack recording
  // overflowing in some tests before Dart reaches regular stack overflow.
  // Clamp the stack size used by Dart so we hit a Dart stack overflow
  // exception before corrupting TSAN. This is only relevant in run_vm_tests,
  // which runs Dart on the main thread. The standalone embedder runs even the
  // main isolate on thread pool workers, which the VM created with a smaller
  // stack size.
  const size_t kMaxDartStackSize = GetMaxStackSize();
  if (stack_base_ - stack_limit_ - stack_headroom_ > kMaxDartStackSize) {
    stack_headroom_ = stack_base_ - stack_limit_ - kMaxDartStackSize;
  }
#endif

  ASSERT(stack_base_ != 0);
  ASSERT(stack_limit_ != 0);
  ASSERT(stack_base_ > stack_limit_);
  ASSERT(stack_base_ > GetCurrentStackPointer());
  ASSERT(stack_limit_ < GetCurrentStackPointer());
  RELEASE_ASSERT(HasStackHeadroom());
}

OSThread* OSThread::CreateOSThread() {
  ASSERT(thread_list_lock_ != nullptr);
  MutexLocker ml(thread_list_lock_);
  if (!creation_enabled_) {
    return nullptr;
  }
  OSThread* os_thread = new OSThread();
  AddThreadToListLocked(os_thread);
  return os_thread;
}

OSThreadPtr::~OSThreadPtr() {
  delete thread_;
  thread_ = nullptr;
}

OSThread::~OSThread() {
  if (!is_os_thread()) {
    // If the embedder enters an isolate on this thread and does not exit the
    // isolate, the thread local at thread_key_, which we are destructing here,
    // will contain a dart::Thread instead of a dart::OSThread.
    FATAL("Thread exited without calling Dart_ExitIsolate");
  }
  RemoveThreadFromList(this);
  delete log_;
  log_ = nullptr;
#if defined(SUPPORT_TIMELINE)
  RecorderSynchronizationLockScope ls;
  TimelineEventRecorder* recorder = Timeline::recorder();
  if (recorder != nullptr && !ls.IsShuttingDown()) {
    // Acquire the recorder's lock so that |timeline_block_| cannot be given to
    // another thread until the call to |TimelineEventRecorder::FinishBlock| is
    // complete. This is guarded by a check ensuring that the recorder has not
    // yet been deleted, and thus ensuring that the recorder's lock can be
    // acquired. It is fine to skip the call to
    // |TimelineEventRecorder::FinishBlock| if the recorder has already been
    // deleted, because only the recorder cares about whether blocks have been
    // marked as finished.
    MutexLocker recorder_lock_locker(&Timeline::recorder()->lock_);
    MutexLocker timeline_block_lock_locker(timeline_block_lock());
    Timeline::recorder()->FinishBlock(timeline_block_);
  }
#endif
  timeline_block_ = nullptr;
  free(name_);
}

void OSThread::SetName(const char* name) {
  MutexLocker ml(thread_list_lock_);
  // Clear the old thread name.
  if (name_ != nullptr) {
    free(name_);
    name_ = nullptr;
  }
  ASSERT(OSThread::Current() == this);
  ASSERT(name != nullptr);
  name_ = Utils::StrDup(name);

#if defined(SUPPORT_TIMELINE)
  UpdateTimelineTrackMetadata(*this);
#endif  // defined(SUPPORT_TIMELINE)
}

DART_NOINLINE
uword OSThread::GetCurrentStackPointer() {
#ifdef _MSC_VER
  return reinterpret_cast<uword>(_AddressOfReturnAddress()) + kWordSize;
#elif __GNUC__
#if defined(HOST_ARCH_RISCV32) || defined(HOST_ARCH_RISCV64)
  // RISC-V has unusual choice of FP as SP at entry (i.e., DWARF CFA).
  return reinterpret_cast<uword>(__builtin_frame_address(0));
#else
  // Usually FP is address of saved FP, two slots from SP at entry.
  return reinterpret_cast<uword>(__builtin_frame_address(0)) + 2 * kWordSize;
#endif
#else
#error Unimplemented
#endif
}

#if defined(DART_INCLUDE_PROFILER)
void OSThread::DisableThreadInterrupts() {
  ASSERT(OSThread::Current() == this);
  thread_interrupt_disabled_.fetch_add(1u);
}

void OSThread::EnableThreadInterrupts() {
  ASSERT(OSThread::Current() == this);
  uintptr_t old = thread_interrupt_disabled_.fetch_sub(1u);
  if (Profiler::IsRunning() && (old == 1)) {
    ThreadInterrupter::WakeUp();
  }
  if (old == 0) {
    // We just decremented from 0, this means we've got a mismatched pair
    // of calls to EnableThreadInterrupts and DisableThreadInterrupts.
    FATAL("Invalid call to OSThread::EnableThreadInterrupts()");
  }
}

bool OSThread::ThreadInterruptsEnabled() {
  return thread_interrupt_disabled_ == 0;
}
#endif  // defined(DART_INCLUDE_PROFILER)

void OSThread::Init() {
  // Allocate the global OSThread lock.
  if (thread_list_lock_ == nullptr) {
    thread_list_lock_ = new Mutex();
  }
  ASSERT(thread_list_lock_ != nullptr);

  // Enable creation of OSThread structures in the VM.
  EnableOSThreadCreation();

  // Create a new OSThread structure and set it as the TLS.
  OSThread* os_thread = CreateOSThread();
  ASSERT(os_thread != nullptr);
  OSThread::SetCurrent(os_thread);
  os_thread->SetName("Dart_Initialize");
}

void OSThread::Cleanup() {
// We cannot delete the thread list lock, yet.
// See the note on thread_list_lock_ in os_thread.h.
#if 0
  if (thread_list_lock_ != nullptr) {
    // Delete the global OSThread lock.
    ASSERT(thread_list_lock_ != nullptr);
    delete thread_list_lock_;
    thread_list_lock_ = nullptr;
  }
#endif
}

OSThread* OSThread::CreateAndSetUnknownThread() {
  ASSERT(OSThread::GetCurrentTLS() == nullptr);
  OSThread* os_thread = CreateOSThread();
  if (os_thread != nullptr) {
    OSThread::SetCurrent(os_thread);
    if (os_thread->name() == nullptr) {
      os_thread->SetName("Unknown");
    }
  }
  return os_thread;
}

bool OSThread::IsThreadInList(ThreadId id) {
  if (id == OSThread::kInvalidThreadId) {
    return false;
  }
  OSThreadIterator it;
  while (it.HasNext()) {
    ASSERT(OSThread::thread_list_lock_->IsOwnedByCurrentThread());
    OSThread* t = it.Next();
    // An address test is not sufficient because the allocator may recycle
    // the address for another Thread. Test against the thread's id.
    if (t->id() == id) {
      return true;
    }
  }
  return false;
}

bool OSThread::CanCreateOSThreads() {
  return creation_enabled_;
}

void OSThread::DisableOSThreadCreation() {
  MutexLocker ml(thread_list_lock_);
  creation_enabled_ = false;
}

void OSThread::EnableOSThreadCreation() {
  MutexLocker ml(thread_list_lock_);
  creation_enabled_ = true;
}

OSThread* OSThread::GetOSThreadFromThread(ThreadState* thread) {
  ASSERT(thread->os_thread() != nullptr);
  return thread->os_thread();
}

void OSThread::AddThreadToListLocked(OSThread* thread) {
  ASSERT(thread != nullptr);
  ASSERT(thread_list_lock_ != nullptr);
  ASSERT(OSThread::thread_list_lock_->IsOwnedByCurrentThread());
  ASSERT(creation_enabled_);
  ASSERT(thread->thread_list_next_ == nullptr);

#if defined(DEBUG)
  {
    // Ensure that we aren't already in the list.
    OSThread* current = thread_list_head_;
    while (current != nullptr) {
      ASSERT(current != thread);
      current = current->thread_list_next_;
    }
  }
#endif

  // Insert at head of list.
  thread->thread_list_next_ = thread_list_head_;
  thread_list_head_ = thread;
}

void OSThread::RemoveThreadFromList(OSThread* thread) {
  bool final_thread = false;
  {
    ASSERT(thread != nullptr);
    ASSERT(thread_list_lock_ != nullptr);
    MutexLocker ml(thread_list_lock_);
    OSThread* current = thread_list_head_;
    OSThread* previous = nullptr;

    // Scan across list and remove |thread|.
    while (current != nullptr) {
      if (current == thread) {
        // We found |thread|, remove from list.
        if (previous == nullptr) {
          thread_list_head_ = thread->thread_list_next_;
        } else {
          previous->thread_list_next_ = current->thread_list_next_;
        }
        thread->thread_list_next_ = nullptr;
        final_thread = !creation_enabled_ && (thread_list_head_ == nullptr);
        break;
      }
      previous = current;
      current = current->thread_list_next_;
    }
  }
  // Check if this is the last thread. The last thread does a cleanup
  // which removes the thread local key and the associated mutex.
  if (final_thread) {
    Cleanup();
  }
}

// current_vm_thread_ is also accessed from signal handlers for the profiler.
// When detaching the thread, it is important that the stores don't get
// reordered, such as
//     current_vm_thread_ = nullptr;
//     thread->set_os_thread(nullptr);
// otherwise the profiler may observe a non-null Thread::Current() with a null
// thread->os_thread().
// Logically current_vm_thread_ should be volatile, but that is too restrictive
// around loads. Instead, we block inlining of the much rarer stores.
DART_NOINLINE
void OSThread::SetCurrentTLS(BaseThread* value) {
  // Provides thread-local destructors.
  current_os_thread_.set(static_cast<OSThread*>(value));

  // Allows the C compiler more freedom to optimize.
  if ((value != nullptr) && !value->is_os_thread()) {
    current_vm_thread_ = static_cast<Thread*>(value);
  } else {
    current_vm_thread_ = nullptr;
  }
}

void OSThread::Start(const char* name,
                     ThreadStartFunction function,
                     uword parameter) {
  int result = TryStart(name, function, parameter);
  if (result != 0) {
    const int kBufferSize = 1024;
    char error_buf[kBufferSize];
    FATAL("Could not start thread %s: %d (%s)", name, result,
          Utils::StrError(result, error_buf, kBufferSize));
  }
}

OSThreadIterator::OSThreadIterator() {
  ASSERT(OSThread::thread_list_lock_ != nullptr);
  // Lock the thread list while iterating.
  OSThread::thread_list_lock_->Lock();
  next_ = OSThread::thread_list_head_;
}

OSThreadIterator::~OSThreadIterator() {
  ASSERT(OSThread::thread_list_lock_ != nullptr);
  // Unlock the thread list when done.
  OSThread::thread_list_lock_->Unlock();
}

bool OSThreadIterator::HasNext() const {
  ASSERT(OSThread::thread_list_lock_ != nullptr);
  ASSERT(OSThread::thread_list_lock_->IsOwnedByCurrentThread());
  return next_ != nullptr;
}

OSThread* OSThreadIterator::Next() {
  ASSERT(OSThread::thread_list_lock_ != nullptr);
  ASSERT(OSThread::thread_list_lock_->IsOwnedByCurrentThread());
  OSThread* current = next_;
  next_ = next_->thread_list_next_;
  return current;
}

}  // namespace dart
