/*
 * Copyright © 2008 Ryan Lortie
 * Copyright © 2010 Codethink Limited
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 *
 * Author: Ryan Lortie <desrt@desrt.ca>
 */

#include "config.h"

#include "gbitlock.h"

#include <glib/gmacros.h>
#include <glib/gmessages.h>
#include <glib/gatomic.h>
#include <glib/gslist.h>
#include <glib/gthread.h>
#include <glib/gslice.h>

#include "gthreadprivate.h"

#ifdef G_BIT_LOCK_FORCE_FUTEX_EMULATION
#undef HAVE_FUTEX
#undef HAVE_FUTEX_TIME64
#endif

#ifndef HAVE_FUTEX
static GMutex g_futex_mutex;
static GSList *g_futex_address_list = NULL;
#endif

#if defined(HAVE_FUTEX) || defined(HAVE_FUTEX_TIME64)
/*
 * We have headers for futex(2) on the build machine.  This does not
 * imply that every system that ever runs the resulting glib will have
 * kernel support for futex, but you'd have to have a pretty old
 * kernel in order for that not to be the case.
 *
 * If anyone actually gets bit by this, please file a bug. :)
 */

/* < private >
 * g_futex_wait:
 * @address: a pointer to an integer
 * @value: the value that should be at @address
 *
 * Atomically checks that the value stored at @address is equal to
 * @value and then blocks.  If the value stored at @address is not
 * equal to @value then this function returns immediately.
 *
 * To unblock, call g_futex_wake() on @address.
 *
 * This call may spuriously unblock (for example, in response to the
 * process receiving a signal) but this is not guaranteed.  Unlike the
 * Linux system call of a similar name, there is no guarantee that a
 * waiting process will unblock due to a g_futex_wake() call in a
 * separate process.
 */
static void
g_futex_wait (const gint *address,
              gint        value)
{
  g_futex_simple (address, (gsize) FUTEX_WAIT_PRIVATE, (gsize) value, NULL);
}

/* < private >
 * g_futex_wake:
 * @address: a pointer to an integer
 *
 * Nominally, wakes one thread that is blocked in g_futex_wait() on
 * @address (if any thread is currently waiting).
 *
 * As mentioned in the documentation for g_futex_wait(), spurious
 * wakeups may occur.  As such, this call may result in more than one
 * thread being woken up.
 */
static void
g_futex_wake (const gint *address)
{
  g_futex_simple (address, (gsize) FUTEX_WAKE_PRIVATE, (gsize) 1, NULL);
}

#else

/* emulate futex(2) */
typedef struct
{
  const gint *address;
  gint ref_count;
  GCond wait_queue;
} WaitAddress;

static WaitAddress *
g_futex_find_address (const gint *address)
{
  GSList *node;

  for (node = g_futex_address_list; node; node = node->next)
    {
      WaitAddress *waiter = node->data;

      if (waiter->address == address)
        return waiter;
    }

  return NULL;
}

static void
g_futex_wait (const gint *address,
              gint        value)
{
  g_mutex_lock (&g_futex_mutex);
  if G_LIKELY (g_atomic_int_get (address) == value)
    {
      WaitAddress *waiter;

      if ((waiter = g_futex_find_address (address)) == NULL)
        {
          waiter = g_slice_new (WaitAddress);
          waiter->address = address;
          g_cond_init (&waiter->wait_queue);
          waiter->ref_count = 0;
          g_futex_address_list =
            g_slist_prepend (g_futex_address_list, waiter);
        }

      waiter->ref_count++;
      g_cond_wait (&waiter->wait_queue, &g_futex_mutex);

      if (!--waiter->ref_count)
        {
          g_futex_address_list =
            g_slist_remove (g_futex_address_list, waiter);
          g_cond_clear (&waiter->wait_queue);
          g_slice_free (WaitAddress, waiter);
        }
    }
  g_mutex_unlock (&g_futex_mutex);
}

static void
g_futex_wake (const gint *address)
{
  WaitAddress *waiter;

  /* need to lock here for two reasons:
   *   1) need to acquire/release lock to ensure waiter is not in
   *      the process of registering a wait
   *   2) need to -stay- locked until the end to ensure a wake()
   *      in another thread doesn't cause 'waiter' to stop existing
   */
  g_mutex_lock (&g_futex_mutex);
  if ((waiter = g_futex_find_address (address)))
    g_cond_signal (&waiter->wait_queue);
  g_mutex_unlock (&g_futex_mutex);
}
#endif

