/*
 * \brief  Testing thread library
 * \author Alexander Boettcher
 * \author Christian Helmuth
 * \date   2013-12-13
 */

/*
 * Copyright (C) 2013-2017 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

/* Genode includes */
#include <base/log.h>
#include <base/thread.h>
#include <base/component.h>
#include <base/heap.h>
#include <base/attached_rom_dataspace.h>
#include <util/reconstructible.h>
#include <cpu_session/connection.h>
#include <cpu_thread/client.h>
#include <cpu/memory_barrier.h>

using namespace Genode;


enum { STACK_SIZE = 0x3000 };

/*********************************
 ** Stack-allocator concurrency **
 *********************************/

template <int CHILDREN>
class Helper : Thread
{
	private:

		/*
		 * Noncopyable
		 */
		Helper(Helper const &);
		Helper &operator = (Helper const &);

	public:

		using Thread::start;
		using Thread::join;

		void *child[CHILDREN];

		Env &_env;

		Helper(Env &env) : Thread(env, "helper", STACK_SIZE), _env(env) { }

		void *stack() const { return _stack; }

		void entry() override
		{
			Constructible<Helper> helper[CHILDREN];

			for (unsigned i = 0; i < CHILDREN; ++i)
				helper[i].construct(_env);

			for (unsigned i = 0; i < CHILDREN; ++i)
				child[i] = helper[i]->stack();
		}
};


static void test_stack_alloc(Env &env)
{
	log("running '", __func__, "'");

	/*
	 * Create HELPER threads, which concurrently create CHILDREN threads each.
	 * This most likely triggers any race in the stack allocation.
	 */
	enum { HELPER = 10, CHILDREN = 9 };

	Constructible<Helper<CHILDREN> > helper[HELPER];

	for (unsigned i = 0; i < HELPER; ++i) helper[i].construct(env);
	for (unsigned i = 0; i < HELPER; ++i) helper[i]->start();
	for (unsigned i = 0; i < HELPER; ++i) helper[i]->join();

	if (0)
		for (unsigned i = 0; i < HELPER; ++i)
			for (unsigned j = 0; j < CHILDREN; ++j)
				log(helper[i]->child[j], " [", i, ".", j, "]");
}


/*********************
 ** Stack alignment **
 *********************/

/*
 * Aligned FPU instruction accesses are very useful to identify stack-alignment
 * issues. Fortunately, GCC generates pushes of FPU register content for
 * vararg functions if floating-point values are passed to the function.
 */

static void test_stack_alignment_varargs(char const *format, ...) __attribute__((noinline));
static void test_stack_alignment_varargs(char const *format, ...)
{
	va_list list;
	va_start(list, format);
	log(va_arg(list, double));
	log(va_arg(list, double));
	va_end(list);
}


static void log_stack_address(char const *who)
{
	long dummy;
	log(who, " stack @ ", &dummy);
}


struct Stack_helper : Thread
{
	Stack_helper(Env &env) : Thread(env, "stack_helper", STACK_SIZE) { }

	void entry() override
	{
		log_stack_address("helper");
		test_stack_alignment_varargs("%f\n%g\n", 3.142, 2.718);
	}
};


static void test_stack_alignment(Env &env)
{
	log("running '", __func__, "'");

	Stack_helper helper(env);

	helper.start();
	helper.join();

	log_stack_address("main");
	test_stack_alignment_varargs("%f\n%g\n", 3.142, 2.718);
}


/****************************
 ** Main-thread stack area **
 ****************************/

static void test_main_thread()
{
	log("running '", __func__, "'");

	/* check wether my thread object exists */
	Thread * myself = Thread::myself();
	if (!myself) { throw -1; }
	log("thread base          ", myself);

	/* check whether my stack is inside the first stack region */
	addr_t const stack_slot_base = Thread::stack_area_virtual_base();
	addr_t const stack_slot_size = Thread::stack_area_virtual_size();
	addr_t const stack_slot_top  = stack_slot_base + stack_slot_size;

	addr_t const stack_top  = (addr_t)myself->stack_top();
	addr_t const stack_base = (addr_t)myself->stack_base();

	if (stack_top  <= stack_slot_base) { throw -2; }
	if (stack_top  >  stack_slot_top)  { throw -3; }
	if (stack_base >= stack_slot_top)  { throw -4; }
	if (stack_base <  stack_slot_base) { throw -5; }

	log("thread stack top     ", myself->stack_top());
	log("thread stack bottom  ", myself->stack_base());

	/* check wether my stack pointer is inside my stack */
	unsigned dummy = 0;
	addr_t const sp = (addr_t)&dummy;
	if (sp >= stack_top)  { throw -6; }
	if (sp <  stack_base) { throw -7; }
	log("thread stack pointer ", (void *)sp);
}


