/* Copyright (C) 2002-2023 Free Software Foundation, Inc.

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.

GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */

/* Threads compatibility routines for libgcc2 for VxWorks.

   This file implements the GTHREAD_CXX0X part of the interface
   exposed by gthr-vxworks.h, using APIs exposed by regular (!AE/653)
   VxWorks kernels.  */

#include "gthr.h"

#if __GTHREADS_CXX0X

#include <taskLib.h>
#include <stdlib.h>

#define __TIMESPEC_TO_NSEC(timespec) \
  ((long long)timespec.tv_sec * 1000000000 + (long long)timespec.tv_nsec)

#define __TIMESPEC_TO_TICKS(timespec) \
  ((long long)(sysClkRateGet() * __TIMESPEC_TO_NSEC(timespec) + 999999999) \
    / 1000000000)

#ifdef __RTP__
  void tls_delete_hook (void);
  #define __CALL_DELETE_HOOK(tcb) tls_delete_hook()
#else
  /* In kernel mode, we need to pass the TCB to task_delete_hook. The TCB is
     the pointer to the WIND_TCB structure and is the ID of the task.  */
  void tls_delete_hook (void *TCB);
  #define __CALL_DELETE_HOOK(tcb) tls_delete_hook((WIND_TCB *) ((tcb)->task_id))
#endif

int
__gthread_cond_signal (__gthread_cond_t *cond)
{
  if (!cond)
    return ERROR;

  /* If nobody is waiting, skip the semGive altogether: no one can get
     in line while we hold the mutex associated with *COND.  We could
     skip this test altogether, but it's presumed cheaper than going
     through the give and take below, and that a signal without a
     waiter occurs often enough for the test to be worth it.  */
  SEM_INFO info;
  memset (&info, 0, sizeof (info));
  __RETURN_ERRNO_IF_NOT_OK (semInfoGet (*cond, &info));
  if (info.numTasks == 0)
    return OK;

  int ret = __CHECK_RESULT (semGive (*cond));

  /* It might be the case, however, that when we called semInfo, there
     was a waiter just about to timeout, and by the time we called
     semGive, it had already timed out, so our semGive would leave the
     *cond semaphore full, so the next caller of wait would pass
     through.  We don't want that.  So, make sure we leave the
     semaphore empty.  Despite the window in which the semaphore will
     be full, this works because:

     - we're holding the mutex, so nobody else can semGive, and any
       pending semTakes are actually within semExchange.  there might
       be others blocked to acquire the mutex, but those are not
       relevant for the analysis.

     - if there was another non-timed out waiter, semGive will wake it
       up immediately instead of leaving the semaphore full, so the
       semTake below will time out, and the semantics are as expected

     - otherwise, if all waiters timed out before the semGive (or if
       there weren't any to begin with), our semGive completed leaving
       the semaphore full, and our semTake below will consume it
       before any other waiter has a change to reach the semExchange,
       because we're holding the mutex.  */
  if (ret == OK)
    semTake (*cond, NO_WAIT);

  return ret;
}

/* -------------------- Timed Condition Variables --------------------- */

int
__gthread_cond_timedwait (__gthread_cond_t *cond,
			  __gthread_mutex_t *mutex,
			  const __gthread_time_t *abs_timeout)
{
  if (!cond)
    return ERROR;

  if (!mutex)
    return ERROR;

  if (!abs_timeout)
    return ERROR;

  struct timespec current;
  if (clock_gettime (CLOCK_REALTIME, &current) == ERROR)
    /* CLOCK_REALTIME is not supported.  */
    return ERROR;

  const long long abs_timeout_ticks = __TIMESPEC_TO_TICKS ((*abs_timeout));
  const long long current_ticks = __TIMESPEC_TO_TICKS (current);

  long long waiting_ticks;

  if (current_ticks < abs_timeout_ticks)
    waiting_ticks = abs_timeout_ticks - current_ticks;
  else
    /* The point until we would need to wait is in the past,
       no need to wait at all.  */
    waiting_ticks = 0;

  /* We check that waiting_ticks can be safely casted as an int.  */
  if (waiting_ticks > INT_MAX)
    waiting_ticks = INT_MAX;

  int ret = __CHECK_RESULT (semExchange (*mutex, *cond, waiting_ticks));

  __RETURN_ERRNO_IF_NOT_OK (semTake (*mutex, WAIT_FOREVER));

  return ret;
}

