/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
**     PR Atomic operations
*/

#include "pratom.h"
#include "primpl.h"

#include <string.h>

/*
 * The following is a fallback implementation that emulates
 * atomic operations for platforms without atomic operations.
 * If a platform has atomic operations, it should define the
 * macro _PR_HAVE_ATOMIC_OPS, and the following will not be
 * compiled in.
 */

#if !defined(_PR_HAVE_ATOMIC_OPS)

#  if defined(_PR_PTHREADS)
/*
 * PR_AtomicDecrement() is used in NSPR's thread-specific data
 * destructor.  Because thread-specific data destructors may be
 * invoked after a PR_Cleanup() call, we need an implementation
 * of the atomic routines that doesn't need NSPR to be initialized.
 */

/*
 * We use a set of locks for all the emulated atomic operations.
 * By hashing on the address of the integer to be locked the
 * contention between multiple threads should be lessened.
 *
 * The number of atomic locks can be set by the environment variable
 * NSPR_ATOMIC_HASH_LOCKS
 */

/*
 * lock counts should be a power of 2
 */
#    define DEFAULT_ATOMIC_LOCKS                              \
      16 /* should be in sync with the number of initializers \
             below */
#    define MAX_ATOMIC_LOCKS (4 * 1024)

static pthread_mutex_t static_atomic_locks[DEFAULT_ATOMIC_LOCKS] = {
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER,
    PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER};

#    ifdef DEBUG
static PRInt32 static_hash_lock_counts[DEFAULT_ATOMIC_LOCKS];
static PRInt32* hash_lock_counts = static_hash_lock_counts;
#    endif

static PRUint32 num_atomic_locks = DEFAULT_ATOMIC_LOCKS;
static pthread_mutex_t* atomic_locks = static_atomic_locks;
static PRUint32 atomic_hash_mask = DEFAULT_ATOMIC_LOCKS - 1;

#    define _PR_HASH_FOR_LOCK(ptr)                                       \
      ((PRUint32)(((PRUptrdiff)(ptr) >> 2) ^ ((PRUptrdiff)(ptr) >> 8)) & \
       atomic_hash_mask)

void _PR_MD_INIT_ATOMIC() {
  char* eval;
  int index;

  PR_ASSERT(PR_FloorLog2(MAX_ATOMIC_LOCKS) == PR_CeilingLog2(MAX_ATOMIC_LOCKS));

  PR_ASSERT(PR_FloorLog2(DEFAULT_ATOMIC_LOCKS) ==
            PR_CeilingLog2(DEFAULT_ATOMIC_LOCKS));

  if (((eval = getenv("NSPR_ATOMIC_HASH_LOCKS")) != NULL) &&
      ((num_atomic_locks = atoi(eval)) != DEFAULT_ATOMIC_LOCKS)) {
    if (num_atomic_locks > MAX_ATOMIC_LOCKS) {
      num_atomic_locks = MAX_ATOMIC_LOCKS;
    } else if (num_atomic_locks < 1) {
      num_atomic_locks = 1;
    } else {
      num_atomic_locks = PR_FloorLog2(num_atomic_locks);
      num_atomic_locks = 1L << num_atomic_locks;
    }
    atomic_locks =
        (pthread_mutex_t*)PR_Malloc(sizeof(pthread_mutex_t) * num_atomic_locks);
    if (atomic_locks) {
      for (index = 0; index < num_atomic_locks; index++) {
        if (pthread_mutex_init(&atomic_locks[index], NULL)) {
          PR_DELETE(atomic_locks);
          atomic_locks = NULL;
          break;
        }
      }
    }
#    ifdef DEBUG
    if (atomic_locks) {
      hash_lock_counts = PR_CALLOC(num_atomic_locks * sizeof(PRInt32));
      if (hash_lock_counts == NULL) {
        PR_DELETE(atomic_locks);
        atomic_locks = NULL;
      }
    }
#    endif
    if (atomic_locks == NULL) {
      /*
       *  Use statically allocated locks
       */
      atomic_locks = static_atomic_locks;
      num_atomic_locks = DEFAULT_ATOMIC_LOCKS;
#    ifdef DEBUG
      hash_lock_counts = static_hash_lock_counts;
#    endif
    }
    atomic_hash_mask = num_atomic_locks - 1;
  }
  PR_ASSERT(PR_FloorLog2(num_atomic_locks) == PR_CeilingLog2(num_atomic_locks));
}

