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

(c) 1995 Innobase Oy

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

/**********************************************************************
Sets the waiters field in a mutex. */

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. */

void
mutex_spin_wait(
/*============*/
        mutex_t*	mutex,  	/* in: pointer to mutex */
	char*		file_name,/* in: file name where mutex requested */
	ulint		line);	/* in: line where requested */
/**********************************************************************
Sets the debug information for a reserved mutex. */

void
mutex_set_debug_info(
/*=================*/
	mutex_t*	mutex,		/* in: mutex */
	char*		file_name,	/* in: file where requested */
	ulint		line);		/* in: line where requested */
/**********************************************************************
Releases the threads waiting in the primary wait array for this mutex. */

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*	lw;
	ulint	res;

	lw = &(mutex->lock_word);

	/* In assembly we use the so-called AT & T syntax where
	the order of operands is inverted compared to the ordinary Intel
	syntax. The 'l' after the mnemonics denotes a 32-bit operation.
	The line after the code tells which values come out of the asm
	code, and the second line tells the input to the asm code. */

	asm volatile("movl $1, %%eax; xchgl (%%ecx), %%eax" :
	              "=eax" (res), "=m" (*lw) :
	              "ecx" (lw));
	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*	lw;

	lw = &(mutex->lock_word);

	/* In assembly we use the so-called AT & T syntax where
	the order of operands is inverted compared to the ordinary Intel
	syntax. The 'l' after the mnemonics denotes a 32-bit operation. */

	asm volatile("movl $0, %%eax; xchgl (%%ecx), %%eax" :
	              "=m" (*lw) :
	              "ecx" (lw) :
		      "eax");	/* gcc does not seem to understand
				that our asm code resets eax: tell it
				explicitly that after the third ':' */
#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(
/*================*/
	mutex_t*	mutex)	/* in: mutex */
{
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 */		
	mutex_t*	mutex)	/* in: mutex */
{
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));

#ifdef UNIV_SYNC_DEBUG
	mutex->thread_id = ULINT_UNDEFINED;

	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 10 seconds
	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 */
	char*		file_name,/* in: file name where locked */
	ulint		line)	/* in: line where locked */
{
	ut_ad(mutex_validate(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 (!mutex_test_and_set(mutex)) {

		#ifdef UNIV_SYNC_DEBUG
		mutex_set_debug_info(mutex, file_name, line);
		#endif

		mutex->file_name = file_name;
		mutex->line = line;

		return;	/* Succeeded! */
	}

	mutex_spin_wait(mutex, file_name, line);
}
