#ifndef _avcall_sparc_c				/*-*- C -*-*/
#define _avcall_sparc_c
/**
  Copyright 1993 Bill Triggs, <Bill.Triggs@inrialpes.fr>
  Copyright 1995-1999 Bruno Haible, <haible@clisp.cons.org>

  This is free software distributed under the GNU General Public
  Licence described in the file COPYING. Contact the author if
  you don't have this or can't live with it. There is ABSOLUTELY
  NO WARRANTY, explicit or implied, on this software.
**/
/*----------------------------------------------------------------------
  !!! THIS ROUTINE MUST BE COMPILED gcc -O !!!

  Foreign function interface for a Sun4 Sparc with gcc/sun-cc.

  This calls a C function with an argument list built up using macros
  defined in av_call.h.

  Sparc Argument Passing Conventions

  The first 6 words of arguments are passed in integer registers o0-o5
  regardless of type or alignment.  (Registers are windowed: o0-o5 become
  i0-i5 if the called function executes a `save' instruction.)  Remaining
  arguments are pushed onto the stack starting at a fixed offset
  ("argframe"). Space is left on the stack frame for temporary storage of
  the register arguments as well.

  Doubles may be cut in half and misaligned.  Shorter integers are
  always promoted to word-length.  Functions with K&R-style declarations
  and float args pass them as doubles and truncate them on function entry.
  Structures are passed as pointers to a local copy of the structure made
  by the caller.

  Integers and pointers are returned in o0, floats in f0, doubles in
  f0/f1.  If the function returns a structure a pointer to space
  allocated by the caller is pushed onto the stack immediately
  before the function arguments. Gcc without -fpcc-struct-return returns
  <= 4 byte structures as integers.

  Sun cc allocates temporary space for a returned structure just below
  the current frame pointer $fp (the $sp of the caller), and the caller
  must copy them from there. It also returns the temp address in $o0, but
  that gets nuked in the return in the code below so we can't use it.
  **The Sun cc struct return stuff below is a kludge**, but seems to work
  on the test cases...

  Compile this routine with gcc for the __asm__ extensions and with
  optimisation on (-O or -O2 or -g -O) so that argframe is set to the
  correct offset. (%sp is used differently in non-optimized code).
  For Sun cc, use the pre-compiled assembler version of this routine.
  ----------------------------------------------------------------------*/
#include "avcall.h.in"

#define RETURN(TYPE,VAL)	(*(TYPE*)l->raddr = (TYPE)(VAL))

register void* callee	__asm__("%g2");  /* any global or local register */
register __avword o0	__asm__("%o0");
register __avword o1	__asm__("%o1");
register __avword o2	__asm__("%o2");
register __avword o3	__asm__("%o3");
register __avword o4	__asm__("%o4");
register __avword o5	__asm__("%o5");

