/******************************************************
Mutex, the basic synchronization primitive

(c) 1995 Innobase Oy

Created 9/5/1995 Heikki Tuuri
*******************************************************/

#if defined(not_defined) && defined(__GNUC__) && defined(UNIV_INTEL_X86)
/* %z0: Use the size of operand %0 which in our case is *m to determine
instruction size, it should end up as xchgl. "1" in the input constraint,
says that "in" has to go in the same place as "out".*/
#define TAS(m, in, out) \
	asm volatile ("xchg%z0 %2, %0" \
	: "=g" (*(m)), "=r" (out) \
	: "1" (in))	/* Note: "1" here refers to "=r" (out) */
#endif

/**********************************************************************
Sets the waiters field in a mutex. */
UNIV_INTERN
void
mutex_set_waiters(
/*==============*/
	mutex_t*	mutex,	/* in: mutex */
	ulint		n);	/* in: value to set */
/**********************************************************************
Reserves a mutex for the current thread. If the mutex is reserved, the
function spins a preset time (controlled by SYNC_SPIN_ROUNDS) waiting
for the mutex before suspending the thread. */
UNIV_INTERN
void
mutex_spin_wait(
/*============*/
	mutex_t*	mutex,		/* in: pointer to mutex */
	const char*	file_name,	/* in: file name where mutex
					requested */
	ulint		line);		/* in: line where requested */
#ifdef UNIV_SYNC_DEBUG
/**********************************************************************
Sets the debug information for a reserved mutex. */
UNIV_INTERN
void
mutex_set_debug_info(
/*=================*/
	mutex_t*	mutex,		/* in: mutex */
	const char*	file_name,	/* in: file where requested */
	ulint		line);		/* in: line where requested */
#endif /* UNIV_SYNC_DEBUG */
/**********************************************************************
Releases the threads waiting in the primary wait array for this mutex. */
UNIV_INTERN
void
mutex_signal_object(
/*================*/
	mutex_t*	mutex);	/* in: mutex */

/**********************************************************************
Performs an atomic test-and-set instruction to the lock_word field of a
mutex. */
UNIV_INLINE
ulint
mutex_test_and_set(
/*===============*/
				/* out: the previous value of lock_word: 0 or
				1 */
	mutex_t*	mutex)	/* in: mutex */
{
#if defined(_WIN32) && defined(UNIV_CAN_USE_X86_ASSEMBLER)
	ulint	res;
	ulint*	lw;		/* assembler code is used to ensure that
				lock_word is loaded from memory */
	ut_ad(mutex);
	ut_ad(sizeof(ulint) == 4);

	lw = &(mutex->lock_word);

	__asm	MOV	ECX, lw
		__asm	MOV	EDX, 1
		__asm	XCHG	EDX, DWORD PTR [ECX]
		__asm	MOV	res, EDX

		/* The fence below would prevent this thread from
		reading the data structure protected by the mutex
		before the test-and-set operation is committed, but
		the fence is apparently not needed:

		In a posting to comp.arch newsgroup (August 10, 1997)
		Andy Glew said that in P6 a LOCKed instruction like
		XCHG establishes a fence with respect to memory reads
		and writes and thus an explicit fence is not
		needed. In P5 he seemed to agree with a previous
		newsgroup poster that LOCKed instructions serialize
		all instruction execution, and, consequently, also
		memory operations. This is confirmed in Intel Software
		Dev. Manual, Vol. 3. */

		/* mutex_fence(); */

		return(res);
#elif defined(not_defined) && defined(__GNUC__) && defined(UNIV_INTEL_X86)
	ulint	res;

	TAS(&mutex->lock_word, 1, res);

	return(res);
#else
	ibool	ret;

	ret = os_fast_mutex_trylock(&(mutex->os_fast_mutex));

	if (ret == 0) {
		/* We check that os_fast_mutex_trylock does not leak
		and allow race conditions */
		ut_a(mutex->lock_word == 0);

		mutex->lock_word = 1;
	}

	return(ret);
#endif
}

