// 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_OS_THREAD_H_
#define RUNTIME_VM_OS_THREAD_H_

#include "platform/atomic.h"
#include "platform/globals.h"
#include "platform/safe_stack.h"
#include "platform/synchronization.h"
#include "platform/thread_sanitizer.h"
#include "platform/threads.h"
#include "platform/utils.h"
#include "vm/allocation.h"
#include "vm/globals.h"

// Declare the OS-specific types ahead of defining the generic classes.
#if defined(DART_USE_ABSL)
#include "vm/os_thread_absl.h"
#elif defined(DART_HOST_OS_ANDROID)
#include "vm/os_thread_android.h"
#elif defined(DART_HOST_OS_FUCHSIA)
#include "vm/os_thread_fuchsia.h"
#elif defined(DART_HOST_OS_LINUX)
#include "vm/os_thread_linux.h"
#elif defined(DART_HOST_OS_MACOS)
#include "vm/os_thread_macos.h"
#elif defined(DART_HOST_OS_WINDOWS)
#include "vm/os_thread_win.h"
#else
#error Unknown target os.
#endif

namespace dart {

// Forward declarations.
class Log;
class Mutex;
class ThreadState;
class TimelineEventBlock;

class BaseThread {
 public:
  bool is_os_thread() const { return is_os_thread_; }

 private:
  explicit BaseThread(bool is_os_thread) : is_os_thread_(is_os_thread) {}
  virtual ~BaseThread() {}

  bool is_os_thread_;

  friend class ThreadState;
  friend class OSThread;

  DISALLOW_IMPLICIT_CONSTRUCTORS(BaseThread);
};

using ThreadId = platform::ThreadId;

class OSThread;

// Wrapper to get TLS destructor.
class OSThreadPtr {
 public:
  constexpr OSThreadPtr() {}
  ~OSThreadPtr();

  OSThread* get() const { return thread_; }
  void set(OSThread* value) { thread_ = value; }

 private:
  OSThread* thread_ = nullptr;
};

// Low-level operations on OS platform threads.
class OSThread : public BaseThread {
 public:
  static const uword kInvalidStackLimit = ~static_cast<uword>(0);

  // The constructor of OSThread is never called directly, instead we call
  // this factory style method 'CreateOSThread' to create OSThread structures.
  // The method can return a nullptr if the Dart VM is in shutdown mode.
  static OSThread* CreateOSThread();
  ~OSThread();

  ThreadId id() const {
    ASSERT(id_ != OSThread::kInvalidThreadId);
    return id_;
  }

#ifdef SUPPORT_TIMELINE
  ThreadId trace_id() const {
    ASSERT(trace_id_ != OSThread::kInvalidThreadId);
    return trace_id_;
  }
#endif

  const char* name() const { return name_; }

  void SetName(const char* name);

  Mutex* timeline_block_lock() const { return &timeline_block_lock_; }

  // Only safe to access when holding |timeline_block_lock_|.
  TimelineEventBlock* TimelineBlockLocked() const {
    ASSERT(timeline_block_lock()->IsOwnedByCurrentThread());
    return timeline_block_;
  }

  // Only safe to access when holding |timeline_block_lock_|.
  void SetTimelineBlockLocked(TimelineEventBlock* block) {
    ASSERT(timeline_block_lock()->IsOwnedByCurrentThread());
    timeline_block_ = block;
  }

  Log* log() const { return log_; }

  uword stack_base() const { return stack_base_; }
  uword stack_limit() const { return stack_limit_; }
  uword overflow_stack_limit() const { return stack_limit_ + stack_headroom_; }

  bool HasStackHeadroom() { return HasStackHeadroom(stack_headroom_); }
  bool HasStackHeadroom(intptr_t headroom) {
    return GetCurrentStackPointer() > (stack_limit_ + headroom);
  }

#ifdef SUPPORT_TIMELINE
  static ThreadId GetCurrentThreadTraceId();
#endif  // SUPPORT_TIMELINE

  // May fail for the main thread on Linux if resources are low.
  static bool GetCurrentStackBounds(uword* lower, uword* upper);

  // Returns the current C++ stack pointer. Equivalent taking the address of a
  // stack allocated local, but plays well with AddressSanitizer and SafeStack.
  // Accurate enough for stack overflow checks but not accurate enough for
  // alignment checks.
  static uword GetCurrentStackPointer();

#if defined(USING_SAFE_STACK)
  static uword GetCurrentSafestackPointer();
  static void SetCurrentSafestackPointer(uword ssp);
#endif

#if defined(DART_INCLUDE_PROFILER)
  // Used to temporarily disable or enable thread interrupts.
  void DisableThreadInterrupts();
  void EnableThreadInterrupts();
  bool ThreadInterruptsEnabled();
#else
  // Used to temporarily disable or enable thread interrupts.
  void DisableThreadInterrupts() {}
  void EnableThreadInterrupts() {}
  bool ThreadInterruptsEnabled() { return false; }
#endif  // defined(DART_INCLUDE_PROFILER)

