/*
 * Copyright (c) 2001-2006 Marc Alexander Lehmann <schmorp@schmorp.de>
 * 
 * Redistribution and use in source and binary forms, with or without modifica-
 * tion, are permitted provided that the following conditions are met:
 * 
 *   1.  Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *   2.  Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 *   3.  The name of the author may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
 * CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPE-
 * CIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTH-
 * ERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This library is modelled strictly after Ralf S. Engelschalls article at
 * http://www.gnu.org/software/pth/rse-pmt.ps.  So most of the credit must
 * go to Ralf S. Engelschall <rse@engelschall.com>.
 */

#include "coro.h"

#if !defined(STACK_ADJUST_PTR)
/* IRIX is decidedly NON-unix */
# if __sgi
#  define STACK_ADJUST_PTR(sp,ss) ((char *)(sp) + (ss) - 8)
#  define STACK_ADJUST_SIZE(sp,ss) ((ss) - 8)
# elif __i386__ && CORO_LINUX
#  define STACK_ADJUST_PTR(sp,ss) ((char *)(sp) + (ss))
#  define STACK_ADJUST_SIZE(sp,ss) (ss)
# elif __amd64__ && CORO_LINUX
#  define STACK_ADJUST_PTR(sp,ss) ((char *)(sp) + (ss) - 8)
#  define STACK_ADJUST_SIZE(sp,ss) (ss)
# else
#  define STACK_ADJUST_PTR(sp,ss) (sp)
#  define STACK_ADJUST_SIZE(sp,ss) (ss)
# endif
#endif

#if CORO_UCONTEXT
# include <stddef.h>
#endif

#if CORO_SJLJ || CORO_LOSER || CORO_LINUX || CORO_IRIX

#include <stdlib.h>

#if CORO_SJLJ
# include <stdio.h>
# include <signal.h>
# include <unistd.h>
#endif

static volatile coro_func coro_init_func;
static volatile void *coro_init_arg;
static volatile coro_context *new_coro, *create_coro;

/* what we really want to detect here is wether we use a new-enough version of GAS */
/* instead, check for gcc 3 and ELF and hope for the best */
#if __GNUC__ >= 3 && __ELF__
# define HAVE_CFI 1
#endif

static void
coro_init (void)
{
  volatile coro_func func = coro_init_func;
  volatile void *arg = coro_init_arg;

  coro_transfer ((coro_context *)new_coro, (coro_context *)create_coro);

  func ((void *)arg);

  /* the new coro returned. bad. just abort() for now */
  abort ();
}

# if CORO_SJLJ

static volatile int trampoline_count;

/* trampoline signal handler */
static void
trampoline (int sig)
{
  if (setjmp (((coro_context *)new_coro)->env))
    {
#if HAVE_CFI
      asm (".cfi_startproc");
#endif
      coro_init (); /* start it */
#if HAVE_CFI
      asm (".cfi_endproc");
#endif
    }
  else
    trampoline_count++;
}

# endif

#endif