/******************************************
 ** Using cpu-session for thread creation *
 ******************************************/

struct Cpu_helper : Thread
{
	Env &_env;

	Cpu_helper(Env &env, const char * name, Cpu_session &cpu)
	:
		Thread(env, name, STACK_SIZE, Thread::Location(), Thread::Weight(), cpu),
		_env(env)
	{ }

	void entry() override
	{
		log(Thread::name().string(), " : _cpu_session=", _cpu_session,
		    " env.cpu()=", &_env.cpu());
	}
};


static void test_cpu_session(Env &env)
{
	log("running '", __func__, "'");

	Cpu_helper thread0(env, "prio high  ", env.cpu());
	thread0.start();
	thread0.join();

	Cpu_connection con1(env, "prio middle", Cpu_session::PRIORITY_LIMIT / 4);
	Cpu_helper thread1(env, "prio middle", con1);
	thread1.start();
	thread1.join();

	Cpu_connection con2(env, "prio low", Cpu_session::PRIORITY_LIMIT / 2);
	Cpu_helper thread2(env, "prio low   ", con2);
	thread2.start();
	thread2.join();
}


struct Pause_helper : Thread
{
	volatile unsigned loop = 0;
	volatile bool beep = false;

	Pause_helper(Env &env, const char * name, Cpu_session &cpu)
	: Thread(env, name, STACK_SIZE, Thread::Location(), Thread::Weight(), cpu) { }

	void entry() override
	{
		while (1) {
			/*
			 * Don't log here, since this thread becomes "paused".
			 * If it is holding the lock of the log backend being paused, all
			 * other threads of this task trying to print log messages will
			 * block - looks like a deadlock.
			 */
			loop ++;
			if (beep) {
				log("beep");
				beep = false;
				loop ++;
				return;
			}
		}
	}
};


static void test_pause_resume(Env &env)
{
	log("running '", __func__, "'");

	Pause_helper thread(env, "pause", env.cpu());
	thread.start();

	while (thread.loop < 1) { }

	Thread_state state;
	Cpu_thread_client thread_client(thread.cap());

	log("--- pausing ---");
	thread_client.pause();
	unsigned loop_paused = thread.loop;
	log("--- paused ---");

	log("--- reading thread state ---");
	try {
		state = thread_client.state();
	} catch (Cpu_thread::State_access_failed) {
		throw -10;
	}
	if (loop_paused != thread.loop)
		throw -11;

	thread.beep = true;
	log("--- resuming thread ---");
	thread_client.resume();

	while (thread.loop == loop_paused) { }

	log("--- thread resumed ---");
	thread.join();
}


/*
 * Test to check that core as the used kernel behaves well if up to the
 * supported Genode maximum threads are created.
 */
static void test_create_as_many_threads(Env &env)
{
	log("running '", __func__, "'");

	addr_t const max = Thread::stack_area_virtual_size() /
	                   Thread::stack_virtual_size();

	Cpu_helper * threads[max];
	static char thread_name[8];

	Heap heap(env.ram(), env.rm());

	unsigned i = 0;
	try {
		for (; i < max; i++) {
			try {
				snprintf(thread_name, sizeof(thread_name), "%u", i + 1);
				threads[i] = new (heap) Cpu_helper(env, thread_name, env.cpu());
				threads[i]->start();
				threads[i]->join();
			} catch (Cpu_session::Thread_creation_failed) {
				throw "Thread_creation_failed";
			} catch (Thread::Out_of_stack_space) {
				throw "Out_of_stack_space";
			} catch (Genode::Native_capability::Reference_count_overflow) {
				throw "Native_capability::Reference_count_overflow";
			}
		}
	} catch (const char * ex) {
		log("created ", i, " threads before I got '", ex, "'");
		for (unsigned j = i; j > 0; j--) {
			destroy(heap, threads[j - 1]);
			threads[j - 1] = nullptr;
		}
		return;
	}

	/*
	 * We have to get a Out_of_stack_space message, because we can't create
	 * up to max threads, because already the main thread is running ...
	 */
	throw -21;
}


