/*************************************************************************
* Copyright (C) 2021 Intel Corporation
*
* Licensed under the Apache License,  Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 	http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law  or agreed  to  in  writing,  software
* distributed under  the License  is  distributed  on  an  "AS IS"  BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the  specific  language  governing  permissions  and
* limitations under the License.
*************************************************************************/

#include "owndefs.h"

#if (_IPP32E >= _IPP32E_K1)

#include "ifma_arith_n256.h"
#include "ifma_arith_p256.h"
#include "ifma_defs.h"

/* Constants */
#define LEN52 (5 + 3) /* 5 digits + 3 zero padding */

/*
 * EC NIST-P256 base point order
 * in 2^52 radix
 */
static const __ALIGN64 Ipp64u n256_x1[LEN52] = {
   0x0009cac2fc632551, 0x000ada7179e84f3b, 0x000fffffffbce6fa, 0x0000000fffffffff, 0x0000ffffffff0000, 0x0, 0x0, 0x0
};

static const __ALIGN64 Ipp64u n256_x2[LEN52] = {
   0x00039585f8c64aa2, 0x0005b4e2f3d09e77, 0x000fffffff79cdf5, 0x0000001fffffffff, 0x0001fffffffe0000, 0x0, 0x0, 0x0
};

/* k0 = (-1/n256) mod 2^52 */
static const __ALIGN64 Ipp64u n256_k0 = 0x1c8aaee00bc4f;

/*
 * r = 2^((52*5)) mod n256
 */
static const __ALIGN64 Ipp64u n256_r[LEN52] = {
   0x000353d039cdaaf0, 0x000258e8617b0c46, 0x0000000004319055, 0x000fff0000000000, 0x00000000000fffff, 0x0, 0x0, 0x0
};

/* To Montgomery conversion constant
 * r = 2^((52*5)*2) mod n256
 */
static const __ALIGN64 Ipp64u n256_rr[LEN52] = {
   0x0005cc0dea6dc3ba, 0x000192a067d8a084, 0x000bec59615571bb, 0x0001fc245b2392b6, 0x0000e12d9559d956, 0x0, 0x0, 0x0
};

static const __ALIGN64 Ipp64u ones[LEN52] = {
   1, 0, 0, 0, 0, 0, 0, 0
};