PRInt32 _PR_MD_ATOMIC_INCREMENT(PRInt32* val) {
  PRInt32 rv;
  PRInt32 idx = _PR_HASH_FOR_LOCK(val);

  pthread_mutex_lock(&atomic_locks[idx]);
  rv = ++(*val);
#    ifdef DEBUG
  hash_lock_counts[idx]++;
#    endif
  pthread_mutex_unlock(&atomic_locks[idx]);
  return rv;
}

PRInt32 _PR_MD_ATOMIC_ADD(PRInt32* ptr, PRInt32 val) {
  PRInt32 rv;
  PRInt32 idx = _PR_HASH_FOR_LOCK(ptr);

  pthread_mutex_lock(&atomic_locks[idx]);
  rv = ((*ptr) += val);
#    ifdef DEBUG
  hash_lock_counts[idx]++;
#    endif
  pthread_mutex_unlock(&atomic_locks[idx]);
  return rv;
}

PRInt32 _PR_MD_ATOMIC_DECREMENT(PRInt32* val) {
  PRInt32 rv;
  PRInt32 idx = _PR_HASH_FOR_LOCK(val);

  pthread_mutex_lock(&atomic_locks[idx]);
  rv = --(*val);
#    ifdef DEBUG
  hash_lock_counts[idx]++;
#    endif
  pthread_mutex_unlock(&atomic_locks[idx]);
  return rv;
}

PRInt32 _PR_MD_ATOMIC_SET(PRInt32* val, PRInt32 newval) {
  PRInt32 rv;
  PRInt32 idx = _PR_HASH_FOR_LOCK(val);

  pthread_mutex_lock(&atomic_locks[idx]);
  rv = *val;
  *val = newval;
#    ifdef DEBUG
  hash_lock_counts[idx]++;
#    endif
  pthread_mutex_unlock(&atomic_locks[idx]);
  return rv;
}
#  else  /* _PR_PTHREADS */
/*
 * We use a single lock for all the emulated atomic operations.
 * The lock contention should be acceptable.
 */
static PRLock* atomic_lock = NULL;
void _PR_MD_INIT_ATOMIC(void) {
  if (atomic_lock == NULL) {
    atomic_lock = PR_NewLock();
  }
}

PRInt32 _PR_MD_ATOMIC_INCREMENT(PRInt32* val) {
  PRInt32 rv;

  if (!_pr_initialized) {
    _PR_ImplicitInitialization();
  }
  PR_Lock(atomic_lock);
  rv = ++(*val);
  PR_Unlock(atomic_lock);
  return rv;
}

PRInt32 _PR_MD_ATOMIC_ADD(PRInt32* ptr, PRInt32 val) {
  PRInt32 rv;

  if (!_pr_initialized) {
    _PR_ImplicitInitialization();
  }
  PR_Lock(atomic_lock);
  rv = ((*ptr) += val);
  PR_Unlock(atomic_lock);
  return rv;
}

PRInt32 _PR_MD_ATOMIC_DECREMENT(PRInt32* val) {
  PRInt32 rv;

  if (!_pr_initialized) {
    _PR_ImplicitInitialization();
  }
  PR_Lock(atomic_lock);
  rv = --(*val);
  PR_Unlock(atomic_lock);
  return rv;
}

