//
// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
//
// The contents of this file are covered under the licence agreement in the
// file "LICENCE" distributed with Cforall.
//
// bits/locks.h -- Fast internal locks.
//
// Author           : Thierry Delisle
// Created On       : Tue Oct 31 15:14:38 2017
// Last Modified By : --
// Last Modified On : --
// Update Count     : 0
//

#pragma once

#include "bits/debug.h"
#include "bits/defs.h"

// pause to prevent excess processor bus usage
#if defined( __sparc )
	#define Pause() __asm__ __volatile__ ( "rd %ccr,%g0" )
#elif defined( __i386 ) || defined( __x86_64 )
	#define Pause() __asm__ __volatile__ ( "pause" : : : )
#else
	#error unsupported architecture
#endif

#if defined( __i386 ) || defined( __x86_64 )
	// Intel recommendation
	#define __ALIGN__ __attribute__(( aligned (128) ))
#elif defined( __sparc )
	#define __ALIGN__ CALIGN
#else
	#error unsupported architecture
#endif

#if defined( __x86_64 )
	#define __lock_test_and_test_and_set( lock ) (lock) == 0 && __sync_lock_test_and_set_8( &(lock), 1 ) == 0
	#define __lock_release( lock ) __sync_lock_release_8( &(lock) );
#elif defined( __i386 )
	#define __lock_test_and_test_and_set( lock ) (lock) == 0 && __sync_lock_test_and_set_4( &(lock), 1 ) == 0
	#define __lock_release( lock ) __sync_lock_release_4( &(lock) );
#else
	#error unsupported architecture
#endif

struct __spinlock_t {
	__ALIGN__ volatile uintptr_t lock;
	#ifdef __CFA_DEBUG__
		const char * prev_name;
		void* prev_thrd;
	#endif
} __ALIGN__;

#ifdef __cforall
	extern void yield( unsigned int );
	extern thread_local struct thread_desc *    volatile this_thread;

	static inline void ?{}( __spinlock_t & this ) {
		this.lock = 0;
	}

	// Lock the spinlock, return false if already acquired
	static inline _Bool try_lock  ( __spinlock_t & this __cfaabi_dbg_ctx_param2 ) {
		_Bool result = __lock_test_and_test_and_set( this.lock );
		__cfaabi_dbg_debug_do(
			if( result ) {
				this.prev_name = caller;
				this.prev_thrd = this_thread;
			}
		)
		return result;
	}

	// Lock the spinlock, spin if already acquired
	static inline void lock( __spinlock_t & this __cfaabi_dbg_ctx_param2 ) {
		#ifndef NOEXPBACK
			enum { SPIN_START = 4, SPIN_END = 64 * 1024, };
			unsigned int spin = SPIN_START;
		#endif

		for ( unsigned int i = 1;; i += 1 ) {
			if ( __lock_test_and_test_and_set( this.lock ) ) break;
			#ifndef NOEXPBACK
				// exponential spin
				for ( volatile unsigned int s = 0; s < spin; s += 1 ) Pause();

				// slowly increase by powers of 2
				if ( i % 64 == 0 ) spin += spin;

				// prevent overflow
				if ( spin > SPIN_END ) spin = SPIN_START;
			#else
				Pause();
			#endif
		}
		__cfaabi_dbg_debug_do(
			this.prev_name = caller;
			this.prev_thrd = this_thread;
		)
	}

	// Lock the spinlock, spin if already acquired
	static inline void lock_yield( __spinlock_t & this __cfaabi_dbg_ctx_param2 ) {
		for ( unsigned int i = 1;; i += 1 ) {
			if ( __lock_test_and_test_and_set( this.lock ) ) break;
			yield( i );
		}
		__cfaabi_dbg_debug_do(
			this.prev_name = caller;
			this.prev_thrd = this_thread;
		)
	}

	static inline void unlock( __spinlock_t & this ) {
		__lock_release( this.lock );
	}
#endif