#define MUL_RED_ROUND(R, A, B, IDX)                                      \
   {                                                                     \
      const m512 Bi = permutexvar_i8(idx_b##IDX, (B));                   \
      m512 Rlo      = madd52lo_i64(zero, (A), Bi);                       \
      m512 tmp      = madd52hi_i64(zero, (A), Bi);                       \
                                                                         \
      (R) = add_i64((R), Rlo);                                           \
      /* broadcast R[0] */                                               \
      const m512 R0 = permutexvar_i8(idx_b0, (R));                       \
      const m512 Yi = madd52lo_i64(zero, k0, R0);                        \
      (R)           = madd52lo_i64((R), N, Yi);                          \
      tmp           = madd52hi_i64(tmp, N, Yi);                          \
      /* shift */                                                        \
      const m512 carry = maskz_srli_i64(maskone, (R), DIGIT_SIZE);       \
      tmp              = add_i64(tmp, carry);                            \
      (R)              = maskz_permutexvar_i8(mask_sr64, idx_sr64, (R)); \
      /* hi */                                                           \
      (R) = add_i64((R), tmp);                                           \
   }

/* R = (A*B) */
IPP_OWN_DEFN(m512, ifma_amm52_n256, (const m512 a, const m512 b))
{
   const m512 N        = loadu_i64(n256_x1); /* modulus */
   const m512 k0       = set1_i64(n256_k0);  /* k0 */
   const m512 zero     = setzero_i64();
   const mask8 maskone = 0x1;

   /* Index broadcast */
   const m512 idx_b0 = set_i64(REPL8(0x0706050403020100)); // 7,   6,  5,  4,  3,  2,  1,  0
   const m512 idx_b1 = set_i64(REPL8(0x0f0e0d0c0b0a0908)); // 15, 14, 13, 12, 11, 10,  9,  8
   const m512 idx_b2 = set_i64(REPL8(0x1716151413121110)); // 23, 22, 21, 20, 19, 18, 17, 16
   const m512 idx_b3 = set_i64(REPL8(0x1f1e1d1c1b1a1918)); // 31, 30, 29, 28, 27, 26, 25, 24
   const m512 idx_b4 = set_i64(REPL8(0x2726252423222120)); // 39, 38, 37, 36, 35, 34, 33, 32

   /* Mask to shift zmm register right on 64-bit */
   const mask64 mask_sr64 = 0x00ffffffffffffff;
   const m512 idx_sr64    = set_i64(0x0,            //  0,  0,  0,  0,  0,  0,  0,  0
                                 0x3f3e3d3c3b3a3938,  // 63, 62, 61, 60, 59, 58, 57, 56
                                 0x3736353433323130,  // 55, 54, 53, 52, 51, 50, 49, 48
                                 0x2f2e2d2c2b2a2928,  // 47, 46, 45, 44, 43, 42, 41, 40
                                 0x2726252423222120,  // 39, 38, 37, 36, 35, 34, 33, 32
                                 0x1f1e1d1c1b1a1918,  // 31, 30, 29, 28, 27, 26, 25, 24
                                 0x1716151413121110,  // 23, 22, 21, 20, 19, 18, 17, 16
                                 0x0f0e0d0c0b0a0908); // 15, 14, 13, 12, 11, 10, 9, 8

   m512 r = setzero_i64();

   MUL_RED_ROUND(r, a, b, 0)
   MUL_RED_ROUND(r, a, b, 1)
   MUL_RED_ROUND(r, a, b, 2)
   MUL_RED_ROUND(r, a, b, 3)
   MUL_RED_ROUND(r, a, b, 4)

   r = ifma_lnorm52(r);

   return r;
}

IPP_OWN_DEFN(m512, ifma_add52_n256, (const m512 a, const m512 b))
{
   const m512 zero = setzero_i64();
   const m512 Nx2  = loadu_i64(n256_x2);

   /* r = a + b */
   m512 r = add_i64(a, b);
   r      = ifma_lnorm52(r);

   /* t = r - N */
   m512 t = sub_i64(r, Nx2);
   t      = ifma_norm52(t);

   /* lt = t < 0 */
   const mask8 lt   = cmp_i64_mask(zero, srli_i64(t, DIGIT_SIZE - 1), _MM_CMPINT_LT);
   const mask8 mask = check_bit(lt, 4); // check sign in 5h digit
   /* mask != 0 ? r : t */
   r = mask_mov_i64(t, mask, r);

   return r;
}

static m512 ifma_ams52_n256(const m512 a)
{
   return ifma_amm52_n256(a, a);
}

IPP_OWN_DEFN(m512, ifma_fastred52_n256, (const m512 a))
{
   const m512 N    = loadu_i64(n256_x1);
   const m512 zero = setzero_i64();

   /* r = a - N */
   m512 r = sub_i64(a, N);
   r      = ifma_norm52(r);

   /* 1 < 0 */
   const mask8 lt   = cmp_i64_mask(zero, srli_i64(r, DIGIT_SIZE - 1), _MM_CMPINT_LT);
   const mask8 mask = check_bit(lt, 4); // check sign in 5th digit

   /* mask != 0 ? a : r */
   r = mask_mov_i64(r, mask, a);
   return r;
}

IPP_OWN_DEFN(m512, ifma_tomont52_n256, (const m512 a))
{
   return ifma_amm52_n256(a, loadu_i64(n256_rr));
}

IPP_OWN_DEFN(m512, ifma_frommont52_n256, (const m512 a))
{
   m512 r = ifma_amm52_n256(a, loadu_i64(ones));
   return ifma_fastred52_n256(r);
}

#define sqr(R, A) (R) = ifma_ams52_n256((A))
#define mul(R, A, B) (R) = ifma_amm52_n256((A), (B))

/*
 * computes r = 1/z = z^(n256-2) mod n256
 *
 * note: z in in Montgomery domain
 *       r in Montgomery domain
 */
__INLINE m512 ifma_ams52_n256_ntimes(const m512 a, int n)
{
   m512 r = a;
   for (; n > 0; --n) {
      sqr(r, r);
   }
   return r;
}

#define sqr_ntimes(R, A, N) (R) = ifma_ams52_n256_ntimes((A), (N))

/*
 * computes r = 1/z = z^(n256-2) mod n256
 *
 * note: z is in Montgomery domain
 *       (as soon mul() and sqr() below are amm-functions,
 *        the result is in Montgomery domain too)
 */

IPP_OWN_DEFN(m512, ifma_aminv52_n256, (const m512 z))
{
   const m512 r_n256 = loadu_i64(n256_r);
   int i;

   // pwr_z_Tbl[i][] = z^i, i=0,..,15
   __ALIGN64 m512 pwr_z_tbl[16];
   m512 r;

   pwr_z_tbl[0] = r_n256;
   pwr_z_tbl[1] = z;
   for (i = 2; i < 16; i += 2) {
      sqr(pwr_z_tbl[i], pwr_z_tbl[i / 2]);
      mul(pwr_z_tbl[i + 1], pwr_z_tbl[i], z);
   }

   // pwr = (n256-2) in big endian
   Ipp8u pwr[] = "\xFF\xFF\xFF\xFF\x00\x00\x00\x00"
                 "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
                 "\xBC\xE6\xFA\xAD\xA7\x17\x9E\x84"
                 "\xF3\xB9\xCA\xC2\xFC\x63\x25\x4F";
   // init r = 1
   r = r_n256;

   for (i = 0; i < 32; i++) {
      int v  = pwr[i];
      int hi = (v >> 4) & 0xF;
      int lo = v & 0xF;

      sqr(r, r);
      sqr(r, r);
      sqr(r, r);
      sqr(r, r);
      if (hi)
         mul(r, r, pwr_z_tbl[hi]);
      sqr(r, r);
      sqr(r, r);
      sqr(r, r);
      sqr(r, r);
      if (lo)
         mul(r, r, pwr_z_tbl[lo]);
   }

   return r;
}

#undef sqr
#undef mul
#undef sqr_ntimes

#endif // (_IPP32E >= _IPP32E_K1)