#define CONTENTION_CLASSES 11
static gint g_bit_lock_contended[CONTENTION_CLASSES];  /* (atomic) */

G_ALWAYS_INLINE static inline guint
bit_lock_contended_class (gpointer address)
{
  return ((gsize) address) % G_N_ELEMENTS (g_bit_lock_contended);
}

#if (defined (i386) || defined (__amd64__))
  #if G_GNUC_CHECK_VERSION(4, 5)
    #define USE_ASM_GOTO 1
  #endif
#endif

/**
 * g_bit_lock:
 * @address: a pointer to an integer
 * @lock_bit: a bit value between 0 and 31
 *
 * Sets the indicated @lock_bit in @address.  If the bit is already
 * set, this call will block until g_bit_unlock() unsets the
 * corresponding bit.
 *
 * Attempting to lock on two different bits within the same integer is
 * not supported and will very probably cause deadlocks.
 *
 * The value of the bit that is set is (1u << @bit).  If @bit is not
 * between 0 and 31 then the result is undefined.
 *
 * This function accesses @address atomically.  All other accesses to
 * @address must be atomic in order for this function to work
 * reliably. While @address has a `volatile` qualifier, this is a historical
 * artifact and the argument passed to it should not be `volatile`.
 *
 * Since: 2.24
 **/
void
g_bit_lock (volatile gint *address,
            gint           lock_bit)
{
  gint *address_nonvolatile = (gint *) address;

#ifdef USE_ASM_GOTO
 retry:
  __asm__ volatile goto ("lock bts %1, (%0)\n"
                         "jc %l[contended]"
                         : /* no output */
                         : "r" (address), "r" (lock_bit)
                         : "cc", "memory"
                         : contended);
  return;

 contended:
  {
    guint mask = 1u << lock_bit;
    guint v;

    v = (guint) g_atomic_int_get (address_nonvolatile);
    if (v & mask)
      {
        guint class = bit_lock_contended_class (address_nonvolatile);

        g_atomic_int_add (&g_bit_lock_contended[class], +1);
        g_futex_wait (address_nonvolatile, v);
        g_atomic_int_add (&g_bit_lock_contended[class], -1);
      }
  }
  goto retry;
#else
  guint mask = 1u << lock_bit;
  guint v;

 retry:
  v = g_atomic_int_or (address_nonvolatile, mask);
  if (v & mask)
    /* already locked */
    {
      guint class = bit_lock_contended_class (address_nonvolatile);

      g_atomic_int_add (&g_bit_lock_contended[class], +1);
      g_futex_wait (address_nonvolatile, v);
      g_atomic_int_add (&g_bit_lock_contended[class], -1);

      goto retry;
    }
#endif
}

/**
 * g_bit_trylock:
 * @address: a pointer to an integer
 * @lock_bit: a bit value between 0 and 31
 *
 * Sets the indicated @lock_bit in @address, returning %TRUE if
 * successful.  If the bit is already set, returns %FALSE immediately.
 *
 * Attempting to lock on two different bits within the same integer is
 * not supported.
 *
 * The value of the bit that is set is (1u << @bit).  If @bit is not
 * between 0 and 31 then the result is undefined.
 *
 * This function accesses @address atomically.  All other accesses to
 * @address must be atomic in order for this function to work
 * reliably. While @address has a `volatile` qualifier, this is a historical
 * artifact and the argument passed to it should not be `volatile`.
 *
 * Returns: %TRUE if the lock was acquired
 *
 * Since: 2.24
 **/
gboolean
g_bit_trylock (volatile gint *address,
               gint           lock_bit)
{
#ifdef USE_ASM_GOTO
  gboolean result;

  __asm__ volatile ("lock bts %2, (%1)\n"
                    "setnc %%al\n"
                    "movzx %%al, %0"
                    : "=r" (result)
                    : "r" (address), "r" (lock_bit)
                    : "cc", "memory");

  return result;
#else
  gint *address_nonvolatile = (gint *) address;
  guint mask = 1u << lock_bit;
  guint v;

  v = g_atomic_int_or (address_nonvolatile, mask);

  return ~v & mask;
#endif
}

