Some small performance improvements on 32-bit architectures.

This commit is contained in:
Thomas Pornin 2019-01-03 19:50:54 +01:00
parent 08eb07825b
commit 001d094d14
7 changed files with 122 additions and 38 deletions

View file

@ -372,8 +372,7 @@ reduce_final_f255(uint32_t *d)
static void static void
f255_mul(uint32_t *d, const uint32_t *a, const uint32_t *b) f255_mul(uint32_t *d, const uint32_t *a, const uint32_t *b)
{ {
uint32_t t[18]; uint32_t t[18], cc;
uint64_t cc, w;
int i; int i;
/* /*
@ -389,21 +388,42 @@ f255_mul(uint32_t *d, const uint32_t *a, const uint32_t *b)
* offset 9*30 = 270, word 9+k must be added to word k with * offset 9*30 = 270, word 9+k must be added to word k with
* a factor of 19*2^15 = 622592. The extra bits in word 8 are also * a factor of 19*2^15 = 622592. The extra bits in word 8 are also
* added that way. * added that way.
*
* Keeping the carry on 32 bits helps with 32-bit architectures,
* and does not noticeably impact performance on 64-bit systems.
*/ */
cc = MUL31(t[8] >> 15, 19); cc = MUL15(t[8] >> 15, 19); /* at most 19*(2^15-1) = 622573 */
t[8] &= 0x7FFF; t[8] &= 0x7FFF;
for (i = 0; i < 9; i ++) { for (i = 0; i < 9; i ++) {
w = (uint64_t)t[i] + cc + MUL31(t[i + 9], 622592); uint64_t w;
w = (uint64_t)t[i] + (uint64_t)cc + MUL31(t[i + 9], 622592);
t[i] = (uint32_t)w & 0x3FFFFFFF; t[i] = (uint32_t)w & 0x3FFFFFFF;
cc = w >> 30; cc = (uint32_t)(w >> 30); /* at most 622592 */
} }
cc = MUL31(w >> 15, 19);
/*
* Original product was up to (2^256-1)^2, i.e. a 512-bit integer.
* This was split into two parts (upper of 257 bits, lower of 255
* bits), and the upper was added to the lower with a factor 19,
* which means that the intermediate value is less than 77*2^255
* (19*2^257 + 2^255). Therefore, the extra bits "t[8] >> 15" are
* less than 77, and the initial carry cc is at most 76*19 = 1444.
*/
cc = MUL15(t[8] >> 15, 19);
t[8] &= 0x7FFF; t[8] &= 0x7FFF;
for (i = 0; i < 9; i ++) { for (i = 0; i < 9; i ++) {
w = t[i] + cc; uint32_t z;
d[i] = (uint32_t)w & 0x3FFFFFFF;
cc = w >> 30; z = t[i] + cc;
d[i] = z & 0x3FFFFFFF;
cc = z >> 30;
} }
/*
* Final result is at most 2^255 + 1443. In particular, the last
* carry is necessarily 0, since t[8] was truncated to 15 bits.
*/
} }
/* /*
@ -415,8 +435,7 @@ f255_mul(uint32_t *d, const uint32_t *a, const uint32_t *b)
static void static void
f255_square(uint32_t *d, const uint32_t *a) f255_square(uint32_t *d, const uint32_t *a)
{ {
uint32_t t[18]; uint32_t t[18], cc;
uint64_t cc, w;
int i; int i;
/* /*
@ -428,24 +447,25 @@ f255_square(uint32_t *d, const uint32_t *a)
/* /*
* Modular reduction: each high word is added where necessary. * Modular reduction: each high word is added where necessary.
* Since the modulus is 2^255-19 and word 9 corresponds to * See f255_mul() for details on the reduction and carry limits.
* offset 9*30 = 270, word 9+k must be added to word k with
* a factor of 19*2^15 = 622592. The extra bits in word 8 are also
* added that way.
*/ */
cc = MUL31(t[8] >> 15, 19); cc = MUL15(t[8] >> 15, 19);
t[8] &= 0x7FFF; t[8] &= 0x7FFF;
for (i = 0; i < 9; i ++) { for (i = 0; i < 9; i ++) {
w = (uint64_t)t[i] + cc + MUL31(t[i + 9], 622592); uint64_t w;
w = (uint64_t)t[i] + (uint64_t)cc + MUL31(t[i + 9], 622592);
t[i] = (uint32_t)w & 0x3FFFFFFF; t[i] = (uint32_t)w & 0x3FFFFFFF;
cc = w >> 30; cc = (uint32_t)(w >> 30);
} }
cc = MUL31(w >> 15, 19); cc = MUL15(t[8] >> 15, 19);
t[8] &= 0x7FFF; t[8] &= 0x7FFF;
for (i = 0; i < 9; i ++) { for (i = 0; i < 9; i ++) {
w = t[i] + cc; uint32_t z;
d[i] = (uint32_t)w & 0x3FFFFFFF;
cc = w >> 30; z = t[i] + cc;
d[i] = z & 0x3FFFFFFF;
cc = z >> 30;
} }
} }
@ -515,20 +535,31 @@ static void
f255_mul_a24(uint32_t *d, const uint32_t *a) f255_mul_a24(uint32_t *d, const uint32_t *a)
{ {
int i; int i;
uint64_t cc, w; uint64_t w;
uint32_t cc;
/*
* a[] is over 256 bits, thus a[8] has length at most 16 bits.
* We single out the processing of the last word: intermediate
* value w is up to 121665*2^16, yielding a carry for the next
* loop of at most 19*(121665*2^16/2^15) = 4623289.
*/
cc = 0; cc = 0;
for (i = 0; i < 9; i ++) { for (i = 0; i < 8; i ++) {
w = MUL31(a[i], 121665) + cc; w = MUL31(a[i], 121665) + (uint64_t)cc;
d[i] = (uint32_t)w & 0x3FFFFFFF; d[i] = (uint32_t)w & 0x3FFFFFFF;
cc = w >> 30; cc = (uint32_t)(w >> 30);
} }
cc = MUL31((uint32_t)(w >> 15), 19); w = MUL31(a[8], 121665) + (uint64_t)cc;
d[8] &= 0x7FFF; d[8] = (uint32_t)w & 0x7FFF;
cc = MUL15((uint32_t)(w >> 15), 19);
for (i = 0; i < 9; i ++) { for (i = 0; i < 9; i ++) {
w = (uint64_t)d[i] + cc; uint32_t z;
d[i] = w & 0x3FFFFFFF;
cc = w >> 30; z = d[i] + cc;
d[i] = z & 0x3FFFFFFF;
cc = z >> 30;
} }
} }