  // The currently executing thread, or nullptr if not yet initialized.
  static OSThread* TryCurrent() {
    BaseThread* thread = GetCurrentTLS();
    OSThread* os_thread = nullptr;
    if (thread != nullptr) {
      if (thread->is_os_thread()) {
        os_thread = reinterpret_cast<OSThread*>(thread);
      } else {
        ThreadState* vm_thread = reinterpret_cast<ThreadState*>(thread);
        os_thread = GetOSThreadFromThread(vm_thread);
      }
    }
    return os_thread;
  }

  // The currently executing thread. If there is no currently executing thread,
  // a new OSThread is created and returned.
  static OSThread* Current() {
    OSThread* os_thread = TryCurrent();
    if (os_thread == nullptr) {
      os_thread = CreateAndSetUnknownThread();
    }
    return os_thread;
  }
  static void SetCurrent(OSThread* current) { SetCurrentTLS(current); }

  static ThreadState* CurrentVMThread() { return current_vm_thread_; }
#if defined(DEBUG)
  static void SetCurrentVMThread(ThreadState* thread) {
    current_vm_thread_ = thread;
  }
#endif

  // TODO(5411455): Use flag to override default value and Validate the
  // stack size by querying OS.
  static uword GetSpecifiedStackSize() {
    intptr_t headroom =
        OSThread::CalculateHeadroom(OSThread::GetMaxStackSize());
    ASSERT(headroom < OSThread::GetMaxStackSize());
    uword stack_size = OSThread::GetMaxStackSize() - headroom;
    return stack_size;
  }
  static BaseThread* GetCurrentTLS() { return current_os_thread_.get(); }
  static void SetCurrentTLS(BaseThread* value);

  typedef void (*ThreadStartFunction)(uword parameter);
  typedef void (*ThreadDestructor)(void* parameter);

  // Start a thread running the specified function. Returns 0 if the
  // thread started successfully and a system specific error code if
  // the thread failed to start.
  DART_WARN_UNUSED_RESULT static int TryStart(const char* name,
                                              ThreadStartFunction function,
                                              uword parameter);
  // Start a thread running the specified function. If the thread fails to
  // start, then exit with an descriptive error message.
  static void Start(const char* name,
                    ThreadStartFunction function,
                    uword parameter);

  static intptr_t GetMaxStackSize();
  static void Join(ThreadJoinId id);
  static void Detach(ThreadJoinId id);
  static intptr_t ThreadIdToIntPtr(ThreadId id);
  static ThreadId ThreadIdFromIntPtr(intptr_t id);

  // This function can be called only once per OSThread, and should only be
  // called when the returned id will eventually be passed to OSThread::Join().
  static ThreadJoinId GetCurrentThreadJoinId(OSThread* thread);

  // Called at VM startup and shutdown.
  static void Init();

  static bool IsThreadInList(ThreadId id);

  static void DisableOSThreadCreation();
  static bool CanCreateOSThreads();
  static void EnableOSThreadCreation();

  static constexpr intptr_t kStackSizeBufferMax = (16 * KB * kWordSize);
  static constexpr float kStackSizeBufferFraction = 0.5;
  static const ThreadJoinId kInvalidThreadJoinId;

  static constexpr ThreadId kInvalidThreadId = platform::kInvalidThreadId;
  static ThreadId GetCurrentThreadId() {
    return platform::GetCurrentThreadId();
  }

  static bool Compare(ThreadId a, ThreadId b) {
    return platform::AreSameThreads(a, b);
  }

 private:
  // The constructor is private as CreateOSThread should be used
  // to create a new OSThread structure.
  OSThread();

  // These methods should not be used in a generic way and hence
  // are private, they have been added to solve the problem of
  // accessing the VM thread structure from an OSThread object
  // in the windows thread interrupter which is used for profiling.
  // We could eliminate this requirement if the windows thread interrupter
  // is implemented differently.
  ThreadState* thread() const { return thread_; }
  NO_SANITIZE_THREAD
  ThreadState* thread_ignore_race() const { return thread_; }
  void set_thread(ThreadState* value) { thread_ = value; }

