/* -----------------------------------------------------------------------------
 *
 * (c) The GHC Team, 1998-2004
 *
 * Out-of-line primitive operations
 *
 * This file contains the implementations of all the primitive
 * operations ("primops") which are not expanded inline.  See
 * ghc/compiler/prelude/primops.txt.pp for a list of all the primops;
 * this file contains code for most of those with the attribute
 * out_of_line=True.
 *
 * Entry convention: the entry convention for a primop is that all the
 * args are in Stg registers (R1, R2, etc.).  This is to make writing
 * the primops easier.  (see compiler/codeGen/CgCallConv.hs).
 *
 * Return convention: results from a primop are generally returned
 * using the ordinary unboxed tuple return convention.  The C-- parser
 * implements the RET_xxxx() macros to perform unboxed-tuple returns
 * based on the prevailing return convention.
 *
 * This file is written in a subset of C--, extended with various
 * features specific to GHC.  It is compiled by GHC directly.  For the
 * syntax of .cmm files, see the parser in ghc/compiler/cmm/CmmParse.y.
 *
 * ---------------------------------------------------------------------------*/

#include "Cmm.h"
#include "GmpDerivedConstants.h"

#ifdef __PIC__
#ifndef mingw32_HOST_OS
import __gmpz_init;
import __gmpz_add;
import __gmpz_sub;
import __gmpz_mul;
import __gmpz_gcd;
import __gmpn_gcd_1;
import __gmpn_cmp;
import __gmpz_tdiv_q;
import __gmpz_tdiv_r;
import __gmpz_tdiv_qr;
import __gmpz_fdiv_qr;
import __gmpz_divexact;
import __gmpz_and;
import __gmpz_xor;
import __gmpz_ior;
import __gmpz_com;
#endif
#endif

/* -----------------------------------------------------------------------------
   Arbitrary-precision Integer operations.

   There are some assumptions in this code that mp_limb_t == W_.  This is
   the case for all the platforms that GHC supports, currently.
   -------------------------------------------------------------------------- */

integer_cmm_int2Integerzh
{
   /* arguments: R1 = Int# */

   W_ val, s, p; /* to avoid aliasing */

   val = R1;
   ALLOC_PRIM( SIZEOF_StgArrWords + WDS(1), NO_PTRS, integer_cmm_int2Integerzh );

   p = Hp - SIZEOF_StgArrWords;
   SET_HDR(p, stg_ARR_WORDS_info, W_[CCCS]);
   StgArrWords_words(p) = 1;

   /* mpz_set_si is inlined here, makes things simpler */
   if (%lt(val,0)) {
        s  = -1;
        Hp(0) = -val;
   } else {
     if (%gt(val,0)) {
        s = 1;
        Hp(0) = val;
     } else {
        s = 0;
     }
  }

   /* returns (# size  :: Int#,
                 data  :: ByteArray#
               #)
   */
   RET_NP(s,p);
}

integer_cmm_word2Integerzh
{
   /* arguments: R1 = Word# */

   W_ val, s, p; /* to avoid aliasing */

   val = R1;

   ALLOC_PRIM( SIZEOF_StgArrWords + WDS(1), NO_PTRS, integer_cmm_word2Integerzh);

   p = Hp - SIZEOF_StgArrWords;
   SET_HDR(p, stg_ARR_WORDS_info, W_[CCCS]);
   StgArrWords_words(p) = 1;

   if (val != 0) {
        s = 1;
        W_[Hp] = val;
   } else {
        s = 0;
   }

   /* returns (# size  :: Int#,
                 data  :: ByteArray# #)
   */
   RET_NP(s,p);
}


/*
 * 'long long' primops for converting to/from Integers.
 */

#if WORD_SIZE_IN_BITS < 64

