//
// 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 : Peter A. Buhr
// Last Modified On : Fri Dec  8 16:02:22 2017
// Update Count     : 1
//

#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" : : : )
#elif defined( __ARM_ARCH )
	#define Pause() __asm__ __volatile__ ( "nop" : : : )
#else
	#error unsupported architecture
#endif

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

#if __SIZEOF_SIZE_T__ == 8
	#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 __SIZEOF_SIZE_T__ == 4
	#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 size_t lock;
	#ifdef __CFA_DEBUG__
		const char * prev_name;
		void* prev_thrd;
	#endif
} __ALIGN__;

#ifdef __cforall
	extern "C" {
		extern void disable_interrupts();
		extern void enable_interrupts_noPoll();
	}

	extern void yield( unsigned int );
	extern thread_local struct thread_desc *    volatile this_thread;
	extern thread_local struct processor *      volatile this_processor;

	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 );
		if( result ) {
			disable_interrupts();
			__cfaabi_dbg_debug_do(
				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
		}
		disable_interrupts();
		__cfaabi_dbg_debug_do(
			this.prev_name = caller;
			this.prev_thrd = this_thread;
		)
	}

	// // Lock the spinlock, yield 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 );
	// 	}
	// 	disable_interrupts();
	// 	__cfaabi_dbg_debug_do(
	// 		this.prev_name = caller;
	// 		this.prev_thrd = this_thread;
	// 	)
	// }

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