int
__builtin_avcall(av_alist* l)
{
  /*?? We probably need to make space for Sun cc
    struct return somewhere here. */
  register __avword* sp	__asm__("%sp");  /* C names for registers */
  register float fret	__asm__("%f0");  /* %f0 */
  register double dret	__asm__("%f0");  /* %f0,%f1 */

  __avword trampoline[6];		/* room for a trampoline */
  __avword space[__AV_ALIST_WORDS];	/* space for callee's stack frame */
  __avword *argframe = sp + 17;		/* stack offset for argument list */
  int arglen = l->aptr - l->args;
  __avword i;

  if ((l->rtype == __AVstruct) && !(l->flags & __AV_SUNCC_STRUCT_RETURN))
    argframe[-1] = (__avword)l->raddr;	/* push struct return address */

  {
    int i;
    for (i = 6; i < arglen; i++)	/* push excess function args */
      argframe[i] = l->args[i];
  }

  if ((l->rtype == __AVstruct) && (l->flags & __AV_SUNPROCC_STRUCT_RETURN))
    /* SUNWspro cc compiled functions don't copy the structure to the area
     * pointed to by argframe[-1] unless the caller has a proper "unimp n"
     * instruction. We generate the calling instructions on the stack. */
    {
      trampoline[0] = 0x9FC08000;	/* call %g2	*/
      trampoline[1] = 0x01000000;	/* nop		*/
      trampoline[2] = l->rsize & 0xFFF;	/* unimp n	*/
      trampoline[3] = 0xB0102000;       /* mov 0,%i0    */
      trampoline[4] = 0x81C7E008;       /* ret          */
      trampoline[5] = 0x81E80000;       /* restore      */
      __asm__ __volatile__ ("iflush %0" : : "r" (&trampoline[0]));
      __asm__ __volatile__ ("iflush %0" : : "r" (&trampoline[2]));
      __asm__ __volatile__ ("iflush %0" : : "r" (&trampoline[4]));
      __asm__ __volatile__ ("iflush %0" : : "r" (&trampoline[5]));
      o0 = l->args[0]; o1 = l->args[1]; o2 = l->args[2];
      o3 = l->args[3]; o4 = l->args[4]; o5 = l->args[5];
      callee = l->func;
      goto *(void*)trampoline;
    }

					/* call function with 1st 6 args */
  i = ({ __avword iret;	/* %o0 */
         iret = (*l->func)(l->args[0], l->args[1], l->args[2],
			   l->args[3], l->args[4], l->args[5]);
         asm ("nop");	/* struct returning functions skip this instruction */
         iret;
       });

  /* save return value */
  if (l->rtype == __AVvoid) {
  } else
  if (l->rtype == __AVword) {
    RETURN(__avword, i);
  } else
  if (l->rtype == __AVchar) {
    RETURN(char, i);
  } else
  if (l->rtype == __AVschar) {
    RETURN(signed char, i);
  } else
  if (l->rtype == __AVuchar) {
    RETURN(unsigned char, i);
  } else
  if (l->rtype == __AVshort) {
    RETURN(short, i);
  } else
  if (l->rtype == __AVushort) {
    RETURN(unsigned short, i);
  } else
  if (l->rtype == __AVint) {
    RETURN(int, i);
  } else
  if (l->rtype == __AVuint) {
    RETURN(unsigned int, i);
  } else
  if (l->rtype == __AVlong) {
    RETURN(long, i);
  } else
  if (l->rtype == __AVulong) {
    RETURN(unsigned long, i);
  } else
  if (l->rtype == __AVlonglong || l->rtype == __AVulonglong) {
    ((__avword*)l->raddr)[0] = i;
    ((__avword*)l->raddr)[1] = o1;
  } else
  if (l->rtype == __AVfloat) {
    /* old Sun cc returns floats as doubles */
    if (l->flags & __AV_SUNCC_FLOAT_RETURN) {
      RETURN(float, (float)dret);
    } else {
      RETURN(float, fret);
    }
  } else
  if (l->rtype == __AVdouble) {
    RETURN(double, dret);
  } else
  if (l->rtype == __AVvoidp) {
    RETURN(void*, i);
  } else
  if (l->rtype == __AVstruct) {
    /* This is a kludge for old Sun cc and is probably fragile. */
    if (l->flags & __AV_SUNCC_STRUCT_RETURN) {
      /* Sun cc struct return convention */
      if (l->rsize == sizeof(char)) {
        RETURN(char, ((char*)sp)[-1]);
      } else
      if (l->rsize == sizeof(short)) {
        RETURN(short, ((short*)sp)[-1]);
      } else
      if (l->rsize == sizeof(int)) {
        RETURN(int, ((int*)sp)[-1]);
      } else
      if (l->rsize == sizeof(double)) {
        ((int*)l->raddr)[0] = ((int*)sp)[-2];
        ((int*)l->raddr)[1] = ((int*)sp)[-1];
      } else
      if (l->rsize % 4) {
        char* dstaddr = (char*)l->raddr;
        char* srcaddr = (char*)((long)sp - l->rsize);
        unsigned int count = l->rsize;
        if (count > 4)
          srcaddr = (char*)((long)srcaddr & -4);
        while (count > 0) {
          *dstaddr++ = *srcaddr++;
          count--;
        }
      } else {
        __avword* dstaddr = (__avword*)l->raddr;
        __avword* srcaddr = (__avword*)((long)sp - l->rsize);
        while (srcaddr < sp)
          *dstaddr++ = *srcaddr++;
      }
    } else {
      if (l->flags & __AV_PCC_STRUCT_RETURN) {
        /* pcc struct return convention: need a  *(TYPE*)l->raddr = *(TYPE*)i;  */
        if (l->rsize == sizeof(char)) {
          RETURN(char, *(char*)i);
        } else
        if (l->rsize == sizeof(short)) {
          RETURN(short, *(short*)i);
        } else
        if (l->rsize == sizeof(int)) {
          RETURN(int, *(int*)i);
        } else
        if (l->rsize == sizeof(double)) {
          ((int*)l->raddr)[0] = ((int*)i)[0];
          ((int*)l->raddr)[1] = ((int*)i)[1];
        } else {
          int n = (l->rsize + sizeof(__avword)-1)/sizeof(__avword);
          while (--n >= 0)
            ((__avword*)l->raddr)[n] = ((__avword*)i)[n];
        }
      } else {
        /* normal struct return convention */
        if (l->flags & __AV_SMALL_STRUCT_RETURN) {
          if (l->rsize == sizeof(char)) {
            RETURN(char, i);
          } else
          if (l->rsize == sizeof(short)) {
            RETURN(short, i);
          } else
          if (l->rsize == sizeof(int)) {
            RETURN(int, i);
          }
        }
      }
    }
  }
  return 0;
}

#endif /*_avcall_sparc_c */