/**********************************************************************
Performs a reset instruction to the lock_word field of a mutex. This
instruction also serializes memory operations to the program order. */
UNIV_INLINE
void
mutex_reset_lock_word(
/*==================*/
	mutex_t*	mutex)	/* in: mutex */
{
#if defined(_WIN32) && defined(UNIV_CAN_USE_X86_ASSEMBLER)
	ulint*	lw;		/* assembler code is used to ensure that
				lock_word is loaded from memory */
	ut_ad(mutex);

	lw = &(mutex->lock_word);

	__asm	MOV	EDX, 0
		__asm	MOV	ECX, lw
		__asm	XCHG	EDX, DWORD PTR [ECX]
#elif defined(not_defined) && defined(__GNUC__) && defined(UNIV_INTEL_X86)
	ulint	res;

	TAS(&mutex->lock_word, 0, res);
#else
	mutex->lock_word = 0;

	os_fast_mutex_unlock(&(mutex->os_fast_mutex));
#endif
}

/**********************************************************************
Gets the value of the lock word. */
UNIV_INLINE
ulint
mutex_get_lock_word(
/*================*/
	const mutex_t*	mutex)	/* in: mutex */
{
	const volatile ulint*	ptr;	/* declared volatile to ensure that
					lock_word is loaded from memory */
	ut_ad(mutex);

	ptr = &(mutex->lock_word);

	return(*ptr);
}

/**********************************************************************
Gets the waiters field in a mutex. */
UNIV_INLINE
ulint
mutex_get_waiters(
/*==============*/
				/* out: value to set */
	const mutex_t*	mutex)	/* in: mutex */
{
	const volatile ulint*	ptr;	/* declared volatile to ensure that
					the value is read from memory */
	ut_ad(mutex);

	ptr = &(mutex->waiters);

	return(*ptr);		/* Here we assume that the read of a single
				word from memory is atomic */
}

/**********************************************************************
Unlocks a mutex owned by the current thread. */
UNIV_INLINE
void
mutex_exit(
/*=======*/
	mutex_t*	mutex)	/* in: pointer to mutex */
{
	ut_ad(mutex_own(mutex));

	ut_d(mutex->thread_id = (os_thread_id_t) ULINT_UNDEFINED);

#ifdef UNIV_SYNC_DEBUG
	sync_thread_reset_level(mutex);
#endif
	mutex_reset_lock_word(mutex);

	/* A problem: we assume that mutex_reset_lock word
	is a memory barrier, that is when we read the waiters
	field next, the read must be serialized in memory
	after the reset. A speculative processor might
	perform the read first, which could leave a waiting
	thread hanging indefinitely.

	Our current solution call every second
	sync_arr_wake_threads_if_sema_free()
	to wake up possible hanging threads if
	they are missed in mutex_signal_object. */

	if (mutex_get_waiters(mutex) != 0) {

		mutex_signal_object(mutex);
	}

#ifdef UNIV_SYNC_PERF_STAT
	mutex_exit_count++;
#endif
}

/**********************************************************************
Locks a mutex for the current thread. If the mutex is reserved, the function
spins a preset time (controlled by SYNC_SPIN_ROUNDS), waiting for the mutex
before suspending the thread. */
UNIV_INLINE
void
mutex_enter_func(
/*=============*/
	mutex_t*	mutex,		/* in: pointer to mutex */
	const char*	file_name,	/* in: file name where locked */
	ulint		line)		/* in: line where locked */
{
	ut_ad(mutex_validate(mutex));
	ut_ad(!mutex_own(mutex));

	/* Note that we do not peek at the value of lock_word before trying
	the atomic test_and_set; we could peek, and possibly save time. */

#if defined UNIV_DEBUG && !defined UNIV_HOTBACKUP
	mutex->count_using++;
#endif /* UNIV_DEBUG && !UNIV_HOTBACKUP */

	if (!mutex_test_and_set(mutex)) {
		ut_d(mutex->thread_id = os_thread_get_curr_id());
#ifdef UNIV_SYNC_DEBUG
		mutex_set_debug_info(mutex, file_name, line);
#endif
		return;	/* Succeeded! */
	}

	mutex_spin_wait(mutex, file_name, line);
}