//
// 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.
//
// coroutine.c --
//
// Author           : Thierry Delisle
// Created On       : Mon Nov 28 12:27:26 2016
// Last Modified By : Peter A. Buhr
// Last Modified On : Thu Feb  8 16:10:31 2018
// Update Count     : 4
//

#include "coroutine"

extern "C" {
#include <stddef.h>
#include <malloc.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
}

#include "kernel_private.h"

#define __CFA_INVOKE_PRIVATE__
#include "invoke.h"

//-----------------------------------------------------------------------------
// Global state variables

// minimum feasible stack size in bytes
#define MinStackSize 1000
static size_t pageSize = 0;				// architecture pagesize HACK, should go in proper runtime singleton

//-----------------------------------------------------------------------------
// Coroutine ctors and dtors
void ?{}(coStack_t& this) with( this ) {
	size		= 65000;	// size of stack
	storage	= NULL;	// pointer to stack
	limit		= NULL;	// stack grows towards stack limit
	base		= NULL;	// base of stack
	context	= NULL;	// address of cfa_context_t
	top		= NULL;	// address of top of storage
	userStack	= false;
}

void ?{}(coStack_t& this, size_t size) {
	this{};
	this.size = size;

	create_stack(&this, this.size);
}

void ?{}(coroutine_desc& this) {
	this{ "Anonymous Coroutine" };
}

void ?{}(coroutine_desc& this, const char * name) with( this ) {
	this.name = name;
	errno_ = 0;
	state = Start;
	starter = NULL;
	last = NULL;
}

void ?{}(coroutine_desc& this, size_t size) {
	this{};
	(this.stack){size};
}

void ^?{}(coStack_t & this) {
	if ( ! this.userStack && this.storage ) {
		__cfaabi_dbg_debug_do(
			if ( mprotect( this.storage, pageSize, PROT_READ | PROT_WRITE ) == -1 ) {
				abort( "(coStack_t *)%p.^?{}() : internal error, mprotect failure, error(%d) %s.", &this, errno, strerror( errno ) );
			}
		);
		free( this.storage );
	}
}

void ^?{}(coroutine_desc& this) {}

// Part of the Public API
// Not inline since only ever called once per coroutine
forall(dtype T | is_coroutine(T))
void prime(T& cor) {
	coroutine_desc* this = get_coroutine(cor);
	assert(this->state == Start);

	this->state = Primed;
	resume(cor);
}

// Wrapper for co
void CoroutineCtxSwitch(coroutine_desc* src, coroutine_desc* dst) {
	verify( preemption_state.enabled || this_processor->do_terminate );
	disable_interrupts();

	// set state of current coroutine to inactive
	src->state = src->state == Halted ? Halted : Inactive;

	// set new coroutine that task is executing
	this_coroutine = dst;

	// context switch to specified coroutine
	assert( src->stack.context );
	CtxSwitch( src->stack.context, dst->stack.context );
	// when CtxSwitch returns we are back in the src coroutine

	// set state of new coroutine to active
	src->state = Active;

	enable_interrupts( __cfaabi_dbg_ctx );
	verify( preemption_state.enabled || this_processor->do_terminate );
} //ctxSwitchDirect

void create_stack( coStack_t* this, unsigned int storageSize ) with( *this ) {
	//TEMP HACK do this on proper kernel startup
	if(pageSize == 0ul) pageSize = sysconf( _SC_PAGESIZE );

	size_t cxtSize = libCeiling( sizeof(machine_context_t), 8 ); // minimum alignment

	if ( (intptr_t)storage == 0 ) {
		userStack = false;
		size = libCeiling( storageSize, 16 );
		// use malloc/memalign because "new" raises an exception for out-of-memory

		// assume malloc has 8 byte alignment so add 8 to allow rounding up to 16 byte alignment
		__cfaabi_dbg_debug_do( storage = memalign( pageSize, cxtSize + size + pageSize ) );
		__cfaabi_dbg_no_debug_do( storage = malloc( cxtSize + size + 8 ) );

		__cfaabi_dbg_debug_do(
			if ( mprotect( storage, pageSize, PROT_NONE ) == -1 ) {
				abort( "(uMachContext &)%p.createContext() : internal error, mprotect failure, error(%d) %s.", this, (int)errno, strerror( (int)errno ) );
			} // if
		);

		if ( (intptr_t)storage == 0 ) {
			abort( "Attempt to allocate %zd bytes of storage for coroutine or task execution-state but insufficient memory available.", size );
		} // if

		__cfaabi_dbg_debug_do( limit = (char *)storage + pageSize );
		__cfaabi_dbg_no_debug_do( limit = (char *)libCeiling( (unsigned long)storage, 16 ) ); // minimum alignment

	} else {
		assertf( ((size_t)storage & (libAlign() - 1)) != 0ul, "Stack storage %p for task/coroutine must be aligned on %d byte boundary.", storage, (int)libAlign() );
		userStack = true;
		size = storageSize - cxtSize;

		if ( size % 16 != 0u ) size -= 8;

		limit = (char *)libCeiling( (unsigned long)storage, 16 ); // minimum alignment
	} // if
	assertf( size >= MinStackSize, "Stack size %zd provides less than minimum of %d bytes for a stack.", size, MinStackSize );

	base = (char *)limit + size;
	context = base;
	top = (char *)context + cxtSize;
}

// We need to call suspend from invoke.c, so we expose this wrapper that
// is not inline (We can't inline Cforall in C)
extern "C" {
	void __suspend_internal(void) {
		suspend();
	}

	void __leave_coroutine(void) {
		coroutine_desc * src = this_coroutine;		// optimization

		assertf( src->starter != 0,
			"Attempt to suspend/leave coroutine \"%.256s\" (%p) that has never been resumed.\n"
			"Possible cause is a suspend executed in a member called by a coroutine user rather than by the coroutine main.",
			src->name, src );
		assertf( src->starter->state != Halted,
			"Attempt by coroutine \"%.256s\" (%p) to suspend/leave back to terminated coroutine \"%.256s\" (%p).\n"
			"Possible cause is terminated coroutine's main routine has already returned.",
			src->name, src, src->starter->name, src->starter );

		CoroutineCtxSwitch( src, src->starter );
	}
}

// Local Variables: //
// mode: c //
// tab-width: 4 //
// End: //