View file

@ -1739,7 +1739,7 @@ p256_decode(p256_jacobian *P, const void *src, size_t len)
memcpy(P->y, ty, sizeof ty); memcpy(P->y, ty, sizeof ty);
memset(P->z, 0, sizeof P->z); memset(P->z, 0, sizeof P->z);
P->z[0] = 1; P->z[0] = 1;
return NEQ(bad, 0) ^ 1; return EQ(bad, 0);
} }
/* /*

View file

@ -1089,7 +1089,7 @@ p256_decode(p256_jacobian *P, const void *src, size_t len)
memcpy(P->y, ty, sizeof ty); memcpy(P->y, ty, sizeof ty);
memset(P->z, 0, sizeof P->z); memset(P->z, 0, sizeof P->z);
P->z[0] = 1; P->z[0] = 1;
return NEQ(bad, 0) ^ 1; return EQ(bad, 0);
} }
/* /*

View file

@ -114,6 +114,8 @@
#define BR_64 1 #define BR_64 1
#elif defined(__x86_64__) || defined(_M_X64) #elif defined(__x86_64__) || defined(_M_X64)
#define BR_64 1 #define BR_64 1
#elif defined(__aarch64__) || defined(_M_ARM64)
#define BR_64 1
#endif #endif
#endif #endif

View file

@ -29,16 +29,45 @@ void
br_i31_montymul(uint32_t *d, const uint32_t *x, const uint32_t *y, br_i31_montymul(uint32_t *d, const uint32_t *x, const uint32_t *y,
const uint32_t *m, uint32_t m0i) const uint32_t *m, uint32_t m0i)
{ {
/*
* Each outer loop iteration computes:
* d <- (d + xu*y + f*m) / 2^31
* We have xu <= 2^31-1 and f <= 2^31-1.
* Thus, if d <= 2*m-1 on input, then:
* 2*m-1 + 2*(2^31-1)*m <= (2^32)*m-1
* and the new d value is less than 2*m.
*
* We represent d over 31-bit words, with an extra word 'dh'
* which can thus be only 0 or 1.
*/
size_t len, len4, u, v; size_t len, len4, u, v;
uint64_t dh; uint32_t dh;
len = (m[0] + 31) >> 5; len = (m[0] + 31) >> 5;
len4 = len & ~(size_t)3; len4 = len & ~(size_t)3;
br_i31_zero(d, m[0]); br_i31_zero(d, m[0]);
dh = 0; dh = 0;
for (u = 0; u < len; u ++) { for (u = 0; u < len; u ++) {
/*
* The carry for each operation fits on 32 bits:
* d[v+1] <= 2^31-1
* xu*y[v+1] <= (2^31-1)*(2^31-1)
* f*m[v+1] <= (2^31-1)*(2^31-1)
* r <= 2^32-1
* (2^31-1) + 2*(2^31-1)*(2^31-1) + (2^32-1) = 2^63 - 2^31
* After division by 2^31, the new r is then at most 2^32-1
*
* Using a 32-bit carry has performance benefits on 32-bit
* systems; however, on 64-bit architectures, we prefer to
* keep the carry (r) in a 64-bit register, thus avoiding some
* "clear high bits" operations.
*/
uint32_t f, xu; uint32_t f, xu;
uint64_t r, zh; #if BR_64
uint64_t r;
#else
uint32_t r;
#endif
xu = x[u + 1]; xu = x[u + 1];
f = MUL31_lo((d[1] + MUL31_lo(x[u + 1], y[1])), m0i); f = MUL31_lo((d[1] + MUL31_lo(x[u + 1], y[1])), m0i);
@ -73,9 +102,14 @@ br_i31_montymul(uint32_t *d, const uint32_t *x, const uint32_t *y,
d[v] = (uint32_t)z & 0x7FFFFFFF; d[v] = (uint32_t)z & 0x7FFFFFFF;
} }
zh = dh + r; /*
d[len] = (uint32_t)zh & 0x7FFFFFFF; * Since the new dh can only be 0 or 1, the addition of
dh = zh >> 31; * the old dh with the carry MUST fit on 32 bits, and
* thus can be done into dh itself.
*/
dh += r;
d[len] = dh & 0x7FFFFFFF;
dh >>= 31;
} }
/* /*

View file

@ -45,7 +45,20 @@ br_i31_mulacc(uint32_t *d, const uint32_t *a, const uint32_t *b)
for (u = 0; u < blen; u ++) { for (u = 0; u < blen; u ++) {
uint32_t f; uint32_t f;
size_t v; size_t v;
/*
* Carry always fits on 31 bits; we want to keep it in a
* 32-bit register on 32-bit architectures (on a 64-bit
* architecture, cast down from 64 to 32 bits means
* clearing the high bits, which is not free; on a 32-bit
* architecture, the same operation really means ignoring
* the top register, which has negative or zero cost).
*/
#if BR_64
uint64_t cc; uint64_t cc;
#else
uint32_t cc;
#endif
f = b[1 + u]; f = b[1 + u];
cc = 0; cc = 0;

View file

@ -36,7 +36,11 @@ br_i32_mulacc(uint32_t *d, const uint32_t *a, const uint32_t *b)
for (u = 0; u < blen; u ++) { for (u = 0; u < blen; u ++) {
uint32_t f; uint32_t f;
size_t v; size_t v;
#if BR_64
uint64_t cc; uint64_t cc;
#else
uint32_t cc;
#endif
f = b[1 + u]; f = b[1 + u];
cc = 0; cc = 0;