//
// 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 : Fri Jul 21 22:34:57 2017
// Update Count     : 1
//

#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) {
	this.size		= 65000;	// size of stack
	this.storage	= NULL;	// pointer to stack
	this.limit		= NULL;	// stack grows towards stack limit
	this.base		= NULL;	// base of stack
	this.context	= NULL;	// address of cfa_context_t
	this.top		= NULL;	// address of top of storage
	this.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) {
	this.name = name;
	this.errno_ = 0;
	this.state = Start;
	this.starter = NULL;
	this.last = NULL;
}

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

void ^?{}(coStack_t & this) {
	if ( ! this.userStack && this.storage ) {
		LIB_DEBUG_DO(
			if ( mprotect( this.storage, pageSize, PROT_READ | PROT_WRITE ) == -1 ) {
				abortf( "(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) {
	// THREAD_GETMEM( This )->disableInterrupts();

	// 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;

	// THREAD_GETMEM( This )->enableInterrupts();
} //ctxSwitchDirect

void create_stack( coStack_t* this, unsigned int storageSize ) {
	//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)this->storage == 0 ) {
		this->userStack = false;
		this->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
		LIB_DEBUG_DO( this->storage = memalign( pageSize, cxtSize + this->size + pageSize ) );
		LIB_NO_DEBUG_DO( this->storage = malloc( cxtSize + this->size + 8 ) );

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

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

		LIB_DEBUG_DO( this->limit = (char *)this->storage + pageSize );
		LIB_NO_DEBUG_DO( this->limit = (char *)libCeiling( (unsigned long)this->storage, 16 ) ); // minimum alignment

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

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

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

	this->base = (char *)this->limit + this->size;
	this->context = this->base;
	this->top = (char *)this->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: //