/*********************************
 ** Using Locks in creative ways *
 *********************************/

struct Lock_helper : Thread
{
	Lock &lock;
	bool &lock_is_free;
	bool  unlock;

	Lock_helper(Env &env, const char * name, Cpu_session &cpu, Lock &lock,
	            bool &lock_is_free, bool unlock = false)
	:
		Thread(env, name, STACK_SIZE, Thread::Location(), Thread::Weight(),
		       cpu),
		lock(lock), lock_is_free(lock_is_free), unlock(unlock)
	{ }

	void entry() override
	{
		log(" thread '", name(), "' started");

		if (unlock)
			lock.unlock();

		lock.lock();

		if (!lock_is_free) {
			log(" thread '", name(), "' got lock but somebody else is within"
			    " critical section !?");
			throw -22;
		}

		log(" thread '", name(), "' done");

		lock.unlock();
	}
};

static void test_locks(Genode::Env &env)
{
	Lock lock         (Lock::LOCKED);

	bool lock_is_free = true;

	Cpu_connection cpu_m(env, "prio middle", Cpu_session::PRIORITY_LIMIT / 4);
	Cpu_connection cpu_l(env, "prio low", Cpu_session::PRIORITY_LIMIT / 2);

	enum { SYNC_STARTUP = true };

	Lock_helper l1(env, "lock_low1", cpu_l, lock, lock_is_free);
	Lock_helper l2(env, "lock_low2", cpu_l, lock, lock_is_free);
	Lock_helper l3(env, "lock_low3", cpu_l, lock, lock_is_free);
	Lock_helper l4(env, "lock_low4", cpu_l, lock, lock_is_free, SYNC_STARTUP);

	l1.start();
	l2.start();
	l3.start();
	l4.start();

	lock.lock();

	log(" thread '", Thread::myself()->name(), "' - I'm the lock holder - "
	    "take lock again");

	/* we are within the critical section - lock is not free */
	lock_is_free = false;

	/* start another low prio thread to wake current thread when it blocks */
	Lock_helper l5(env, "lock_low5", cpu_l, lock, lock_is_free, SYNC_STARTUP);
	l5.start();

	log(" spin for some time");
	for (unsigned volatile i = 0; i < 8000000; ++i) memory_barrier();
	log(" still spinning");
	for (unsigned volatile i = 0; i < 8000000; ++i) memory_barrier();
	log(" spinning done");

	lock.lock();
	log(" I'm the lock holder - still alive");
	lock_is_free = true;

	lock.unlock();

	/* check that really all threads come back ! */
	l1.join();
	l2.join();
	l3.join();
	l4.join();
	l5.join();

	log("running '", __func__, "' done");
}


/**********************************
 ** Using cxa guards concurrently *
 **********************************/

struct Cxa_helper : Thread
{
	Lock &in_cxa;
	Lock &sync_startup;
	int   test;
	bool  sync;

	Cxa_helper(Env &env, const char * name, Cpu_session &cpu, Lock &cxa,
	           Lock &startup, int test, bool sync = false)
	:
		Thread(env, name, STACK_SIZE, Thread::Location(), Thread::Weight(),
		       cpu),
		in_cxa(cxa), sync_startup(startup), test(test), sync(sync)
	{ }

	void entry() override
	{
		log(" thread '", name(), "' started");

		if (sync)
			sync_startup.unlock();

		struct Contention {
			Contention(Name name, Lock &in_cxa, Lock &sync_startup)
			{
				log(" thread '", name, "' in static constructor");
				sync_startup.unlock();
				in_cxa.lock();
			}
		};

		if (test == 1)
			static Contention contention (name(), in_cxa, sync_startup);
		else
		if (test == 2)
			static Contention contention (name(), in_cxa, sync_startup);
		else
		if (test == 3)
			static Contention contention (name(), in_cxa, sync_startup);
		else
		if (test == 4)
			static Contention contention (name(), in_cxa, sync_startup);
		else
			throw -25;

		log(" thread '", name(), "' done");
	}
};

