// 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 "platform/globals.h"  // NOLINT
#if defined(DART_HOST_OS_FUCHSIA) && !defined(DART_USE_ABSL)

#include "vm/os.h"
#include "vm/os_thread.h"
#include "vm/os_thread_fuchsia.h"

#include <errno.h>  // NOLINT
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <zircon/tls.h>
#include <zircon/types.h>

#include "platform/address_sanitizer.h"
#include "platform/assert.h"
#include "platform/safe_stack.h"

namespace dart {

class ThreadStartData {
 public:
  ThreadStartData(const char* name,
                  OSThread::ThreadStartFunction function,
                  uword parameter)
      : name_(name), function_(function), parameter_(parameter) {}

  const char* name() const { return name_; }
  OSThread::ThreadStartFunction function() const { return function_; }
  uword parameter() const { return parameter_; }

 private:
  const char* name_;
  OSThread::ThreadStartFunction function_;
  uword parameter_;

  DISALLOW_COPY_AND_ASSIGN(ThreadStartData);
};

// Dispatch to the thread start function provided by the caller. This trampoline
// is used to ensure that the thread is properly destroyed if the thread just
// exits.
static void* ThreadStart(void* data_ptr) {
  ThreadStartData* data = reinterpret_cast<ThreadStartData*>(data_ptr);

  const char* name = data->name();
  OSThread::ThreadStartFunction function = data->function();
  uword parameter = data->parameter();
  delete data;

  // Set the thread name.
  char truncated_name[ZX_MAX_NAME_LEN];
  snprintf(truncated_name, ZX_MAX_NAME_LEN, "%s", name);
  zx_handle_t thread_handle = thrd_get_zx_handle(thrd_current());
  zx_object_set_property(thread_handle, ZX_PROP_NAME, truncated_name,
                         ZX_MAX_NAME_LEN);

  // Create new OSThread object and set as TLS for new thread.
  OSThread* thread = OSThread::CreateOSThread();
  if (thread != nullptr) {
    OSThread::SetCurrent(thread);
    thread->SetName(name);
    // Call the supplied thread start function handing it its parameters.
    function(parameter);
  }

  return nullptr;
}

int OSThread::TryStart(const char* name,
                       ThreadStartFunction function,
                       uword parameter) {
  pthread_attr_t attr;
  int result = pthread_attr_init(&attr);
  RETURN_ON_PTHREAD_FAILURE(result);

  result = pthread_attr_setstacksize(&attr, OSThread::GetMaxStackSize());
  RETURN_ON_PTHREAD_FAILURE(result);

  ThreadStartData* data = new ThreadStartData(name, function, parameter);

  pthread_t tid;
  result = pthread_create(&tid, &attr, ThreadStart, data);
  RETURN_ON_PTHREAD_FAILURE(result);

  result = pthread_attr_destroy(&attr);
  RETURN_ON_PTHREAD_FAILURE(result);

  return 0;
}

const ThreadJoinId OSThread::kInvalidThreadJoinId =
    static_cast<ThreadJoinId>(0);

intptr_t OSThread::GetMaxStackSize() {
  const int kStackSize = (128 * kWordSize * KB);
  return kStackSize;
}

#ifdef SUPPORT_TIMELINE
ThreadId OSThread::GetCurrentThreadTraceId() {
  return pthread_self();
}
#endif  // SUPPORT_TIMELINE

char* OSThread::GetCurrentThreadName() {
  char* name = static_cast<char*>(malloc(ZX_MAX_NAME_LEN));
  zx_handle_t thread_handle = thrd_get_zx_handle(thrd_current());
  zx_object_get_property(thread_handle, ZX_PROP_NAME, name, ZX_MAX_NAME_LEN);
  return name;
}

ThreadJoinId OSThread::GetCurrentThreadJoinId(OSThread* thread) {
  ASSERT(thread != nullptr);
  // Make sure we're filling in the join id for the current thread.
  ASSERT(thread->id() == GetCurrentThreadId());
  // Make sure the join_id_ hasn't been set, yet.
  DEBUG_ASSERT(thread->join_id_ == kInvalidThreadJoinId);
  pthread_t id = pthread_self();
#if defined(DEBUG)
  thread->join_id_ = id;
#endif
  return id;
}

void OSThread::Join(ThreadJoinId id) {
  int result = pthread_join(id, nullptr);
  VALIDATE_PTHREAD_RESULT(result);
}

void OSThread::Detach(ThreadJoinId id) {
  int result = pthread_detach(id);  // NOLINT
  VALIDATE_PTHREAD_RESULT(result);
}

intptr_t OSThread::ThreadIdToIntPtr(ThreadId id) {
  COMPILE_ASSERT(sizeof(id) <= sizeof(intptr_t));
  return static_cast<intptr_t>(id);
}

ThreadId OSThread::ThreadIdFromIntPtr(intptr_t id) {
  return static_cast<ThreadId>(id);
}

bool OSThread::GetCurrentStackBounds(uword* lower, uword* upper) {
  pthread_attr_t attr;
  if (pthread_getattr_np(pthread_self(), &attr) != 0) {
    return false;
  }

  void* base;
  size_t size;
  int error = pthread_attr_getstack(&attr, &base, &size);
  pthread_attr_destroy(&attr);
  if (error != 0) {
    return false;
  }

  *lower = reinterpret_cast<uword>(base);
  *upper = *lower + size;
  return true;
}

#if defined(USING_SAFE_STACK)
#define STRINGIFY(s) #s
NO_SANITIZE_ADDRESS
NO_SANITIZE_SAFE_STACK
uword OSThread::GetCurrentSafestackPointer() {
  uword result;
#if defined(HOST_ARCH_X64)
#define _loadfsword(index) "movq  %%fs:" STRINGIFY(index) ", %0"
  asm volatile(_loadfsword(ZX_TLS_UNSAFE_SP_OFFSET)
               : "=r"(result)  // outputs
  );
#undef _loadfsword
#elif defined(HOST_ARCH_ARM64)
#define _loadword(index) "ldr %0, [%0, " STRINGIFY(index) "]"
  asm volatile("mrs %0, TPIDR_EL0;\n" _loadword(ZX_TLS_UNSAFE_SP_OFFSET)
               : "=r"(result)  // outputs
  );
#elif defined(HOST_ARCH_RISCV64)
  asm volatile("ld %0, " STRINGIFY(ZX_TLS_UNSAFE_SP_OFFSET) "(tp)"
               : "=r"(result)  // outputs
  );
#else
#error "Architecture not supported"
#endif
  return result;
}

NO_SANITIZE_ADDRESS
NO_SANITIZE_SAFE_STACK
void OSThread::SetCurrentSafestackPointer(uword ssp) {
#if defined(HOST_ARCH_X64)
#define str(s) #s
#define _storefsword(index) "movq %0, %%fs:" str(index)
  asm volatile(_storefsword(ZX_TLS_UNSAFE_SP_OFFSET)
               :           // outputs.
               : "r"(ssp)  // inputs.
               :           // clobbered.
  );
#undef _storefsword
#undef str
#elif defined(HOST_ARCH_ARM64)
#define _storeword(index) "str %1, [%0, " STRINGIFY(index) "]"
  uword tmp;
  asm volatile("mrs %0, TPIDR_EL0;\n" _storeword(ZX_TLS_UNSAFE_SP_OFFSET)
               : "=r"(tmp)  // outputs.
               : "r"(ssp)   // inputs.
               :            // clobbered.
  );
#elif defined(HOST_ARCH_RISCV64)
  asm volatile("sd %0, " STRINGIFY(ZX_TLS_UNSAFE_SP_OFFSET) "(tp)"
               :           // outputs.
               : "r"(ssp)  // inputs.
               :           // clobbered.
  );
#else
#error "Architecture not supported"
#endif
}
#undef STRINGIFY
#endif

}  // namespace dart

#endif  // defined(DART_HOST_OS_FUCHSIA) && !defined(DART_USE_ABSL)