/* --------------------------- Timed Mutexes ------------------------------ */

int
__gthread_mutex_timedlock (__gthread_mutex_t *m,
			   const __gthread_time_t *abs_time)
{
  if (!m)
    return ERROR;

  if (!abs_time)
    return ERROR;

  struct timespec current;
  if (clock_gettime (CLOCK_REALTIME, &current) == ERROR)
    /* CLOCK_REALTIME is not supported.  */
    return ERROR;

  const long long abs_timeout_ticks = __TIMESPEC_TO_TICKS ((*abs_time));
  const long long current_ticks = __TIMESPEC_TO_TICKS (current);
  long long waiting_ticks;

  if (current_ticks < abs_timeout_ticks)
    waiting_ticks = abs_timeout_ticks - current_ticks;
  else
    /* The point until we would need to wait is in the past,
       no need to wait at all.  */
    waiting_ticks = 0;

  /* Make sure that waiting_ticks can be safely casted as an int.  */
  if (waiting_ticks > INT_MAX)
    waiting_ticks = INT_MAX;

  return __CHECK_RESULT (semTake (*m, waiting_ticks));
}

int
__gthread_recursive_mutex_timedlock (__gthread_recursive_mutex_t *mutex,
				     const __gthread_time_t *abs_timeout)
{
  return __gthread_mutex_timedlock ((__gthread_mutex_t *)mutex, abs_timeout);
}

/* ------------------------------ Threads --------------------------------- */

/* Task control block initialization and destruction functions.  */

int
__init_gthread_tcb (__gthread_t __tcb)
{
  if (!__tcb)
    return ERROR;

  __gthread_mutex_init (&(__tcb->return_value_available));
  if (__tcb->return_value_available == SEM_ID_NULL)
    return ERROR;

  __gthread_mutex_init (&(__tcb->delete_ok));
  if (__tcb->delete_ok == SEM_ID_NULL)
    goto return_sem_delete;

  /* We lock the two mutexes used for signaling.  */
  if (__gthread_mutex_lock (&(__tcb->delete_ok)) != OK)
    goto delete_sem_delete;

  if (__gthread_mutex_lock (&(__tcb->return_value_available)) != OK)
    goto delete_sem_delete;

  __tcb->task_id = TASK_ID_NULL;
  return OK;

delete_sem_delete:
  semDelete (__tcb->delete_ok);
return_sem_delete:
  semDelete (__tcb->return_value_available);
  return ERROR;
}

/* Here, we pass a pointer to a tcb to allow calls from
   cleanup attributes.  */
void
__delete_gthread_tcb (__gthread_t* __tcb)
{
  semDelete ((*__tcb)->return_value_available);
  semDelete ((*__tcb)->delete_ok);
  free (*__tcb);
}

/* This __gthread_t stores the address of the TCB malloc'ed in
   __gthread_create.  It is then accessible via __gthread_self().  */
__thread __gthread_t __local_tcb = NULL;

__gthread_t
__gthread_self (void)
{
  if (!__local_tcb)
    {
      /* We are in the initial thread, we need to initialize the TCB.  */
      __local_tcb = malloc (sizeof (*__local_tcb));
      if (!__local_tcb)
	return NULL;

      if (__init_gthread_tcb (__local_tcb) != OK)
	{
	  __delete_gthread_tcb (&__local_tcb);
	  return NULL;
	}
      /* We do not set the mutexes in the structure as a thread is not supposed
         to join or detach himself.  */
      __local_tcb->task_id = taskIdSelf ();
    }
  return __local_tcb;
}