static void test_cxa_guards(Env &env)
{
	log("running '", __func__, "'");

	Cpu_connection cpu_m(env, "prio middle", Cpu_session::PRIORITY_LIMIT / 4);
	Cpu_connection cpu_l(env, "prio low", Cpu_session::PRIORITY_LIMIT / 2);

	{
		enum { TEST_1ST = 1 };

		Lock in_cxa       (Lock::LOCKED);
		Lock sync_startup (Lock::LOCKED);

		/* start low priority thread */
		Cxa_helper cxa_l(env, "cxa_low", cpu_l, in_cxa, sync_startup, TEST_1ST);
		cxa_l.start();

		/* wait until low priority thread is inside static variable */
		sync_startup.lock();
		sync_startup.unlock();

		/* start high priority threads */
		Cxa_helper cxa_h1(env, "cxa_high_1", env.cpu(), in_cxa, sync_startup,
		                  TEST_1ST);
		Cxa_helper cxa_h2(env, "cxa_high_2", env.cpu(), in_cxa, sync_startup,
		                  TEST_1ST);
		Cxa_helper cxa_h3(env, "cxa_high_3", env.cpu(), in_cxa, sync_startup,
		                  TEST_1ST);
		Cxa_helper cxa_h4(env, "cxa_high_4", env.cpu(), in_cxa, sync_startup,
		                  TEST_1ST);
		cxa_h1.start();
		cxa_h2.start();
		cxa_h3.start();
		cxa_h4.start();

		/* start middle priority thread */
		enum { SYNC_STARTUP = true };
		Cxa_helper cxa_m(env, "cxa_middle", cpu_m, in_cxa, sync_startup,
		                 TEST_1ST, SYNC_STARTUP);
		cxa_m.start();

		/*
		 * high priority threads are for sure in the static Contention variable,
		 * if the middle priority thread manages to sync with current
		 * (high priority) entrypoint thread
		 */
		sync_startup.lock();

		/* let's see whether we get all our threads out of the static variable */
		in_cxa.unlock();

		/* eureka ! */
		cxa_h1.join(); cxa_h2.join(); cxa_h3.join(); cxa_h4.join();
		cxa_m.join();
		cxa_l.join();
	}

	{
		enum { TEST_2ND = 2, TEST_3RD = 3, TEST_4TH = 4 };

		Lock in_cxa_2       (Lock::LOCKED);
		Lock sync_startup_2 (Lock::LOCKED);
		Lock in_cxa_3       (Lock::LOCKED);
		Lock sync_startup_3 (Lock::LOCKED);
		Lock in_cxa_4       (Lock::LOCKED);
		Lock sync_startup_4 (Lock::LOCKED);

		/* start low priority threads */
		Cxa_helper cxa_l_2(env, "cxa_low_2", cpu_l, in_cxa_2, sync_startup_2,
		                   TEST_2ND);
		Cxa_helper cxa_l_3(env, "cxa_low_3", cpu_l, in_cxa_3, sync_startup_3,
		                   TEST_3RD);
		Cxa_helper cxa_l_4(env, "cxa_low_4", cpu_l, in_cxa_4, sync_startup_4,
		                   TEST_4TH);
		cxa_l_2.start();
		cxa_l_3.start();
		cxa_l_4.start();

		/* wait until low priority threads are inside static variables */
		sync_startup_2.lock();
		sync_startup_2.unlock();
		sync_startup_3.lock();
		sync_startup_3.unlock();
		sync_startup_4.lock();
		sync_startup_4.unlock();

		/* start high priority threads */
		Cxa_helper cxa_h1_2(env, "cxa_high_1_2", env.cpu(), in_cxa_2,
		                    sync_startup_2, TEST_2ND);
		Cxa_helper cxa_h2_2(env, "cxa_high_2_2", env.cpu(), in_cxa_2,
		                    sync_startup_2, TEST_2ND);
		Cxa_helper cxa_h3_2(env, "cxa_high_3_2", env.cpu(), in_cxa_2,
		                    sync_startup_2, TEST_2ND);
		Cxa_helper cxa_h4_2(env, "cxa_high_4_2", env.cpu(), in_cxa_2,
		                    sync_startup_2, TEST_2ND);

		Cxa_helper cxa_h1_3(env, "cxa_high_1_3", env.cpu(), in_cxa_3,
		                    sync_startup_3, TEST_3RD);
		Cxa_helper cxa_h2_3(env, "cxa_high_2_3", env.cpu(), in_cxa_3,
		                    sync_startup_3, TEST_3RD);
		Cxa_helper cxa_h3_3(env, "cxa_high_3_3", env.cpu(), in_cxa_3,
		                    sync_startup_3, TEST_3RD);
		Cxa_helper cxa_h4_3(env, "cxa_high_4_3", env.cpu(), in_cxa_3,
		                    sync_startup_3, TEST_3RD);

		Cxa_helper cxa_h1_4(env, "cxa_high_1_4", env.cpu(), in_cxa_4,
		                    sync_startup_4, TEST_4TH);
		Cxa_helper cxa_h2_4(env, "cxa_high_2_4", env.cpu(), in_cxa_4,
		                    sync_startup_4, TEST_4TH);
		Cxa_helper cxa_h3_4(env, "cxa_high_3_4", env.cpu(), in_cxa_4,
		                    sync_startup_4, TEST_4TH);
		Cxa_helper cxa_h4_4(env, "cxa_high_4_4", env.cpu(), in_cxa_4,
		                    sync_startup_4, TEST_4TH);

		cxa_h1_2.start(); cxa_h1_3.start(); cxa_h1_4.start();
		cxa_h2_2.start(); cxa_h2_3.start(); cxa_h2_4.start();
		cxa_h3_2.start(); cxa_h3_3.start(); cxa_h3_4.start();
		cxa_h4_2.start(); cxa_h4_3.start(); cxa_h4_4.start();

		/* start middle priority threads */
		enum { SYNC_STARTUP = true };
		Cxa_helper cxa_m_2(env, "cxa_middle_2", cpu_m, in_cxa_2,
		                   sync_startup_2, TEST_2ND, SYNC_STARTUP);
		Cxa_helper cxa_m_3(env, "cxa_middle_3", cpu_m, in_cxa_3,
		                   sync_startup_3, TEST_3RD, SYNC_STARTUP);
		Cxa_helper cxa_m_4(env, "cxa_middle_4", cpu_m, in_cxa_4,
		                   sync_startup_4, TEST_4TH, SYNC_STARTUP);

		cxa_m_2.start();
		cxa_m_3.start();
		cxa_m_4.start();

		/*
		 * high priority threads are for sure in the static Contention
		 * variables, if the middle priority threads manage to sync with
		 * current (high priority) entrypoint thread
		 */
		sync_startup_2.lock();
		sync_startup_3.lock();
		sync_startup_4.lock();

		/* let's see whether we get all our threads out of the static variable */
		in_cxa_4.unlock();
		in_cxa_3.unlock();
		in_cxa_2.unlock();

		cxa_h1_2.join(); cxa_h2_2.join(); cxa_h3_2.join(); cxa_h4_2.join();
		cxa_m_2.join(); cxa_l_2.join();

		cxa_h1_3.join(); cxa_h2_3.join(); cxa_h3_3.join(); cxa_h4_3.join();
		cxa_m_3.join(); cxa_l_3.join();

		cxa_h1_4.join(); cxa_h2_4.join(); cxa_h3_4.join(); cxa_h4_4.join();
		cxa_m_4.join(); cxa_l_4.join();
	}
	log("running '", __func__, "' done");
}