/**
 * g_bit_unlock:
 * @address: a pointer to an integer
 * @lock_bit: a bit value between 0 and 31
 *
 * Clears the indicated @lock_bit in @address.  If another thread is
 * currently blocked in g_bit_lock() on this same bit then it will be
 * woken up.
 *
 * This function accesses @address atomically.  All other accesses to
 * @address must be atomic in order for this function to work
 * reliably. While @address has a `volatile` qualifier, this is a historical
 * artifact and the argument passed to it should not be `volatile`.
 *
 * Since: 2.24
 **/
void
g_bit_unlock (volatile gint *address,
              gint           lock_bit)
{
  gint *address_nonvolatile = (gint *) address;

#ifdef USE_ASM_GOTO
  __asm__ volatile ("lock btr %1, (%0)"
                    : /* no output */
                    : "r" (address), "r" (lock_bit)
                    : "cc", "memory");
#else
  guint mask = 1u << lock_bit;

  g_atomic_int_and (address_nonvolatile, ~mask);
#endif

  /* Warning: unlocking may allow another thread to proceed and destroy the
   * memory that @address points to. We thus must not dereference it anymore.
   */

  {
    guint class = bit_lock_contended_class (address_nonvolatile);

    if (g_atomic_int_get (&g_bit_lock_contended[class]))
      g_futex_wake (address_nonvolatile);
  }
}


/* We emulate pointer-sized futex(2) because the kernel API only
 * supports integers.
 *
 * We assume that the 'interesting' part is always the lower order bits.
 * This assumption holds because pointer bitlocks are restricted to
 * using the low order bits of the pointer as the lock.
 *
 * On 32 bits, there is nothing to do since the pointer size is equal to
 * the integer size.  On little endian the lower-order bits don't move,
 * so do nothing.  Only on 64bit big endian do we need to do a bit of
 * pointer arithmetic: the low order bits are shifted by 4 bytes.  We
 * have a helper function that always does the right thing here.
 *
 * Since we always consider the low-order bits of the integer value, a
 * simple cast from (gsize) to (guint) always takes care of that.
 *
 * After that, pointer-sized futex becomes as simple as:
 *
 *   g_futex_wait (g_futex_int_address (address), (guint) value);
 *
 * and
 *
 *   g_futex_wake (g_futex_int_address (int_address));
 */
static const gint *
g_futex_int_address (const void *address)
{
  const gint *int_address = address;

  /* this implementation makes these (reasonable) assumptions: */
  G_STATIC_ASSERT (G_BYTE_ORDER == G_LITTLE_ENDIAN ||
      (G_BYTE_ORDER == G_BIG_ENDIAN &&
       sizeof (int) == 4 &&
       (sizeof (gpointer) == 4 || sizeof (gpointer) == 8)));

#if G_BYTE_ORDER == G_BIG_ENDIAN && GLIB_SIZEOF_VOID_P == 8
  int_address++;
#endif

  return int_address;
}

G_ALWAYS_INLINE static inline gpointer
pointer_bit_lock_mask_ptr (gpointer ptr, guint lock_bit, gboolean set, guintptr preserve_mask, gpointer preserve_ptr)
{
  guintptr x_ptr;
  guintptr x_preserve_ptr;
  guintptr lock_mask;

  x_ptr = (guintptr) ptr;

  if (preserve_mask != 0)
    {
      x_preserve_ptr = (guintptr) preserve_ptr;
      x_ptr = (x_preserve_ptr & preserve_mask) | (x_ptr & ~preserve_mask);
    }

  if (lock_bit == G_MAXUINT)
    return (gpointer) x_ptr;

  lock_mask = (guintptr) (1u << lock_bit);
  if (set)
    return (gpointer) (x_ptr | lock_mask);
  else
    return (gpointer) (x_ptr & ~lock_mask);
}

/**
 * g_pointer_bit_lock_and_get:
 * @address: (not nullable): a pointer to a #gpointer-sized value
 * @lock_bit: a bit value between 0 and 31
 * @out_ptr: (out) (optional): returns the set pointer atomically.
 *   This is the value after setting the lock, it thus always has the
 *   lock bit set, while previously @address had the lockbit unset.
 *   You may also use g_pointer_bit_lock_mask_ptr() to clear the lock bit.
 *
 * This is equivalent to g_bit_lock, but working on pointers (or other
 * pointer-sized values).
 *
 * For portability reasons, you may only lock on the bottom 32 bits of
 * the pointer.
 *
 * Since: 2.80
 **/