PRInt32 _PR_MD_ATOMIC_SET(PRInt32* val, PRInt32 newval) {
  PRInt32 rv;

  if (!_pr_initialized) {
    _PR_ImplicitInitialization();
  }
  PR_Lock(atomic_lock);
  rv = *val;
  *val = newval;
  PR_Unlock(atomic_lock);
  return rv;
}
#  endif /* _PR_PTHREADS */

#endif /* !_PR_HAVE_ATOMIC_OPS */

void _PR_InitAtomic(void) { _PR_MD_INIT_ATOMIC(); }

PR_IMPLEMENT(PRInt32)
PR_AtomicIncrement(PRInt32* val) { return _PR_MD_ATOMIC_INCREMENT(val); }

PR_IMPLEMENT(PRInt32)
PR_AtomicDecrement(PRInt32* val) { return _PR_MD_ATOMIC_DECREMENT(val); }

PR_IMPLEMENT(PRInt32)
PR_AtomicSet(PRInt32* val, PRInt32 newval) {
  return _PR_MD_ATOMIC_SET(val, newval);
}

PR_IMPLEMENT(PRInt32)
PR_AtomicAdd(PRInt32* ptr, PRInt32 val) { return _PR_MD_ATOMIC_ADD(ptr, val); }
/*
 * For platforms, which don't support the CAS (compare-and-swap) instruction
 * (or an equivalent), the stack operations are implemented by use of PRLock
 */

PR_IMPLEMENT(PRStack*)
PR_CreateStack(const char* stack_name) {
  PRStack* stack;

  if (!_pr_initialized) {
    _PR_ImplicitInitialization();
  }

  if ((stack = PR_NEW(PRStack)) == NULL) {
    return NULL;
  }
  if (stack_name) {
    stack->prstk_name = (char*)PR_Malloc(strlen(stack_name) + 1);
    if (stack->prstk_name == NULL) {
      PR_DELETE(stack);
      return NULL;
    }
    strcpy(stack->prstk_name, stack_name);
  } else {
    stack->prstk_name = NULL;
  }

#ifndef _PR_HAVE_ATOMIC_CAS
  stack->prstk_lock = PR_NewLock();
  if (stack->prstk_lock == NULL) {
    PR_Free(stack->prstk_name);
    PR_DELETE(stack);
    return NULL;
  }
#endif /* !_PR_HAVE_ATOMIC_CAS */

  stack->prstk_head.prstk_elem_next = NULL;

  return stack;
}

PR_IMPLEMENT(PRStatus)
PR_DestroyStack(PRStack* stack) {
  if (stack->prstk_head.prstk_elem_next != NULL) {
    PR_SetError(PR_INVALID_STATE_ERROR, 0);
    return PR_FAILURE;
  }

  if (stack->prstk_name) {
    PR_Free(stack->prstk_name);
  }
#ifndef _PR_HAVE_ATOMIC_CAS
  PR_DestroyLock(stack->prstk_lock);
#endif /* !_PR_HAVE_ATOMIC_CAS */
  PR_DELETE(stack);

  return PR_SUCCESS;
}

#ifndef _PR_HAVE_ATOMIC_CAS

PR_IMPLEMENT(void)
PR_StackPush(PRStack* stack, PRStackElem* stack_elem) {
  PR_Lock(stack->prstk_lock);
  stack_elem->prstk_elem_next = stack->prstk_head.prstk_elem_next;
  stack->prstk_head.prstk_elem_next = stack_elem;
  PR_Unlock(stack->prstk_lock);
  return;
}

PR_IMPLEMENT(PRStackElem*)
PR_StackPop(PRStack* stack) {
  PRStackElem* element;

  PR_Lock(stack->prstk_lock);
  element = stack->prstk_head.prstk_elem_next;
  if (element != NULL) {
    stack->prstk_head.prstk_elem_next = element->prstk_elem_next;
    element->prstk_elem_next = NULL; /* debugging aid */
  }
  PR_Unlock(stack->prstk_lock);
  return element;
}
#endif /* !_PR_HAVE_ATOMIC_CAS */