/*********************************************
 ** Successive construction and destruction **
 *********************************************/

struct Create_destroy_helper : Thread
{
	unsigned const    result_value;
	unsigned volatile result { ~0U };

	Create_destroy_helper(Env &env, unsigned result_value)
	: Thread(env, "create_destroy", STACK_SIZE),
	  result_value(result_value)
	{ }

	void entry() override
	{
		result = result_value;
	}
};

static void test_successive_create_destroy_threads(Env &env)
{
	log("running '", __func__, "'");

	for (unsigned i = 0; i < 500; i++) {
		Create_destroy_helper thread(env, i);
		thread.start();
		thread.join();
		if (thread.result != i)
			throw -30;
	}
}


void Component::construct(Env &env)
{
	log("--- thread test started ---");

	Attached_rom_dataspace config(env, "config");

	try {
		test_stack_alloc(env);
		test_stack_alignment(env);
		test_main_thread();
		test_cpu_session(env);
		if (config.xml().attribute_value("prio", false)) {
			test_locks(env);
			test_cxa_guards(env);
		}
		if (config.xml().attribute_value("pause_resume", false))
			test_pause_resume(env);

		test_create_as_many_threads(env);
		test_successive_create_destroy_threads(env);
	} catch (int error) {
		Genode::error("error ", error);
		throw;
	}

	log("--- test completed successfully ---");
}