/* initialize a machine state */
void coro_create (coro_context *ctx,
                  coro_func coro, void *arg,
                  void *sptr, long ssize)
{
#if CORO_UCONTEXT

  getcontext (&(ctx->uc));

  ctx->uc.uc_link           =  0;
  ctx->uc.uc_stack.ss_sp    = STACK_ADJUST_PTR (sptr,ssize);
  ctx->uc.uc_stack.ss_size  = (size_t) STACK_ADJUST_SIZE (sptr,ssize);
  ctx->uc.uc_stack.ss_flags = 0;

  makecontext (&(ctx->uc), (void (*)()) coro, 1, arg);

#elif CORO_SJLJ || CORO_LOSER || CORO_LINUX || CORO_IRIX

# if CORO_SJLJ
  stack_t ostk, nstk;
  struct sigaction osa, nsa;
  sigset_t nsig, osig;
# endif
  coro_context nctx;

  coro_init_func = coro;
  coro_init_arg  = arg;

  new_coro    = ctx;
  create_coro = &nctx;

# if CORO_SJLJ
  /* we use SIGUSR2. first block it, then fiddle with it. */

  sigemptyset (&nsig);
  sigaddset (&nsig, SIGUSR2);
  sigprocmask (SIG_BLOCK, &nsig, &osig);

  nsa.sa_handler = trampoline;
  sigemptyset (&nsa.sa_mask);
  nsa.sa_flags = SA_ONSTACK;

  if (sigaction (SIGUSR2, &nsa, &osa))
    {
      perror ("sigaction");
      abort ();
    }

  /* set the new stack */
  nstk.ss_sp    = STACK_ADJUST_PTR (sptr,ssize); /* yes, some platforms (IRIX) get this wrong. */
  nstk.ss_size  = STACK_ADJUST_SIZE (sptr,ssize);
  nstk.ss_flags = 0;

  if (sigaltstack (&nstk, &ostk) < 0)
    {
      perror ("sigaltstack");
      abort ();
    }

  trampoline_count = 0;
  kill (getpid (), SIGUSR2);
  sigfillset (&nsig); sigdelset (&nsig, SIGUSR2);

  while (!trampoline_count)
    sigsuspend (&nsig);

  sigaltstack (0, &nstk);
  nstk.ss_flags = SS_DISABLE;
  if (sigaltstack (&nstk, 0) < 0)
    perror ("sigaltstack");

  sigaltstack (0, &nstk);
  if (~nstk.ss_flags & SS_DISABLE)
    abort ();

  if (~ostk.ss_flags & SS_DISABLE)
    sigaltstack (&ostk, 0);

  sigaction (SIGUSR2, &osa, 0);

  sigprocmask (SIG_SETMASK, &osig, 0);

# elif CORO_LOSER

  setjmp (ctx->env);
#if __CYGWIN__
  ctx->env[7] = (long)((char *)sptr + ssize);
  ctx->env[8] = (long)coro_init;
#elif defined(_M_IX86)
  ((_JUMP_BUFFER *)&ctx->env)->Eip   = (long)coro_init;
  ((_JUMP_BUFFER *)&ctx->env)->Esp   = (long)STACK_ADJUST_PTR (sptr,ssize);
#elif defined(_M_AMD64)
  ((_JUMP_BUFFER *)&ctx->env)->Rip   = (__int64)coro_init;
  ((_JUMP_BUFFER *)&ctx->env)->Rsp   = (__int64)STACK_ADJUST_PTR (sptr,ssize);
#elif defined(_M_IA64)
  ((_JUMP_BUFFER *)&ctx->env)->StIIP = (__int64)coro_init;
  ((_JUMP_BUFFER *)&ctx->env)->IntSp = (__int64)STACK_ADJUST_PTR (sptr,ssize);
#else
#error "microsoft libc or architecture not supported"
#endif

# elif CORO_LINUX

  _setjmp (ctx->env);
#if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined (JB_PC) && defined (JB_SP)
  ctx->env[0].__jmpbuf[JB_PC] = (long)coro_init;
  ctx->env[0].__jmpbuf[JB_SP] = (long)STACK_ADJUST_PTR (sptr, ssize);
#elif __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 0 && defined (__mc68000__)
  ctx->env[0].__jmpbuf[0].__aregs[0] = (long int)coro_init;
  ctx->env[0].__jmpbuf[0].__sp = (int *)((char *)sptr + ssize);
#elif defined (__GNU_LIBRARY__) && defined (__i386__)
  ctx->env[0].__jmpbuf[0].__pc = (char *)coro_init;
  ctx->env[0].__jmpbuf[0].__sp = (void *)((char *)sptr + ssize);
#elif defined (__GNU_LIBRARY__) && defined (__amd64__)
  ctx->env[0].__jmpbuf[JB_PC]  = (long)coro_init;
  ctx->env[0].__jmpbuf[JB_RSP] = (long)STACK_ADJUST_PTR (sptr, ssize);
#else
#error "linux libc or architecture not supported"
#endif

# elif CORO_IRIX

  setjmp (ctx->env);
  ctx->env[JB_PC] = (__uint64_t)coro_init;
  ctx->env[JB_SP] = (__uint64_t)STACK_ADJUST_PTR (sptr, ssize);

# endif

  coro_transfer ((coro_context *)create_coro, (coro_context *)new_coro);

#else
error unsupported architecture
#endif
}