int
__task_wrapper (__gthread_t tcb, FUNCPTR __func, _Vx_usr_arg_t __args)
{
  if (!tcb)
    return ERROR;

  __local_tcb = tcb;

  /* We use this variable to avoid memory leaks in the case where
     the underlying function throws an exception.  */
  __attribute__ ((cleanup (__delete_gthread_tcb))) __gthread_t __tmp = tcb;

  void *return_value = (void *) __func (__args);
  tcb->return_value = return_value;

  /* Call the destructors.  */
  __CALL_DELETE_HOOK (tcb);

  /* Future calls of join() will be able to retrieve the return value.  */
  __gthread_mutex_unlock (&tcb->return_value_available);

  /* We wait for the thread to be joined or detached.  */
  __gthread_mutex_lock (&(tcb->delete_ok));
  __gthread_mutex_unlock (&(tcb->delete_ok));

  /* Memory deallocation is done by the cleanup attribute of the tmp variable.  */

  return OK;
}

/* Proper gthreads API.  */

int
__gthread_create (__gthread_t * __threadid, void *(*__func) (void *),
		  void *__args)
{
  if (!__threadid)
    return ERROR;

  int priority;
  __RETURN_ERRNO_IF_NOT_OK (taskPriorityGet (taskIdSelf (), &priority));

  int options;
  __RETURN_ERRNO_IF_NOT_OK (taskOptionsGet (taskIdSelf (), &options));

#if defined (__SPE__)
  options |= VX_SPE_TASK;
#else
  options |= VX_FP_TASK;
#endif
  options &= VX_USR_TASK_OPTIONS;

  int stacksize = 20 * 1024;

  __gthread_t tcb = malloc (sizeof (*tcb));
  if (!tcb)
    return ERROR;

  if (__init_gthread_tcb (tcb) != OK)
    {
      free (tcb);
      return ERROR;
    }

  TASK_ID task_id = taskCreate (NULL,
				priority, options, stacksize,
				(FUNCPTR) & __task_wrapper,
				(_Vx_usr_arg_t) tcb,
				(_Vx_usr_arg_t) __func,
				(_Vx_usr_arg_t) __args,
				0, 0, 0, 0, 0, 0, 0);

  /* If taskCreate succeeds, task_id will be a valid TASK_ID and not zero.  */
  __RETURN_ERRNO_IF_NOT_OK (!task_id);

  tcb->task_id = task_id;
  *__threadid = tcb;

  return __CHECK_RESULT (taskActivate (task_id));
}

int
__gthread_equal (__gthread_t __t1, __gthread_t __t2)
{
  return (__t1 == __t2) ? OK : ERROR;
}

int
__gthread_yield (void)
{
  return taskDelay (0);
}

int
__gthread_join (__gthread_t __threadid, void **__value_ptr)
{
  if (!__threadid)
    return ERROR;

  /* A thread cannot join itself.  */
  if (__threadid->task_id == taskIdSelf ())
    return ERROR;

  /* Waiting for the task to set the return value.  */
  __gthread_mutex_lock (&__threadid->return_value_available);
  __gthread_mutex_unlock (&__threadid->return_value_available);

  if (__value_ptr)
    *__value_ptr = __threadid->return_value;

  /* The task will be safely be deleted.  */
  __gthread_mutex_unlock (&(__threadid->delete_ok));

  __RETURN_ERRNO_IF_NOT_OK (taskWait (__threadid->task_id, WAIT_FOREVER));

  return OK;
}

int
__gthread_detach (__gthread_t __threadid)
{
  if (!__threadid)
    return ERROR;

  if (taskIdVerify (__threadid->task_id) != OK)
    return ERROR;

  /* The task will be safely be deleted.  */
  __gthread_mutex_unlock (&(__threadid->delete_ok));

  return OK;
}

#endif