integer_cmm_int64ToIntegerzh
{
   /* arguments: L1 = Int64# */

   L_ val;
   W_ hi, lo, s, neg, words_needed, p;

   val = L1;
   neg = 0;

   hi = TO_W_(val >> 32);
   lo = TO_W_(val);

   if ( hi == 0 || (hi == 0xFFFFFFFF && lo != 0) )  {
       // minimum is one word
       words_needed = 1;
   } else {
       words_needed = 2;
   }

   ALLOC_PRIM( SIZEOF_StgArrWords + WDS(words_needed),
               NO_PTRS, integer_cmm_int64ToIntegerzh );

   p = Hp - SIZEOF_StgArrWords - WDS(words_needed) + WDS(1);
   SET_HDR(p, stg_ARR_WORDS_info, W_[CCCS]);
   StgArrWords_words(p) = words_needed;

   if ( %lt(hi,0) ) {
     neg = 1;
     lo = -lo;
     if(lo == 0) {
       hi = -hi;
     } else {
       hi = -hi - 1;
     }
   }

   if ( words_needed == 2 )  {
      s = 2;
      Hp(-1) = lo;
      Hp(0) = hi;
   } else {
       if ( lo != 0 ) {
           s = 1;
           Hp(0) = lo;
       } else /* val==0 */  {
           s = 0;
       }
   }
   if ( neg != 0 ) {
        s = -s;
   }

   /* returns (# size  :: Int#,
                 data  :: ByteArray# #)
   */
   RET_NP(s,p);
}
integer_cmm_word64ToIntegerzh
{
   /* arguments: L1 = Word64# */

   L_ val;
   W_ hi, lo, s, words_needed, p;

   val = L1;
   hi = TO_W_(val >> 32);
   lo = TO_W_(val);

   if ( hi != 0 ) {
      words_needed = 2;
   } else {
      words_needed = 1;
   }

   ALLOC_PRIM( SIZEOF_StgArrWords + WDS(words_needed),
               NO_PTRS, integer_cmm_word64ToIntegerzh );

   p = Hp - SIZEOF_StgArrWords - WDS(words_needed) + WDS(1);
   SET_HDR(p, stg_ARR_WORDS_info, W_[CCCS]);
   StgArrWords_words(p) = words_needed;

   if ( hi != 0 ) {
     s = 2;
     Hp(-1) = lo;
     Hp(0)  = hi;
   } else {
      if ( lo != 0 ) {
        s = 1;
        Hp(0) = lo;
     } else /* val==0 */  {
      s = 0;
     }
  }

   /* returns (# size  :: Int#,
                 data  :: ByteArray# #)
   */
   RET_NP(s,p);
}

#endif /* WORD_SIZE_IN_BITS < 64 */

#define GMP_TAKE2_RET1(name,mp_fun)                                     \
name                                                                    \
{                                                                       \
  CInt s1, s2;                                                          \
  W_ d1, d2;                                                            \
  W_ mp_tmp1;                                                           \
  W_ mp_tmp2;                                                           \
  W_ mp_result1;                                                        \
                                                                        \
  /* call doYouWantToGC() */                                            \
  MAYBE_GC(R2_PTR & R4_PTR, name);                                      \
                                                                        \
  STK_CHK_GEN( 3 * SIZEOF_MP_INT, R2_PTR & R4_PTR, name );              \
                                                                        \
  s1 = W_TO_INT(R1);                                                    \
  d1 = R2;                                                              \
  s2 = W_TO_INT(R3);                                                    \
  d2 = R4;                                                              \
                                                                        \
  mp_tmp1    = Sp - 1 * SIZEOF_MP_INT;                                  \
  mp_tmp2    = Sp - 2 * SIZEOF_MP_INT;                                  \
  mp_result1 = Sp - 3 * SIZEOF_MP_INT;                                  \
  MP_INT__mp_alloc(mp_tmp1) = W_TO_INT(StgArrWords_words(d1));          \
  MP_INT__mp_size(mp_tmp1)  = (s1);                                     \
  MP_INT__mp_d(mp_tmp1)     = BYTE_ARR_CTS(d1);                         \
  MP_INT__mp_alloc(mp_tmp2) = W_TO_INT(StgArrWords_words(d2));          \
  MP_INT__mp_size(mp_tmp2)  = (s2);                                     \
  MP_INT__mp_d(mp_tmp2)     = BYTE_ARR_CTS(d2);                         \
                                                                        \
  foreign "C" __gmpz_init(mp_result1 "ptr") [];                         \
                                                                        \
  /* Perform the operation */                                           \
  foreign "C" mp_fun(mp_result1 "ptr",mp_tmp1  "ptr",mp_tmp2  "ptr") []; \
                                                                        \
  RET_NP(TO_W_(MP_INT__mp_size(mp_result1)),                            \
         MP_INT__mp_d(mp_result1) - SIZEOF_StgArrWords);                \
}

