/**************************************************************************/
/*                                                                        */
/*                                 OCaml                                  */
/*                                                                        */
/*             Xavier Leroy, projet Cristal, INRIA Rocquencourt           */
/*                                                                        */
/*   Copyright 2003 Institut National de Recherche en Informatique et     */
/*     en Automatique.                                                    */
/*                                                                        */
/*   All rights reserved.  This file is distributed under the terms of    */
/*   the GNU Lesser General Public License version 2.1, with the          */
/*   special exception on linking described in the file LICENSE.          */
/*                                                                        */
/**************************************************************************/

#include "bng.h"
#include "caml/config.h"

#if defined(__GNUC__)
#if defined(BNG_ARCH_ia32)
#include "bng_ia32.c"
#elif defined(BNG_ARCH_amd64)
#include "bng_amd64.c"
#elif defined(BNG_ARCH_ppc)
#include "bng_ppc.c"
#elif defined (BNG_ARCH_sparc)
#include "bng_sparc.c"
#elif defined (BNG_ARCH_arm64)
#include "bng_arm64.c"
#endif
#endif

#include "bng_digit.c"

/**** Operations that cannot be overridden ****/

/* Return number of leading zero bits in d */
int bng_leading_zero_bits(bngdigit d)
{
  int n = BNG_BITS_PER_DIGIT;
#ifdef ARCH_SIXTYFOUR
  if ((d & 0xFFFFFFFF00000000L) != 0) { n -= 32; d = d >> 32; }
#endif
  if ((d & 0xFFFF0000) != 0) { n -= 16; d = d >> 16; }
  if ((d & 0xFF00) != 0) { n -= 8; d = d >> 8; }
  if ((d & 0xF0) != 0) { n -= 4; d = d >> 4; }
  if ((d & 0xC) != 0) { n -= 2; d = d >> 2; }
  if ((d & 2) != 0) { n -= 1; d = d >> 1; }
  return n - d;
}

/* Complement the digits of {a,len} */
void bng_complement(bng a/*[alen]*/, bngsize alen)
{
  for (/**/; alen > 0; alen--, a++) *a = ~*a;
}

/* Return number of significant digits in {a,alen}. */
bngsize bng_num_digits(bng a/*[alen]*/, bngsize alen)
{
  while (1) {
    if (alen == 0) return 1;
    if (a[alen - 1] != 0) return alen;
    alen--;
  }
}

/* Return 0 if {a,alen} = {b,blen}
         -1 if {a,alen} < {b,blen}
          1 if {a,alen} > {b,blen}. */
int bng_compare(bng a/*[alen]*/, bngsize alen,
                bng b/*[blen]*/, bngsize blen)
{
  bngdigit da, db;

  while (alen > 0 && a[alen-1] == 0) alen--;
  while (blen > 0 && b[blen-1] == 0) blen--;
  if (alen > blen) return 1;
  if (alen < blen) return -1;
  while (alen > 0) {
    alen--;
    da = a[alen];
    db = b[alen];
    if (da > db) return 1;
    if (da < db) return -1;
  }
  return 0;
}

/**** Generic definitions of the overridable operations ****/

/* {a,alen} := {a, alen} + carry.  Return carry out. */
static bngcarry bng_generic_add_carry
       (bng a/*[alen]*/,  bngsize alen, bngcarry carry)
{
  if (carry == 0 || alen == 0) return carry;
  do {
    if (++(*a) != 0) return 0;
    a++;
  } while (--alen);
  return 1;
}

/* {a,alen} := {a,alen} + {b,blen} + carry.  Return carry out.
   Require alen >= blen. */
static bngcarry bng_generic_add
       (bng a/*[alen]*/, bngsize alen,
        bng b/*[blen]*/, bngsize blen,
        bngcarry carry)
{
  alen -= blen;
  for (/**/; blen > 0; blen--, a++, b++) {
    BngAdd2Carry(*a, carry, *a, *b, carry);
  }
  if (carry == 0 || alen == 0) return carry;
  do {
    if (++(*a) != 0) return 0;
    a++;
  } while (--alen);
  return 1;
}

/* {a,alen} := {a, alen} - carry.  Return carry out. */
static bngcarry bng_generic_sub_carry
       (bng a/*[alen]*/,  bngsize alen, bngcarry carry)
{
  if (carry == 0 || alen == 0) return carry;
  do {
    if ((*a)-- != 0) return 0;
    a++;
  } while (--alen);
  return 1;
}

/* {a,alen} := {a,alen} - {b,blen} - carry.  Return carry out.
   Require alen >= blen. */
static bngcarry bng_generic_sub
       (bng a/*[alen]*/, bngsize alen,
        bng b/*[blen]*/, bngsize blen,
        bngcarry carry)
{
  alen -= blen;
  for (/**/; blen > 0; blen--, a++, b++) {
    BngSub2Carry(*a, carry, *a, *b, carry);
  }
  if (carry == 0 || alen == 0) return carry;
  do {
    if ((*a)-- != 0) return 0;
    a++;
  } while (--alen);
  return 1;
}