void
(g_pointer_bit_lock_and_get) (gpointer address,
                              guint lock_bit,
                              guintptr *out_ptr)
{
  guint class = bit_lock_contended_class (address);
  guintptr mask;
  guintptr v;

  g_return_if_fail (lock_bit < 32);

  mask = 1u << lock_bit;

#ifdef USE_ASM_GOTO
  if (G_LIKELY (!out_ptr))
    {
      while (TRUE)
        {
          __asm__ volatile goto ("lock bts %1, (%0)\n"
                                 "jc %l[contended]"
                                 : /* no output */
                                 : "r"(address), "r"((gsize) lock_bit)
                                 : "cc", "memory"
                                 : contended);
          return;

        contended:
          v = (guintptr) g_atomic_pointer_get ((gpointer *) address);
          if (v & mask)
            {
              g_atomic_int_add (&g_bit_lock_contended[class], +1);
              g_futex_wait (g_futex_int_address (address), v);
              g_atomic_int_add (&g_bit_lock_contended[class], -1);
            }
        }
    }
#endif

retry:
  v = g_atomic_pointer_or ((gpointer *) address, mask);
  if (v & mask)
    /* already locked */
    {
      g_atomic_int_add (&g_bit_lock_contended[class], +1);
      g_futex_wait (g_futex_int_address (address), (guint) v);
      g_atomic_int_add (&g_bit_lock_contended[class], -1);
      goto retry;
    }

  if (out_ptr)
    *out_ptr = (v | mask);
}

/**
 * g_pointer_bit_lock:
 * @address: (not nullable): a pointer to a #gpointer-sized value
 * @lock_bit: a bit value between 0 and 31
 *
 * This is equivalent to g_bit_lock, but working on pointers (or other
 * pointer-sized values).
 *
 * For portability reasons, you may only lock on the bottom 32 bits of
 * the pointer.
 *
 * While @address has a `volatile` qualifier, this is a historical
 * artifact and the argument passed to it should not be `volatile`.
 *
 * Since: 2.30
 **/
void
(g_pointer_bit_lock) (volatile void *address,
                      gint lock_bit)
{
  g_pointer_bit_lock_and_get ((gpointer *) address, (guint) lock_bit, NULL);
}

/**
 * g_pointer_bit_trylock:
 * @address: (not nullable): a pointer to a #gpointer-sized value
 * @lock_bit: a bit value between 0 and 31
 *
 * This is equivalent to g_bit_trylock(), but working on pointers (or
 * other pointer-sized values).
 *
 * For portability reasons, you may only lock on the bottom 32 bits of
 * the pointer.
 *
 * While @address has a `volatile` qualifier, this is a historical
 * artifact and the argument passed to it should not be `volatile`.
 *
 * Returns: %TRUE if the lock was acquired
 *
 * Since: 2.30
 **/
gboolean
(g_pointer_bit_trylock) (volatile void *address,
                         gint           lock_bit)
{
  g_return_val_if_fail (lock_bit < 32, FALSE);

  {
#ifdef USE_ASM_GOTO
    gboolean result;

    __asm__ volatile ("lock bts %2, (%1)\n"
                      "setnc %%al\n"
                      "movzx %%al, %0"
                      : "=r" (result)
                      : "r" (address), "r" ((gsize) lock_bit)
                      : "cc", "memory");

    return result;
#else
    void *address_nonvolatile = (void *) address;
    gpointer *pointer_address = address_nonvolatile;
    gsize mask = 1u << lock_bit;
    guintptr v;

    g_return_val_if_fail (lock_bit < 32, FALSE);

    v = g_atomic_pointer_or (pointer_address, mask);

    return (~(gsize) v & mask) != 0;
#endif
  }
}

/**
 * g_pointer_bit_unlock:
 * @address: (not nullable): a pointer to a #gpointer-sized value
 * @lock_bit: a bit value between 0 and 31
 *
 * This is equivalent to g_bit_unlock, but working on pointers (or other
 * pointer-sized values).
 *
 * For portability reasons, you may only lock on the bottom 32 bits of
 * the pointer.
 *
 * While @address has a `volatile` qualifier, this is a historical
 * artifact and the argument passed to it should not be `volatile`.
 *
 * Since: 2.30
 **/