#define GMP_TAKE1_UL1_RET1(name,mp_fun)                                 \
name                                                                    \
{                                                                       \
  CInt s1;                                                              \
  W_ d1;                                                                \
  CLong ul;                                                             \
  W_ mp_tmp;                                                            \
  W_ mp_result;                                                         \
                                                                        \
  /* call doYouWantToGC() */                                            \
  MAYBE_GC(R2_PTR, name);                                               \
                                                                        \
  STK_CHK_GEN( 2 * SIZEOF_MP_INT, R2_PTR, name );                       \
                                                                        \
  s1 = W_TO_INT(R1);                                                    \
  d1 = R2;                                                              \
  ul = R3;                                                              \
                                                                        \
  mp_tmp     = Sp - 1 * SIZEOF_MP_INT;                                  \
  mp_result  = Sp - 2 * SIZEOF_MP_INT;                                  \
  MP_INT__mp_alloc(mp_tmp) = W_TO_INT(StgArrWords_words(d1));           \
  MP_INT__mp_size(mp_tmp)  = (s1);                                      \
  MP_INT__mp_d(mp_tmp)     = BYTE_ARR_CTS(d1);                          \
                                                                        \
  foreign "C" __gmpz_init(mp_result "ptr") [];                          \
                                                                        \
  /* Perform the operation */                                           \
  foreign "C" mp_fun(mp_result "ptr",mp_tmp "ptr", ul) [];              \
                                                                        \
  RET_NP(TO_W_(MP_INT__mp_size(mp_result)),                             \
         MP_INT__mp_d(mp_result) - SIZEOF_StgArrWords);                 \
}

#define GMP_TAKE1_RET1(name,mp_fun)                                     \
name                                                                    \
{                                                                       \
  CInt s1;                                                              \
  W_ d1;                                                                \
  W_ mp_tmp1;                                                           \
  W_ mp_result1;                                                        \
                                                                        \
  /* call doYouWantToGC() */                                            \
  MAYBE_GC(R2_PTR, name);                                               \
                                                                        \
  STK_CHK_GEN( 2 * SIZEOF_MP_INT, R2_PTR, name );                       \
                                                                        \
  d1 = R2;                                                              \
  s1 = W_TO_INT(R1);                                                    \
                                                                        \
  mp_tmp1    = Sp - 1 * SIZEOF_MP_INT;                                  \
  mp_result1 = Sp - 2 * SIZEOF_MP_INT;                                  \
  MP_INT__mp_alloc(mp_tmp1)     = W_TO_INT(StgArrWords_words(d1));      \
  MP_INT__mp_size(mp_tmp1)      = (s1);                                 \
  MP_INT__mp_d(mp_tmp1)         = BYTE_ARR_CTS(d1);                     \
                                                                        \
  foreign "C" __gmpz_init(mp_result1 "ptr") [];                         \
                                                                        \
  /* Perform the operation */                                           \
  foreign "C" mp_fun(mp_result1 "ptr",mp_tmp1 "ptr") [];                \
                                                                        \
  RET_NP(TO_W_(MP_INT__mp_size(mp_result1)),                            \
         MP_INT__mp_d(mp_result1) - SIZEOF_StgArrWords);                \
}

#define GMP_TAKE2_RET2(name,mp_fun)                                                     \
name                                                                                    \
{                                                                                       \
  CInt s1, s2;                                                                          \
  W_ d1, d2;                                                                            \
  W_ mp_tmp1;                                                                           \
  W_ mp_tmp2;                                                                           \
  W_ mp_result1;                                                                        \
  W_ mp_result2;                                                                        \
                                                                                        \
  /* call doYouWantToGC() */                                                            \
  MAYBE_GC(R2_PTR & R4_PTR, name);                                                      \
                                                                                        \
  STK_CHK_GEN( 4 * SIZEOF_MP_INT, R2_PTR & R4_PTR, name );                              \
                                                                                        \
  s1 = W_TO_INT(R1);                                                                    \
  d1 = R2;                                                                              \
  s2 = W_TO_INT(R3);                                                                    \
  d2 = R4;                                                                              \
                                                                                        \
  mp_tmp1    = Sp - 1 * SIZEOF_MP_INT;                                                  \
  mp_tmp2    = Sp - 2 * SIZEOF_MP_INT;                                                  \
  mp_result1 = Sp - 3 * SIZEOF_MP_INT;                                                  \
  mp_result2 = Sp - 4 * SIZEOF_MP_INT;                                                  \
  MP_INT__mp_alloc(mp_tmp1)     = W_TO_INT(StgArrWords_words(d1));                      \
  MP_INT__mp_size(mp_tmp1)      = (s1);                                                 \
  MP_INT__mp_d(mp_tmp1)         = BYTE_ARR_CTS(d1);                                     \
  MP_INT__mp_alloc(mp_tmp2)     = W_TO_INT(StgArrWords_words(d2));                      \
  MP_INT__mp_size(mp_tmp2)      = (s2);                                                 \
  MP_INT__mp_d(mp_tmp2)         = BYTE_ARR_CTS(d2);                                     \
                                                                                        \
  foreign "C" __gmpz_init(mp_result1 "ptr") [];                                         \
  foreign "C" __gmpz_init(mp_result2 "ptr") [];                                         \
                                                                                        \
  /* Perform the operation */                                                           \
  foreign "C" mp_fun(mp_result1 "ptr",mp_result2 "ptr",mp_tmp1 "ptr",mp_tmp2 "ptr") []; \
                                                                                        \
  RET_NPNP(TO_W_(MP_INT__mp_size(mp_result1)),                                          \
           MP_INT__mp_d(mp_result1) - SIZEOF_StgArrWords,                               \
           TO_W_(MP_INT__mp_size(mp_result2)),                                          \
           MP_INT__mp_d(mp_result2) - SIZEOF_StgArrWords);                              \
}

