//
// 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 Mar 30 17:20:57 2018
// Update Count     : 9
//

#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, void * storage, size_t storageSize ) with( this ) {
      size		 = storageSize == 0 ? 65000 : storageSize; // size of stack
      this.storage = storage;                                // 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	 = storage != NULL;
}

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, const char * name, void * storage, size_t storageSize ) with( this ) {
      (this.stack){storage, storageSize};
      this.name = name;
      errno_ = 0;
      state = Start;
      starter = NULL;
      last = NULL;
}

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) {
      // Safety note : This could cause some false positives due to preemption
      verify( TL_GET( preemption_state.enabled ) || TL_GET( 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
      kernelTLS.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 );
      // Safety note : This could cause some false positives due to preemption
      verify( TL_GET( preemption_state.enabled ) || TL_GET( 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 ( !storage ) {
            __cfaabi_dbg_print_safe("Kernel : Creating stack of size %zu for stack obj %p\n", cxtSize + size + 8, this);

            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 {
            __cfaabi_dbg_print_safe("Kernel : stack obj %p using user stack %p(%u bytes)\n", this, storage, storageSize);

            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 = TL_GET( 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: //