/* {a,alen} := {a,alen} << shift.
   Return the bits shifted out of the most significant digit of a.
   Require 0 <= shift < BITS_PER_BNGDIGIT. */
static bngdigit bng_generic_shift_left
     (bng a/*[alen]*/, bngsize alen,
      int shift)
{
  int shift2 = BNG_BITS_PER_DIGIT - shift;
  bngdigit carry = 0;
  if (shift > 0) {
    for (/**/; alen > 0; alen--, a++) {
      bngdigit d = *a;
      *a = (d << shift) | carry;
      carry = d >> shift2;
    }
  }
  return carry;
}

/* {a,alen} := {a,alen} >> shift.
   Return the bits shifted out of the least significant digit of a.
   Require 0 <= shift < BITS_PER_BNGDIGIT. */
static bngdigit bng_generic_shift_right
     (bng a/*[alen]*/, bngsize alen,
      int shift)
{
  int shift2 = BNG_BITS_PER_DIGIT - shift;
  bngdigit carry = 0;
  if (shift > 0) {
    for (a = a + alen - 1; alen > 0; alen--, a--) {
      bngdigit d = *a;
      *a = (d >> shift) | carry;
      carry = d << shift2;
    }
  }
  return carry;
}

/* {a,alen} := {a,alen} + d * {b,blen}.  Return carry out.
   Require alen >= blen. */
static bngdigit bng_generic_mult_add_digit
     (bng a/*[alen]*/, bngsize alen,
      bng b/*[blen]*/, bngsize blen,
      bngdigit d)
{
  bngdigit out, ph, pl;
  bngcarry carry;

  alen -= blen;
  for (out = 0; blen > 0; blen--, a++, b++) {
    bngdigit bd = *b;
    /* ph:pl = double-digit product of b's current digit and d */
    BngMult(ph, pl, bd, d);
    /* current digit of a += pl + out.  Accumulate carries in ph. */
    BngAdd3(*a, ph, *a, pl, out);
    /* prepare out for next iteration */
    out = ph;
  }
  if (alen == 0) return out;
  /* current digit of a += out */
  BngAdd2(*a, carry, *a, out);
  a++;
  alen--;
  /* Propagate carry */
  if (carry == 0 || alen == 0) return carry;
  do {
    if (++(*a) != 0) return 0;
    a++;
  } while (--alen);
  return 1;
}

/* {a,alen} := {a,alen} - d * {b,blen}.  Return carry out.
   Require alen >= blen. */
static bngdigit bng_generic_mult_sub_digit
     (bng a/*[alen]*/, bngsize alen,
      bng b/*[blen]*/, bngsize blen,
      bngdigit d)
{
  bngdigit out, ph, pl;
  bngcarry carry;

  alen -= blen;
  for (out = 0; blen > 0; blen--, a++, b++) {
    bngdigit bd = *b;
    /* ph:pl = double-digit product of b's current digit and d */
    BngMult(ph, pl, bd, d);
    /* current digit of a -= pl + out.  Accumulate carrys in ph. */
    BngSub3(*a, ph, *a, pl, out);
    /* prepare out for next iteration */
    out = ph;
  }
  if (alen == 0) return out;
  /* current digit of a -= out */
  BngSub2(*a, carry, *a, out);
  a++;
  alen--;
  /* Propagate carry */
  if (carry == 0 || alen == 0) return carry;
  do {
    if ((*a)-- != 0) return 0;
    a++;
  } while (--alen);
  return 1;
}

/* {a,alen} := {a,alen} + {b,blen} * {c,clen}.  Return carry out.
   Require alen >= blen + clen. */
static bngcarry bng_generic_mult_add
     (bng a/*[alen]*/, bngsize alen,
      bng b/*[blen]*/, bngsize blen,
      bng c/*[clen]*/, bngsize clen)
{
  bngcarry carry;
  for (carry = 0; clen > 0; clen--, c++, alen--, a++)
    carry += bng_mult_add_digit(a, alen, b, blen, *c);
  return carry;
}

/* {a,alen} := 2 * {a,alen} + {b,blen}^2.  Return carry out.
   Require alen >= 2 * blen. */
static bngcarry bng_generic_square_add
     (bng a/*[alen]*/, bngsize alen,
      bng b/*[blen]*/, bngsize blen)
{
  bngcarry carry1, carry2;
  bngsize i, aofs;
  bngdigit ph, pl, d;

  /* Double products */
  for (carry1 = 0, i = 1; i < blen; i++) {
    aofs = 2 * i - 1;
    carry1 += bng_mult_add_digit(a + aofs, alen - aofs,
                                 b + i, blen - i, b[i - 1]);
  }
  /* Multiply by two */
  carry1 = (carry1 << 1) | bng_shift_left(a, alen, 1);
  /* Add square of digits */
  carry2 = 0;
  for (i = 0; i < blen; i++) {
    d = b[i];
    BngMult(ph, pl, d, d);
    BngAdd2Carry(*a, carry2, *a, pl, carry2);
    a++;
    BngAdd2Carry(*a, carry2, *a, ph, carry2);
    a++;
  }
  alen -= 2 * blen;
  if (alen > 0 && carry2 != 0) {
    do {
      if (++(*a) != 0) { carry2 = 0; break; }
      a++;
    } while (--alen);
  }
  return carry1 + carry2;
}