  static void Cleanup();

  // Retrieves the name given to the current thread at the OS level and returns
  // it as a heap-allocated string that must eventually be freed by the caller
  // using free. Returns |nullptr| when the name cannot be retrieved.
  static char* GetCurrentThreadName();
  static OSThread* GetOSThreadFromThread(ThreadState* thread);
  static void AddThreadToListLocked(OSThread* thread);
  static void RemoveThreadFromList(OSThread* thread);
  static OSThread* CreateAndSetUnknownThread();

  static uword CalculateHeadroom(uword stack_size) {
    uword headroom = kStackSizeBufferFraction * stack_size;
    return (headroom > kStackSizeBufferMax) ? kStackSizeBufferMax : headroom;
  }

  const ThreadId id_;
#if defined(DEBUG)
  // In DEBUG mode we use this field to ensure that GetCurrentThreadJoinId is
  // only called once per OSThread.
  ThreadJoinId join_id_ = kInvalidThreadJoinId;
#endif
#ifdef SUPPORT_TIMELINE
  const ThreadId trace_id_;  // Used to interface with tracing tools.
#endif
  char* name_;  // A name for this thread.

  mutable Mutex timeline_block_lock_;
  // The block that the timeline recorder has permitted this thread to write
  // events to.
  TimelineEventBlock* timeline_block_ = nullptr;

  // All |Thread|s are registered in the thread list.
  OSThread* thread_list_next_ = nullptr;

#if defined(DART_INCLUDE_PROFILER)
  // Thread interrupts disabled by default.
  RelaxedAtomic<uintptr_t> thread_interrupt_disabled_ = {1};
#endif  // defined(DART_INCLUDE_PROFILER)

  Log* log_;
  uword stack_base_ = 0;
  uword stack_limit_ = 0;
  uword stack_headroom_ = 0;
  ThreadState* thread_ = nullptr;
  // The ThreadPool::Worker which owns this OSThread. If this OSThread was not
  // started by a ThreadPool it will be nullptr. This TLS value is not
  // protected and should only be read/written by the OSThread itself.
  void* owning_thread_pool_worker_ = nullptr;

  // thread_list_lock_ cannot have a static lifetime because the order in which
  // destructors run is undefined. At the moment this lock cannot be deleted
  // either since otherwise, if a thread only begins to run after we have
  // started to run TLS destructors for a call to exit(), there will be a race
  // on its deletion in CreateOSThread().
  static Mutex* thread_list_lock_;
  static OSThread* thread_list_head_;
  static bool creation_enabled_;

  // Inline initialization is important for avoiding unnecessary TLS
  // initialization checks at each use.
  static inline thread_local ThreadState* current_vm_thread_ = nullptr;

  // Inline initialization results in duplicate definition link errors on Mac.
  // But this TLS is rarely accessed, so the guard doesn't matter much.
  static thread_local OSThreadPtr current_os_thread_;

  friend class Thread;  // to access set_thread(Thread*).
  friend class OSThreadIterator;
  friend class ThreadInterrupterFuchsia;
  friend class ThreadInterrupterMacOS;
  friend class ThreadInterrupterWin;
  friend class ThreadPool;  // to access owning_thread_pool_worker_
};

// Note that this takes the thread list lock, prohibiting threads from coming
// on- or off-line.
class OSThreadIterator : public ValueObject {
 public:
  OSThreadIterator();
  ~OSThreadIterator();

  // Returns false when there are no more threads left.
  bool HasNext() const;

  // Returns the current thread and moves forward.
  OSThread* Next();

 private:
  OSThread* next_;
};

// Mark when we are running in a signal handler (Linux, Android) or with a
// suspended thread (Windows, Mac, Fuchia). During this time, we cannot take
// locks, access Thread/Isolate::Current(), or use malloc.
class ThreadInterruptScope : public ValueObject {
#if defined(DEBUG)
 public:
  ThreadInterruptScope() {
    // Poison attempts to use Thread::Current. This is much cheaper than adding
    // an assert in Thread::Current itself.
    saved_current_vm_thread_ = OSThread::CurrentVMThread();
    OSThread::SetCurrentVMThread(reinterpret_cast<ThreadState*>(0xabababab));
  }

  ~ThreadInterruptScope() {
    OSThread::SetCurrentVMThread(saved_current_vm_thread_);
  }

 private:
  DisallowMutexLockingScope disallow_locks_;
  ThreadState* saved_current_vm_thread_;
#endif  // DEBUG
};

}  // namespace dart

#endif  // RUNTIME_VM_OS_THREAD_H_