void
(g_pointer_bit_unlock) (volatile void *address,
                        gint           lock_bit)
{
  void *address_nonvolatile = (void *) address;

  g_return_if_fail (lock_bit < 32);

  {
#ifdef USE_ASM_GOTO
    __asm__ volatile ("lock btr %1, (%0)"
                      : /* no output */
                      : "r" (address), "r" ((gsize) lock_bit)
                      : "cc", "memory");
#else
    gpointer *pointer_address = address_nonvolatile;
    gsize mask = 1u << lock_bit;

    g_atomic_pointer_and (pointer_address, ~mask);
#endif

    /* Warning: unlocking may allow another thread to proceed and destroy the
     * memory that @address points to. We thus must not dereference it anymore.
     */

    {
      guint class = bit_lock_contended_class (address_nonvolatile);

      if (g_atomic_int_get (&g_bit_lock_contended[class]))
        g_futex_wake (g_futex_int_address (address_nonvolatile));
    }
  }
}

/**
 * g_pointer_bit_lock_mask_ptr:
 * @ptr: (nullable): the pointer to mask
 * @lock_bit: the bit to set/clear. If set to `G_MAXUINT`, the
 *   lockbit is taken from @preserve_ptr or @ptr (depending on @preserve_mask).
 * @set: whether to set (lock) the bit or unset (unlock). This
 *   has no effect, if @lock_bit is set to `G_MAXUINT`.
 * @preserve_mask: if non-zero, a bit-mask for @preserve_ptr. The
 *   @preserve_mask bits from @preserve_ptr are set in the result.
 *   Note that the @lock_bit bit will be always set according to @set,
 *   regardless of @preserve_mask and @preserve_ptr (unless @lock_bit is
 *   `G_MAXUINT`).
 * @preserve_ptr: (nullable): if @preserve_mask is non-zero, the bits
 *   from this pointer are set in the result.
 *
 * This mangles @ptr as g_pointer_bit_lock() and g_pointer_bit_unlock()
 * do.
 *
 * Returns: the mangled pointer.
 *
 * Since: 2.80
 **/
gpointer
g_pointer_bit_lock_mask_ptr (gpointer ptr, guint lock_bit, gboolean set, guintptr preserve_mask, gpointer preserve_ptr)
{
  g_return_val_if_fail (lock_bit < 32u || lock_bit == G_MAXUINT, ptr);

  return pointer_bit_lock_mask_ptr (ptr, lock_bit, set, preserve_mask, preserve_ptr);
}

/**
 * g_pointer_bit_unlock_and_set:
 * @address: (not nullable): a pointer to a #gpointer-sized value
 * @lock_bit: a bit value between 0 and 31
 * @ptr: the new pointer value to set
 * @preserve_mask: if non-zero, those bits of the current pointer in @address
 *   are preserved.
 *   Note that the @lock_bit bit will be always set according to @set,
 *   regardless of @preserve_mask and the currently set value in @address.
 *
 * This is equivalent to g_pointer_bit_unlock() and atomically setting
 * the pointer value.
 *
 * Note that the lock bit will be cleared from the pointer. If the unlocked
 * pointer that was set is not identical to @ptr, an assertion fails. In other
 * words, @ptr must have @lock_bit unset. This also means, you usually can
 * only use this on the lowest bits.
 *
 * Since: 2.80
 **/
void (g_pointer_bit_unlock_and_set) (void *address,
                                     guint lock_bit,
                                     gpointer ptr,
                                     guintptr preserve_mask)
{
  gpointer *pointer_address = address;
  guint class = bit_lock_contended_class (address);
  gpointer ptr2;

  g_return_if_fail (lock_bit < 32u);

  if (preserve_mask != 0)
    {
      gpointer old_ptr = g_atomic_pointer_get ((gpointer *) address);

    again:
      ptr2 = pointer_bit_lock_mask_ptr (ptr, lock_bit, FALSE, preserve_mask, old_ptr);
      if (!g_atomic_pointer_compare_and_exchange_full (pointer_address, old_ptr, ptr2, &old_ptr))
        goto again;
    }
  else
    {
      ptr2 = pointer_bit_lock_mask_ptr (ptr, lock_bit, FALSE, 0, NULL);
      g_atomic_pointer_set (pointer_address, ptr2);
    }

  if (g_atomic_int_get (&g_bit_lock_contended[class]) > 0)
    g_futex_wake (g_futex_int_address (address));

  /* It makes no sense, if unlocking mangles the pointer. Assert against
   * that.
   *
   * Note that based on @preserve_mask, the pointer also gets mangled, which
   * can make sense for the caller. We don't assert for that. */
  g_return_if_fail (ptr == pointer_bit_lock_mask_ptr (ptr, lock_bit, FALSE, 0, NULL));
}