GMP_TAKE2_RET1(integer_cmm_plusIntegerzh,     __gmpz_add)
GMP_TAKE2_RET1(integer_cmm_minusIntegerzh,    __gmpz_sub)
GMP_TAKE2_RET1(integer_cmm_timesIntegerzh,    __gmpz_mul)
GMP_TAKE2_RET1(integer_cmm_gcdIntegerzh,      __gmpz_gcd)
GMP_TAKE2_RET1(integer_cmm_quotIntegerzh,     __gmpz_tdiv_q)
GMP_TAKE2_RET1(integer_cmm_remIntegerzh,      __gmpz_tdiv_r)
GMP_TAKE2_RET1(integer_cmm_divExactIntegerzh, __gmpz_divexact)
GMP_TAKE2_RET1(integer_cmm_andIntegerzh,      __gmpz_and)
GMP_TAKE2_RET1(integer_cmm_orIntegerzh,       __gmpz_ior)
GMP_TAKE2_RET1(integer_cmm_xorIntegerzh,      __gmpz_xor)
GMP_TAKE1_UL1_RET1(integer_cmm_mul2ExpIntegerzh, __gmpz_mul_2exp)
GMP_TAKE1_UL1_RET1(integer_cmm_fdivQ2ExpIntegerzh, __gmpz_fdiv_q_2exp)
GMP_TAKE1_RET1(integer_cmm_complementIntegerzh, __gmpz_com)

GMP_TAKE2_RET2(integer_cmm_quotRemIntegerzh, __gmpz_tdiv_qr)
GMP_TAKE2_RET2(integer_cmm_divModIntegerzh,  __gmpz_fdiv_qr)

integer_cmm_gcdIntzh
{
    /* R1 = the first Int#; R2 = the second Int# */
    W_ r;
    W_ mp_tmp_w;

    STK_CHK_GEN( 1 * SIZEOF_MP_INT, NO_PTRS, integer_cmm_gcdIntzh );

    mp_tmp_w = Sp - 1 * SIZEOF_MP_INT;

    W_[mp_tmp_w] = R1;
    (r) = foreign "C" __gmpn_gcd_1(mp_tmp_w "ptr", 1, R2) [];

    R1 = r;
    /* Result parked in R1, return via info-pointer at TOS */
    jump %ENTRY_CODE(Sp(0));
}


integer_cmm_gcdIntegerIntzh
{
    /* R1 = s1; R2 = d1; R3 = the int */
    W_ s1;
    (s1) = foreign "C" __gmpn_gcd_1( BYTE_ARR_CTS(R2) "ptr", R1, R3) [];
    R1 = s1;

    /* Result parked in R1, return via info-pointer at TOS */
    jump %ENTRY_CODE(Sp(0));
}