/* {a,len-1} := {b,len} / d.  Return {b,len} modulo d.
   Require MSD of b < d.
   If BngDivNeedsNormalization is defined, require d normalized. */
static bngdigit bng_generic_div_rem_norm_digit
     (bng a/*[len-1]*/, bng b/*[len]*/, bngsize len, bngdigit d)
{
  bngdigit topdigit, quo, rem;
  intnat i;

  topdigit = b[len - 1];
  for (i = len - 2; i >= 0; i--) {
    /* Divide topdigit:current digit of numerator by d */
    BngDiv(quo, rem, topdigit, b[i], d);
    /* Quotient is current digit of result */
    a[i] = quo;
    /* Iterate with topdigit = remainder */
    topdigit = rem;
  }
  return topdigit;
}

#ifdef BngDivNeedsNormalization
/* {a,len-1} := {b,len} / d.  Return {b,len} modulo d.
   Require MSD of b < d. */
static bngdigit bng_generic_div_rem_digit
     (bng a/*[len-1]*/, bng b/*[len]*/, bngsize len, bngdigit d)
{
  bngdigit rem;
  int shift;

  /* Normalize d and b */
  shift = bng_leading_zero_bits(d);
  d <<= shift;
  bng_shift_left(b, len, shift);
  /* Do the division */
  rem = bng_div_rem_norm_digit(a, b, len, d);
  /* Undo normalization on b and remainder */
  bng_shift_right(b, len, shift);
  return rem >> shift;
}
#endif

/* {n+dlen, nlen-dlen} := {n,nlen} / {d, dlen}.
   {n, dlen} := {n,nlen} modulo {d, dlen}.
   Require nlen > dlen and MSD of n < MSD of d.
   (This implies MSD of d > 0). */
static void bng_generic_div_rem
       (bng n/*[nlen]*/, bngsize nlen,
        bng d/*[dlen]*/, bngsize dlen)
{
  bngdigit topden, quo, rem;
  int shift;
  bngsize i, j;

  /* Normalize d */
  shift = bng_leading_zero_bits(d[dlen - 1]);
  /* Note that no bits of n are lost by the following shift,
     since n[nlen-1] < d[dlen-1] */
  bng_shift_left(n, nlen, shift);
  bng_shift_left(d, dlen, shift);
  /* Special case if d is just one digit */
  if (dlen == 1) {
    *n = bng_div_rem_norm_digit(n + 1, n, nlen, *d);
  } else {
    topden = d[dlen - 1];
    /* Long division */
    for (j = nlen - 1; j >= dlen; j--) {
      i = j - dlen;
      /* At this point:
         - the current numerator is      n[j] : ...................... : n[0]
         - to be subtracted quo times:   d[dlen-1] : ... : d[0] : 0... : 0
         (there are i zeroes at the end) */
      /* Under-estimate the next digit of the quotient (quo) */
      if (topden + 1 == 0)
        quo = n[j];
      else
        BngDiv(quo, rem, n[j], n[j - 1], topden + 1);
      /* Subtract d * quo (shifted i places) from numerator */
      n[j] -= bng_mult_sub_digit(n + i, dlen, d, dlen, quo);
      /* Adjust if necessary */
      while (n[j] != 0 || bng_compare(n + i, dlen, d, dlen) >= 0) {
        /* Numerator is still bigger than shifted divisor.
           Increment quotient and subtract shifted divisor. */
        quo++;
        n[j] -= bng_sub(n + i, dlen, d, dlen, 0);
      }
      /* Store quotient digit */
      n[j] = quo;
    }
  }
  /* Undo normalization on remainder and divisor */
  bng_shift_right(n, dlen, shift);
  bng_shift_right(d, dlen, shift);
}

/**** Construction of the table of operations ****/

struct bng_operations bng_ops = {
  bng_generic_add_carry,
  bng_generic_add,
  bng_generic_sub_carry,
  bng_generic_sub,
  bng_generic_shift_left,
  bng_generic_shift_right,
  bng_generic_mult_add_digit,
  bng_generic_mult_sub_digit,
  bng_generic_mult_add,
  bng_generic_square_add,
  bng_generic_div_rem_norm_digit,
#ifdef BngDivNeedsNormalization
  bng_generic_div_rem_digit,
#else
  bng_generic_div_rem_norm_digit,
#endif
  bng_generic_div_rem
};

void bng_init(void)
{
#ifdef BNG_SETUP_OPS
  BNG_SETUP_OPS;
#endif
}
