blob: 9593ace313665a0bc6d38455fe5b0fc5559b08e7 [file] [log] [blame]
// SPDX-License-Identifier: BSD-2-Clause
/*
* Copyright (c) 2014, STMicroelectronics International N.V.
*/
#include "mpa.h"
/*************************************************************
*
* HELPER FUNCTIONS
*
*************************************************************/
/*------------------------------------------------------------
*
* These functions have ARM assembler implementations
*
*/
#if !defined(USE_ARM_ASM)
static mpa_dword_t __mpa_soft_div(mpa_dword_t num, mpa_word_t den_in,
mpa_word_t *rem)
{
mpa_dword_t quot = 0, qbit = 1;
mpa_dword_t den = den_in;
while ((int64_t) den >= 0) {
den <<= 1;
qbit <<= 1;
}
while (qbit) {
if (den <= num) {
num -= den;
quot += qbit;
}
den >>= 1;
qbit >>= 1;
}
if (rem)
*rem = (mpa_word_t) num;
return quot;
}
/* --------------------------------------------------------------------
* Function: __mpa_div_dword
*
* Calculates quotient and remainder of (n1*base + n0) / d.
* It is assumed that the quotient is small enough to fit a single word.
*/
mpa_word_t __mpa_div_dword(mpa_word_t n0,
mpa_word_t n1, mpa_word_t d, mpa_word_t *r)
{
#if defined(MPA_SUPPORT_DWORD_T)
mpa_dword_t n;
/* mpa_dword_t tmp_q; */
n = ((mpa_dword_t) n1 << WORD_SIZE) + n0;
return __mpa_soft_div(n, d, r);
/* tmp_q = n / d; */
/* *r = (mpa_word_t)(n % d); */
/* return tmp_q; */
#else
#error write non-dword code for __mpa_div_dword
#endif
}
#endif /* USE_ARM_ASM */
/* --------------------------------------------------------------------
* Function: __mpa_div_q_r_internal
*
* Finds the quotient and remainder when |op1| is divided by |op2|.
* q, r, op1 and op2 must all be distinct and not null.
* E.i. we get q and r such that |op1| = q*|op2| + r, 0<= r < |op2|.
* Assumptions: |op1| >= |op2| and |op2| >= 2^(WORD_SIZE)
*
*/
static void __mpa_div_q_r_internal(mpanum q,
mpanum r,
const mpanum op1,
const mpanum op2, mpa_scratch_mem pool)
{
mpa_word_t normshift;
int base_diff;
int i;
int cmp_same;
mpa_usize_t n; /* size of op1 */
mpa_usize_t t; /* size of op2 */
mpa_word_t w1;
mpa_word_t w2;
mpa_word_t w3;
mpa_word_t w4;
mpa_word_t w5;
mpanum p;
mpanum y;
mpanum x;
/*
* get temp storage
*/
mpa_alloc_static_temp_var(&p, pool);
mpa_alloc_static_temp_var(&y, pool);
mpa_copy(y, op2);
/*
* May need a large value for r since op1 may be an "oversized"
* value that is reduced. x is used for that internally and it's
* initial value is op1.
*/
mpa_alloc_static_temp_var(&x, pool);
mpa_copy(x, op1);
__mpanum_set_sign(x, MPA_POS_SIGN);
__mpanum_set_sign(y, MPA_POS_SIGN);
/*
* Normalization
*/
normshift = 0;
w1 = __mpanum_msw(y);
while (w1 < ((mpa_word_t)1 << (WORD_SIZE - 1))) {
normshift++;
w1 <<= 1;
}
if (normshift) {
mpa_shift_left(x, x, normshift);
mpa_shift_left(y, y, normshift);
}
n = x->size;
t = y->size;
base_diff = (int)n - t;
mpa_wipe(q);
/*
* check if op1 >= op2*base^(base_diff)
*/
/* mpa_shift_left(y, y, base_diff * WORD_SIZE); */
__mpa_shift_words_left(y, base_diff);
i = __mpanum_size(y);
cmp_same = 1;
while (cmp_same != 0 && i > 0) {
i--;
cmp_same = (__mpanum_get_word(i, x) == __mpanum_get_word(i, y));
}
if (!cmp_same && (__mpanum_get_word(i, x) > __mpanum_get_word(i, y))) {
q->d[base_diff] = 1;
mpa_sub(x, x, y, pool);
}
/* start main division loop */
i = x->size - 1;
while (i >= (int)t) {
if (__mpanum_get_word(i, x) ==
__mpanum_get_word(t + base_diff - 1, y)) {
q->d[i - t] = WORD_ALL_BITS_ONE;
} else {
q->d[i - t] =
__mpa_div_dword(__mpanum_get_word(i - 1, x),
__mpanum_get_word(i, x),
__mpanum_get_word(t + base_diff
- 1, y),
NULL);
}
while (1) {
/* set incoming carry to zero for all three ops */
w1 = 0;
w3 = 0;
w5 = 0;
__mpa_mul_add_word(q->d[i - t],
__mpanum_get_word(t + base_diff - 1,
y), &w2, &w1);
if (w1 > __mpanum_get_word(i, x))
goto loop_dec;
__mpa_mul_add_word(q->d[i - t],
__mpanum_get_word(t + base_diff - 2,
y), &w4, &w3);
__mpa_full_adder(w2, w3, &w3, &w5);
w2 = 0; /* used as carry */
__mpa_full_adder(w1, w5, &w5, &w2);
if (w2 || w5 > __mpanum_get_word(i, x))
goto loop_dec;
if (w5 < __mpanum_get_word(i, x))
break;
if (w3 > __mpanum_get_word(i - 1, x))
goto loop_dec;
if (w3 < __mpanum_get_word(i - 1, x))
break;
if (w4 > __mpanum_get_word(i - 2, x))
goto loop_dec;
break;
loop_dec:
q->d[i - t]--;
}
/* mpa_shift_right(y, y, WORD_SIZE); */
__mpa_shift_words_right(y, 1);
base_diff--;
mpa_mul_word(p, y, q->d[i - t], pool);
mpa_sub(x, x, p, pool);
if (__mpanum_sign(x) == MPA_NEG_SIGN) {
mpa_add(x, x, y, pool);
q->d[i - t]--;
}
i--;
}
/*
* Find size of q
*/
i = n - t;
while (i >= 0 && q->d[i] == 0)
i--;
q->size = i + 1;
/*
* Divide r by the normalization value and copy to output
*/
mpa_shift_right(x, x, normshift);
mpa_copy(r, x);
/*
* release p, y, and x
*/
mpa_free_static_temp_var(&p, pool);
mpa_free_static_temp_var(&y, pool);
mpa_free_static_temp_var(&x, pool);
}
/* --------------------------------------------------------------------
* Function: __mpa_div_q_r_internal_word
*
* Finds the quotient and remainder when |op1| is divided by |op2|, where
* op2 is a word.
* q, r and op1 must all be distinct and not null.
* E.i. we get q and r such that |op1| = q*|op2| + r, 0<= r < |op2|.
* Assumptions: |op1| >= |op2|.
*/
void __mpa_div_q_r_internal_word(mpanum q,
mpanum r,
const mpanum op1, const mpa_word_t op2)
{
int pos1;
mpa_word_t q_word;
mpa_word_t r_word;
mpa_word_t n1;
mpa_word_t n0;
int i;
if (__mpanum_size(op1) == 1) {
mpa_set_word(q, op1->d[0] / op2);
mpa_set_word(r, op1->d[0] % op2);
return;
}
mpa_copy(r, op1);
mpa_set_word(q, 0);
pos1 = (int)__mpanum_size(r) - 1;
n1 = 0;
n0 = r->d[pos1];
while (pos1 >= 0) {
q_word = __mpa_div_dword(n0, n1, op2, &r_word);
q->d[pos1] = q_word;
r->d[pos1] = r_word;
n1 = r->d[pos1];
n0 = r->d[--pos1];
}
/* set sizes of r and q */
r->size = (r->d[0] == 0 ? 0 : 1);
i = __mpanum_size(op1) - 1;
while (i >= 0 && q->d[i] == 0)
i--;
q->size = i + 1;
}
/* --------------------------------------------------------------------
* Function: __mpa_div_q_r
*
* Calculates the quotient and remainder when op1 is divided by op2.
* q, r, op1 and op2 must all be distinct and not null, except that op1
* may be equal to op2.
* There must be enough space allocated in q and r to handle the results.
* If op2 is zero a division with zero will be executed and the above layers
* can handle that as it pleases.
* The sign of q and r are shown in the table:
* __________________________________
* | Sign(q/r) | op1 >= 0 | op1 < 0 |
* |--------------------------------|
* | op2 > 0 | +/+ | -/- |
* |--------------------------------|
* | op2 < 0 | -/+ | +/- |
* |________________________________|
*/
void __mpa_div_q_r(mpanum q,
mpanum r,
const mpanum op1, const mpanum op2, mpa_scratch_mem pool)
{
int q_sign;
int cmp;
if (__mpanum_is_zero(op1)) {
mpa_set_word(q, 0);
mpa_set_word(r, 0);
return;
}
if (__mpanum_is_zero(op2)) {
/* generate a divide by zero error */
q_sign = 42 / op2->size;
return;
}
q_sign =
(__mpanum_sign(op1) !=
__mpanum_sign(op2)) ? MPA_NEG_SIGN : MPA_POS_SIGN;
cmp = __mpa_abs_cmp(op1, op2);
if (cmp == 0) {
mpa_set_word(q, 1);
mpa_set_word(r, 0);
return;
}
if (cmp > 0) { /* |op1| > |op2| */
if (__mpanum_size(op2) > 1)
__mpa_div_q_r_internal(q, r, op1, op2, pool);
else
__mpa_div_q_r_internal_word(q, r, op1, op2->d[0]);
} else { /* |op1| < |op2| */
mpa_set_word(q, 0);
mpa_copy(r, op1);
return;
}
__mpanum_set_sign(q, q_sign);
__mpanum_set_sign(r, __mpanum_sign(op1));
}
/*************************************************************
*
* LIB FUNCTIONS
*
*************************************************************/
/* --------------------------------------------------------------------
* Function: mpa_div
*
* Returns the quotient and the remainder of op1 divided by op2.
* q and r must be distinct variables.
* This function returns q and r such that
* op1 = q*op2 + r
* where 0 <= r < |op2|
*/
void mpa_div(mpanum q,
mpanum r, const mpanum op1, const mpanum op2, mpa_scratch_mem pool)
{
mpanum tmp_q;
mpanum tmp_r;
char mem_marker_q;
char mem_marker_r;
/* handle the case when q is one of the operands or zero */
if (q == op1 || q == op2 || q == 0) {
mpa_alloc_static_temp_var(&tmp_q, pool);
mem_marker_q = 1;
} else {
tmp_q = q;
mem_marker_q = 0;
}
/* handle the case when r is one of the operands or zero */
if (r == op1 || r == op2 || r == 0) {
mpa_alloc_static_temp_var(&tmp_r, pool);
mem_marker_r = 1;
} else {
tmp_r = r;
mem_marker_r = 0;
}
__mpa_div_q_r(tmp_q, tmp_r, op1, op2, pool);
if (q != 0)
mpa_copy(q, tmp_q);
if (mem_marker_q)
mpa_free_static_temp_var(&tmp_q, pool);
if (r != 0)
mpa_copy(r, tmp_r);
if (mem_marker_r)
mpa_free_static_temp_var(&tmp_r, pool);
}