integer_cmm_cmpIntegerIntzh
{
    /* R1 = s1; R2 = d1; R3 = the int */
    W_ usize, vsize, v_digit, u_digit;

    usize = R1;
    vsize = 0;
    v_digit = R3;

    // paraphrased from __gmpz_cmp_si() in the GMP sources
    if (%gt(v_digit,0)) {
        vsize = 1;
    } else {
        if (%lt(v_digit,0)) {
            vsize = -1;
            v_digit = -v_digit;
        }
    }

    if (usize != vsize) {
        R1 = usize - vsize;
        jump %ENTRY_CODE(Sp(0));
    }

    if (usize == 0) {
        R1 = 0;
        jump %ENTRY_CODE(Sp(0));
    }

    u_digit = W_[BYTE_ARR_CTS(R2)];

    if (u_digit == v_digit) {
        R1 = 0;
        jump %ENTRY_CODE(Sp(0));
    }

    if (%gtu(u_digit,v_digit)) { // NB. unsigned: these are mp_limb_t's
        R1 = usize;
    } else {
        R1 = -usize;
    }

    jump %ENTRY_CODE(Sp(0));
}

integer_cmm_cmpIntegerzh
{
    /* R1 = s1; R2 = d1; R3 = s2; R4 = d2 */
    W_ usize, vsize, size, up, vp;
    CInt cmp;

    // paraphrased from __gmpz_cmp() in the GMP sources
    usize = R1;
    vsize = R3;

    if (usize != vsize) {
        R1 = usize - vsize;
        jump %ENTRY_CODE(Sp(0));
    }

    if (usize == 0) {
        R1 = 0;
        jump %ENTRY_CODE(Sp(0));
    }

    if (%lt(usize,0)) { // NB. not <, which is unsigned
        size = -usize;
    } else {
        size = usize;
    }

    up = BYTE_ARR_CTS(R2);
    vp = BYTE_ARR_CTS(R4);

    (cmp) = foreign "C" __gmpn_cmp(up "ptr", vp "ptr", size) [];

    if (cmp == 0 :: CInt) {
        R1 = 0;
        jump %ENTRY_CODE(Sp(0));
    }

    if (%lt(cmp,0 :: CInt) == %lt(usize,0)) {
        R1 = 1;
    } else {
        R1 = (-1);
    }
    /* Result parked in R1, return via info-pointer at TOS */
    jump %ENTRY_CODE(Sp(0));
}

integer_cmm_integer2Intzh
{
    /* R1 = s; R2 = d */
    W_ r, s;

    s = R1;
    if (s == 0) {
        r = 0;
    } else {
        r = W_[R2 + SIZEOF_StgArrWords];
        if (%lt(s,0)) {
            r = -r;
        }
    }
    /* Result parked in R1, return via info-pointer at TOS */
    R1 = r;
    jump %ENTRY_CODE(Sp(0));
}

integer_cmm_integer2Wordzh
{
  /* R1 = s; R2 = d */
  W_ r, s;

  s = R1;
  if (s == 0) {
    r = 0;
  } else {
    r = W_[R2 + SIZEOF_StgArrWords];
    if (%lt(s,0)) {
        r = -r;
    }
  }
  /* Result parked in R1, return via info-pointer at TOS */
  R1 = r;
  jump %ENTRY_CODE(Sp(0));
}

#define DOUBLE_MANTISSA_SIZE SIZEOF_DOUBLE
#define ARR_SIZE (SIZEOF_StgArrWords + DOUBLE_MANTISSA_SIZE)

integer_cmm_decodeDoublezh
{
    D_ arg;
    W_ p;
    W_ mp_tmp1;
    W_ mp_tmp_w;

    STK_CHK_GEN( 2 * SIZEOF_MP_INT, NO_PTRS, integer_cmm_decodeDoublezh );

    mp_tmp1  = Sp - 1 * SIZEOF_MP_INT;
    mp_tmp_w = Sp - 2 * SIZEOF_MP_INT;

    /* arguments: D1 = Double# */
    arg = D1;

    ALLOC_PRIM( ARR_SIZE, NO_PTRS, integer_cmm_decodeDoublezh );

    /* Be prepared to tell Lennart-coded integer_cbits_decodeDouble
       where mantissa.d can be put (it does not care about the rest) */
    p = Hp - ARR_SIZE + WDS(1);
    SET_HDR(p, stg_ARR_WORDS_info, W_[CCCS]);
    StgArrWords_words(p) = BYTES_TO_WDS(DOUBLE_MANTISSA_SIZE);
    MP_INT__mp_d(mp_tmp1) = BYTE_ARR_CTS(p);

    /* Perform the operation */
    foreign "C" integer_cbits_decodeDouble(mp_tmp1 "ptr", mp_tmp_w "ptr",arg) [];

    /* returns: (Int# (expn), Int#, ByteArray#) */
    RET_NNP(W_[mp_tmp_w], TO_W_(MP_INT__mp_size(mp_tmp1)), p);
}
