blob: dfd4ca21dbd2848a10e00c013e73ab39b5a21cb3 [file]
/*
* Copyright (c) 2022, 2024, Intel Corporation. All rights reserved.
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include "macroAssembler_x86.hpp"
#include "stubGenerator_x86_64.hpp"
#define __ _masm->
// References:
// - (Normative) RFC7539 - ChaCha20 and Poly1305 for IETF Protocols
// - M. Goll and S. Gueron, "Vectorization of Poly1305 Message Authentication Code"
// - "The design of Poly1305" https://loup-vaillant.fr/tutorials/poly1305-design
// Explanation for the 'well known' modular arithmetic optimization, reduction by pseudo-Mersene prime 2^130-5:
//
// Reduction by 2^130-5 can be expressed as follows:
// ( ax2^130 + b ) mod 2^130-5 //i.e. number split along the 130-bit boundary
// = ( ax2^130 - 5xa + 5xa + b ) mod 2^130-5
// = ( ax(2^130 - 5) + 5xa + b ) mod 2^130-5 // i.e. adding multiples of modulus is a noop
// = ( 5xa + b ) mod 2^130-5
// QED: shows mathematically the well known algorithm of 'split the number down the middle, multiply upper and add'
// This is particularly useful to understand when combining with 'odd-sized' limbs that might cause misallignment
//
// Pseudocode for this file (in general):
// * used for poly1305_multiply_scalar
// x used for poly1305_multiply8_avx512
// lower-case variables are scalar numbers in 3x44-bit limbs (in gprs)
// upper-case variables are 8-element vector numbers in 3x44-bit limbs (in zmm registers)
// [ ] used to denote vector numbers (with their elements)
// Constant Pool:
ATTRIBUTE_ALIGNED(64) static const uint64_t POLY1305_PAD_MSG[] = {
0x0000010000000000, 0x0000010000000000,
0x0000010000000000, 0x0000010000000000,
0x0000010000000000, 0x0000010000000000,
0x0000010000000000, 0x0000010000000000,
};
static address poly1305_pad_msg() {
return (address)POLY1305_PAD_MSG;
}
ATTRIBUTE_ALIGNED(64) static const uint64_t POLY1305_MASK42[] = {
0x000003ffffffffff, 0x000003ffffffffff,
0x000003ffffffffff, 0x000003ffffffffff,
0x000003ffffffffff, 0x000003ffffffffff,
0x000003ffffffffff, 0x000003ffffffffff
};
static address poly1305_mask42() {
return (address)POLY1305_MASK42;
}
ATTRIBUTE_ALIGNED(64) static const uint64_t POLY1305_MASK44[] = {
0x00000fffffffffff, 0x00000fffffffffff,
0x00000fffffffffff, 0x00000fffffffffff,
0x00000fffffffffff, 0x00000fffffffffff,
0x00000fffffffffff, 0x00000fffffffffff,
};
static address poly1305_mask44() {
return (address)POLY1305_MASK44;
}
// Compute product for 8 16-byte message blocks,
// i.e. For each block, compute [a2 a1 a0] = [a2 a1 a0] x [r2 r1 r0]
//
// Each block/number is represented by 3 44-bit limb digits, start with multiplication
//
// a2 a1 a0
// x r2 r1 r0
// ----------------------------------
// a2xr0 a1xr0 a0xr0
// + a1xr1 a0xr1 5xa2xr1' (r1' = r1<<2)
// + a0xr2 5xa2xr2' 5xa1xr2' (r2' = r2<<2)
// ----------------------------------
// p2 p1 p0
//
// Then, propagate the carry (bits after bit 44) from lower limbs into higher limbs.
// Then, modular reduction from upper limb wrapped to lower limbs
//
// Math Note 1: 'carry propagation' from p2 to p0 involves multiplication by 5 (i.e. slightly modified modular reduction from above):
// ( p2x2^88 ) mod 2^130-5
// = ( p2'x2^88 + p2''x2^130) mod 2^130-5 // Split on 130-bit boudary
// = ( p2'x2^88 + p2''x2^130 - 5xp2'' + 5xp2'') mod 2^130-5
// = ( p2'x2^88 + p2''x(2^130 - 5) + 5xp2'') mod 2^130-5 // i.e. adding multiples of modulus is a noop
// = ( p2'x2^88 + 5xp2'') mod 2^130-5
//
// Math Note 2: R1P = 4*5*R1 and R2P = 4*5*R2; This precomputation allows simultaneous reduction and multiplication.
// This is not the standard 'multiply-upper-by-5', here is why the factor is 4*5 instead of 5.
// For example, partial product (a2xr2):
// (a2x2^88)x(r2x2^88) mod 2^130-5
// = (a2xr2 x 2^176) mod 2^130-5
// = (a2xr2 x 2^46x2^130) mod 2^130-5
// = (a2xr2x2^46 x 2^130- 5xa2xr2x2^46 + 5xa2xr2x2^46) mod 2^130-5
// = (a2xr2x2^46 x (2^130- 5) + 5xa2xr2x2^46) mod 2^130-5 // i.e. adding multiples of modulus is a noop
// = (5xa2xr2x2^46) mod 2^130-5
// = (a2x5xr2x2^2 x 2^44) mod 2^130-5 // Align to limb boudary
// = (a2x[5xr2x4] x 2^44) mod 2^130-5
// = (a2xR2P x 2^44) mod 2^130-5 // i.e. R2P = 4*5*R2
//
void StubGenerator::poly1305_multiply8_avx512(
const XMMRegister A0, const XMMRegister A1, const XMMRegister A2,
const XMMRegister R0, const XMMRegister R1, const XMMRegister R2, const XMMRegister R1P, const XMMRegister R2P,
const XMMRegister P0L, const XMMRegister P0H, const XMMRegister P1L, const XMMRegister P1H, const XMMRegister P2L, const XMMRegister P2H,
const XMMRegister TMP, const Register rscratch)
{
// Reset partial sums
__ evpxorq(P0L, P0L, P0L, Assembler::AVX_512bit);
__ evpxorq(P0H, P0H, P0H, Assembler::AVX_512bit);
__ evpxorq(P1L, P1L, P1L, Assembler::AVX_512bit);
__ evpxorq(P1H, P1H, P1H, Assembler::AVX_512bit);
__ evpxorq(P2L, P2L, P2L, Assembler::AVX_512bit);
__ evpxorq(P2H, P2H, P2H, Assembler::AVX_512bit);
// Calculate partial products
// p0 = a2xr1'
// p1 = a2xr2'
// p2 = a2xr0
__ evpmadd52luq(P0L, A2, R1P, Assembler::AVX_512bit);
__ evpmadd52huq(P0H, A2, R1P, Assembler::AVX_512bit);
__ evpmadd52luq(P1L, A2, R2P, Assembler::AVX_512bit);
__ evpmadd52huq(P1H, A2, R2P, Assembler::AVX_512bit);
__ evpmadd52luq(P2L, A2, R0, Assembler::AVX_512bit);
__ evpmadd52huq(P2H, A2, R0, Assembler::AVX_512bit);
// p0 += a0xr0
// p1 += a0xr1
// p2 += a0xr2
__ evpmadd52luq(P1L, A0, R1, Assembler::AVX_512bit);
__ evpmadd52huq(P1H, A0, R1, Assembler::AVX_512bit);
__ evpmadd52luq(P2L, A0, R2, Assembler::AVX_512bit);
__ evpmadd52huq(P2H, A0, R2, Assembler::AVX_512bit);
__ evpmadd52luq(P0L, A0, R0, Assembler::AVX_512bit);
__ evpmadd52huq(P0H, A0, R0, Assembler::AVX_512bit);
// p0 += a1xr2'
// p1 += a1xr0
// p2 += a1xr1
__ evpmadd52luq(P0L, A1, R2P, Assembler::AVX_512bit);
__ evpmadd52huq(P0H, A1, R2P, Assembler::AVX_512bit);
__ evpmadd52luq(P1L, A1, R0, Assembler::AVX_512bit);
__ evpmadd52huq(P1H, A1, R0, Assembler::AVX_512bit);
__ evpmadd52luq(P2L, A1, R1, Assembler::AVX_512bit);
__ evpmadd52huq(P2H, A1, R1, Assembler::AVX_512bit);
// Carry propagation:
// (Not quite aligned) | More mathematically correct:
// P2L P1L P0L | P2Lx2^88 + P1Lx2^44 + P0Lx2^0
// + P2H P1H P0H | + P2Hx2^140 + P1Hx2^96 + P0Hx2^52
// --------------------------- | -----------------------------------------------
// = P2H A2 A1 A0 | = P2Hx2^130 + A2x2^88 + A1x2^44 + A0x2^0
//
__ vpsrlq(TMP, P0L, 44, Assembler::AVX_512bit);
__ evpandq(A0, P0L, ExternalAddress(poly1305_mask44()), Assembler::AVX_512bit, rscratch); // Clear top 20 bits
__ vpsllq(P0H, P0H, 8, Assembler::AVX_512bit);
__ vpaddq(P0H, P0H, TMP, Assembler::AVX_512bit);
__ vpaddq(P1L, P1L, P0H, Assembler::AVX_512bit);
__ evpandq(A1, P1L, ExternalAddress(poly1305_mask44()), Assembler::AVX_512bit, rscratch); // Clear top 20 bits
__ vpsrlq(TMP, P1L, 44, Assembler::AVX_512bit);
__ vpsllq(P1H, P1H, 8, Assembler::AVX_512bit);
__ vpaddq(P1H, P1H, TMP, Assembler::AVX_512bit);
__ vpaddq(P2L, P2L, P1H, Assembler::AVX_512bit);
__ evpandq(A2, P2L, ExternalAddress(poly1305_mask42()), Assembler::AVX_512bit, rscratch); // Clear top 22 bits
__ vpsrlq(TMP, P2L, 42, Assembler::AVX_512bit);
__ vpsllq(P2H, P2H, 10, Assembler::AVX_512bit);
__ vpaddq(P2H, P2H, TMP, Assembler::AVX_512bit);
// Reduction: p2->a0->a1
// Multiply by 5 the highest bits (p2 is above 130 bits)
__ vpaddq(A0, A0, P2H, Assembler::AVX_512bit);
__ vpsllq(P2H, P2H, 2, Assembler::AVX_512bit);
__ vpaddq(A0, A0, P2H, Assembler::AVX_512bit);
__ vpsrlq(TMP, A0, 44, Assembler::AVX_512bit);
__ evpandq(A0, A0, ExternalAddress(poly1305_mask44()), Assembler::AVX_512bit, rscratch);
__ vpaddq(A1, A1, TMP, Assembler::AVX_512bit);
}
// Compute product for a single 16-byte message blocks
// - Assumes that r = [r1 r0] is only 128 bits (not 130)
// - Input [a2 a1 a0]; when only128 is set, input is 128 bits (i.e. a2==0)
// - Output [a2 a1 a0] is at least 130 bits (i.e. a2 is used regardless of only128)
//
// Note 1: a2 here is only two bits so anything above is subject of reduction.
// Note 2: Constant c1 = 5xr1 = r1 + (r1 << 2) simplifies multiply with less operations
//
// Flow of the code below is as follows:
//
// a2 a1 a0
// x r1 r0
// -----------------------------
// a2xr0 a1xr0 a0xr0
// + a0xr1
// + 5xa2xr1 5xa1xr1
// -----------------------------
// [0|L2L] [L1H|L1L] [L0H|L0L]
//
// Registers: t2:t1 t0:a0
//
// Completing the multiply and adding (with carry) 3x128-bit limbs into
// 192-bits again (3x64-bits):
// a0 = L0L
// a1 = L0H + L1L
// t2 = L1H + L2L
void StubGenerator::poly1305_multiply_scalar(
const Register a0, const Register a1, const Register a2,
const Register r0, const Register r1, const Register c1, bool only128,
const Register t0, const Register t1, const Register t2,
const Register mulql, const Register mulqh)
{
// mulq instruction requires/clobers rax, rdx (mulql, mulqh)
// t2:t1 = (a0 * r1)
__ movq(rax, r1);
__ mulq(a0);
__ movq(t1, rax);
__ movq(t2, rdx);
// t0:a0 = (a0 * r0)
__ movq(rax, r0);
__ mulq(a0);
__ movq(a0, rax); // a0 not used in other operations
__ movq(t0, rdx);
// t2:t1 += (a1 * r0)
__ movq(rax, r0);
__ mulq(a1);
__ addq(t1, rax);
__ adcq(t2, rdx);
// t0:a0 += (a1 * r1x5)
__ movq(rax, c1);
__ mulq(a1);
__ addq(a0, rax);
__ adcq(t0, rdx);
// Note: a2 is clamped to 2-bits,
// r1/r0 is clamped to 60-bits,
// their product is less than 2^64.
if (only128) { // Accumulator only 128 bits, i.e. a2 == 0
// just move and add t0-t1 to a1
__ movq(a1, t0);
__ addq(a1, t1);
__ adcq(t2, 0);
} else {
// t2:t1 += (a2 * r1x5)
__ movq(a1, a2); // use a1 for a2
__ imulq(a1, c1);
__ addq(t1, a1);
__ adcq(t2, 0);
__ movq(a1, t0); // t0:a0 => a1:a0
// t2:a1 += (a2 * r0):t1
__ imulq(a2, r0);
__ addq(a1, t1);
__ adcq(t2, a2);
}
// At this point, 3 64-bit limbs are in t2:a1:a0
// t2 can span over more than 2 bits so final partial reduction step is needed.
//
// Partial reduction (just to fit into 130 bits)
// a2 = t2 & 3
// k = (t2 & ~3) + (t2 >> 2)
// Y x4 + Y x1
// a2:a1:a0 += k
//
// Result will be in a2:a1:a0
__ movq(t0, t2);
__ movl(a2, t2); // DWORD
__ andq(t0, ~3);
__ shrq(t2, 2);
__ addq(t0, t2);
__ andl(a2, 3); // DWORD
// a2:a1:a0 += k (kept in t0)
__ addq(a0, t0);
__ adcq(a1, 0);
__ adcl(a2, 0); // DWORD
}
// Convert array of 128-bit numbers in quadwords (in D0:D1) into 128-bit numbers across 44-bit limbs (in L0:L1:L2)
// Optionally pad all the numbers (i.e. add 2^128)
//
// +-------------------------+-------------------------+
// D0:D1 | h0 h1 g0 g1 f0 f1 e0 e1 | d0 d1 c0 c1 b0 b1 a0 a1 |
// +-------------------------+-------------------------+
// +-------------------------+
// L2 | h2 d2 g2 c2 f2 b2 e2 a2 |
// +-------------------------+
// +-------------------------+
// L1 | h1 d1 g1 c1 f1 b1 e1 a1 |
// +-------------------------+
// +-------------------------+
// L0 | h0 d0 g0 c0 f0 b0 e0 a0 |
// +-------------------------+
//
void StubGenerator::poly1305_limbs_avx512(
const XMMRegister D0, const XMMRegister D1,
const XMMRegister L0, const XMMRegister L1, const XMMRegister L2, bool padMSG,
const XMMRegister TMP, const Register rscratch)
{
// Interleave blocks of data
__ evpunpckhqdq(TMP, D0, D1, Assembler::AVX_512bit);
__ evpunpcklqdq(L0, D0, D1, Assembler::AVX_512bit);
// Highest 42-bit limbs of new blocks
__ vpsrlq(L2, TMP, 24, Assembler::AVX_512bit);
if (padMSG) {
__ evporq(L2, L2, ExternalAddress(poly1305_pad_msg()), Assembler::AVX_512bit, rscratch); // Add 2^128 to all 8 final qwords of the message
}
// Middle 44-bit limbs of new blocks
__ vpsrlq(L1, L0, 44, Assembler::AVX_512bit);
__ vpsllq(TMP, TMP, 20, Assembler::AVX_512bit);
__ vpternlogq(L1, 0xA8, TMP, ExternalAddress(poly1305_mask44()), Assembler::AVX_512bit, rscratch); // (A OR B AND C)
// Lowest 44-bit limbs of new blocks
__ evpandq(L0, L0, ExternalAddress(poly1305_mask44()), Assembler::AVX_512bit, rscratch);
}
/**
* Copy 5x26-bit (unreduced) limbs stored at Register limbs into a2:a1:a0 (3x64-bit limbs)
*
* a2 is optional. When only128 is set, limbs are expected to fit into 128-bits (i.e. a1:a0 such as clamped R)
*/
void StubGenerator::poly1305_limbs(
const Register limbs, const Register a0, const Register a1, const Register a2,
const Register t0, const Register t1)
{
__ movq(a0, Address(limbs, 0));
__ movq(t0, Address(limbs, 8));
__ shlq(t0, 26);
__ addq(a0, t0);
__ movq(t0, Address(limbs, 16));
__ movq(t1, Address(limbs, 24));
__ movq(a1, t0);
__ shlq(t0, 52);
__ shrq(a1, 12);
__ shlq(t1, 14);
__ addq(a0, t0);
__ adcq(a1, t1);
__ movq(t0, Address(limbs, 32));
if (a2 != noreg) {
__ movq(a2, t0);
__ shrq(a2, 24);
}
__ shlq(t0, 40);
__ addq(a1, t0);
if (a2 != noreg) {
__ adcq(a2, 0);
// One round of reduction
// Take bits above 130 in a2, multiply by 5 and add to a2:a1:a0
__ movq(t0, a2);
__ andq(t0, ~3);
__ andq(a2, 3);
__ movq(t1, t0);
__ shrq(t1, 2);
__ addq(t0, t1);
__ addq(a0, t0);
__ adcq(a1, 0);
__ adcq(a2, 0);
}
}
/**
* Break 3x64-bit a2:a1:a0 limbs into 5x26-bit limbs and store out into 5 quadwords at address `limbs`
*/
void StubGenerator::poly1305_limbs_out(
const Register a0, const Register a1, const Register a2,
const Register limbs,
const Register t0, const Register t1)
{
// Extra round of reduction
// Take bits above 130 in a2, multiply by 5 and add to a2:a1:a0
__ movq(t0, a2);
__ andq(t0, ~3);
__ andq(a2, 3);
__ movq(t1, t0);
__ shrq(t1, 2);
__ addq(t0, t1);
__ addq(a0, t0);
__ adcq(a1, 0);
__ adcq(a2, 0);
// Chop a2:a1:a0 into 26-bit limbs
__ movl(t0, a0);
__ andl(t0, 0x3ffffff);
__ movq(Address(limbs, 0), t0);
__ shrq(a0, 26);
__ movl(t0, a0);
__ andl(t0, 0x3ffffff);
__ movq(Address(limbs, 8), t0);
__ shrq(a0, 26); // 12 bits left in a0, concatenate 14 from a1
__ movl(t0, a1);
__ shll(t0, 12);
__ addl(t0, a0);
__ andl(t0, 0x3ffffff);
__ movq(Address(limbs, 16), t0);
__ shrq(a1, 14); // already used up 14 bits
__ shlq(a2, 50); // a2 contains 2 bits when reduced, but $Element.limbs dont have to be fully reduced
__ addq(a1, a2); // put remaining bits into a1
__ movl(t0, a1);
__ andl(t0, 0x3ffffff);
__ movq(Address(limbs, 24), t0);
__ shrq(a1, 26);
__ movl(t0, a1);
//andl(t0, 0x3ffffff); doesnt have to be fully reduced, leave remaining bit(s)
__ movq(Address(limbs, 32), t0);
}
// This function consumes as many whole 16*16-byte blocks as available in input
// After execution, input and length will point at remaining (unprocessed) data
// and [a2 a1 a0] will contain the current accumulator value
//
// Math Note:
// Main loop in this function multiplies each message block by r^16; And some glue before and after..
// Proof (for brevity, split into 4 'rows' instead of 16):
//
// hash = ((((m1*r + m2)*r + m3)*r ... mn)*r
// = m1*r^n + m2*r^(n-1) + ... +mn_1*r^2 + mn*r // Horner's rule
//
// = m1*r^n + m4*r^(n-4) + m8*r^(n-8) ... // split into 4 groups for brevity, same applies to 16 blocks
// + m2*r^(n-1) + m5*r^(n-5) + m9*r^(n-9) ...
// + m3*r^(n-2) + m6*r^(n-6) + m10*r^(n-10) ...
// + m4*r^(n-3) + m7*r^(n-7) + m11*r^(n-11) ...
//
// = r^4 * (m1*r^(n-4) + m4*r^(n-8) + m8 *r^(n-16) ... + mn_3) // factor out r^4..r; same applies to 16 but r^16..r factors
// + r^3 * (m2*r^(n-4) + m5*r^(n-8) + m9 *r^(n-16) ... + mn_2)
// + r^2 * (m3*r^(n-4) + m6*r^(n-8) + m10*r^(n-16) ... + mn_1)
// + r^1 * (m4*r^(n-4) + m7*r^(n-8) + m11*r^(n-16) ... + mn_0) // Note last column: message group has no multiplier
//
// = (((m1*r^4 + m4)*r^4 + m8 )*r^4 ... + mn_3) * r^4 // reverse Horner's rule, for each group
// + (((m2*r^4 + m5)*r^4 + m9 )*r^4 ... + mn_2) * r^3 // each column is multiplied by r^4, except last
// + (((m3*r^4 + m6)*r^4 + m10)*r^4 ... + mn_1) * r^2
// + (((m4*r^4 + m7)*r^4 + m11)*r^4 ... + mn_0) * r^1
//
// Also see M. Goll and S. Gueron, "Vectorization of Poly1305 Message Authentication Code"
//
// Pseudocode:
// * used for poly1305_multiply_scalar
// x used for poly1305_multiply8_avx512
// lower-case variables are scalar numbers in 3x44-bit limbs (in gprs)
// upper-case variables are 8&16-element vector numbers in 3x44-bit limbs (in zmm registers)
//
// CL = a // [0 0 0 0 0 0 0 a]
// AL = poly1305_limbs_avx512(input)
// AH = poly1305_limbs_avx512(input+8)
// AL = AL + C
// input+=16, length-=16
//
// a = r
// a = a*r
// r^2 = a
// a = a*r
// r^3 = a
// r = a*r
// r^4 = a
//
// T = r^4 || r^3 || r^2 || r
// B = limbs(T) // [r^4 0 r^3 0 r^2 0 r^1 0 ]
// CL = B >> 1 // [ 0 r^4 0 r^3 0 r^2 0 r^1]
// R = r^4 || r^4 || .. // [r^4 r^4 r^4 r^4 r^4 r^4 r^4 r^4]
// B = BxR // [r^8 0 r^7 0 r^6 0 r^5 0 ]
// B = B | CL // [r^8 r^4 r^7 r^3 r^6 r^2 r^5 r^1]
// CL = B
// R = r^8 || r^8 || .. // [r^8 r^8 r^8 r^8 r^8 r^8 r^8 r^8]
// B = B x R // [r^16 r^12 r^15 r^11 r^14 r^10 r^13 r^9]
// CH = B
// R = r^16 || r^16 || .. // [r^16 r^16 r^16 r^16 r^16 r^16 r^16 r^16]
//
// for (;length>=16; input+=16, length-=16)
// BL = poly1305_limbs_avx512(input)
// BH = poly1305_limbs_avx512(input+8)
// AL = AL x R
// AH = AH x R
// AL = AL + BL
// AH = AH + BH
//
// AL = AL x CL
// AH = AH x CH
// A = AL + AH // 16->8 blocks
// T = A >> 4 // 8 ->4 blocks
// A = A + T
// T = A >> 2 // 4 ->2 blocks
// A = A + T
// T = A >> 1 // 2 ->1 blocks
// A = A + T
// a = A
//
// Register Map:
// GPRs:
// input = rdi
// length = rbx
// accumulator = rcx
// R = r8
// a0 = rsi
// a1 = r9
// a2 = r10
// r0 = r11
// r1 = r12
// c1 = r8;
// t0 = r13
// t1 = r14
// t2 = r15
// stack(rsp, rbp)
// mulq(rax, rdx) in poly1305_multiply_scalar
//
// ZMMs:
// D: xmm0-1
// TMP: xmm2
// T: xmm3-8
// A: xmm9-14
// B: xmm15-20
// C: xmm21-26
// R: xmm27-31
void StubGenerator::poly1305_process_blocks_avx512(
const Register input, const Register length,
const Register a0, const Register a1, const Register a2,
const Register r0, const Register r1, const Register c1)
{
Label L_process256Loop, L_process256LoopDone;
const Register t0 = r13;
const Register t1 = r14;
const Register t2 = r15;
const Register mulql = rax;
const Register mulqh = rdx;
const XMMRegister D0 = xmm0;
const XMMRegister D1 = xmm1;
const XMMRegister TMP = xmm2;
const XMMRegister T0 = xmm3;
const XMMRegister T1 = xmm4;
const XMMRegister T2 = xmm5;
const XMMRegister T3 = xmm6;
const XMMRegister T4 = xmm7;
const XMMRegister T5 = xmm8;
const XMMRegister A0 = xmm9;
const XMMRegister A1 = xmm10;
const XMMRegister A2 = xmm11;
const XMMRegister A3 = xmm12;
const XMMRegister A4 = xmm13;
const XMMRegister A5 = xmm14;
const XMMRegister B0 = xmm15;
const XMMRegister B1 = xmm16;
const XMMRegister B2 = xmm17;
const XMMRegister B3 = xmm18;
const XMMRegister B4 = xmm19;
const XMMRegister B5 = xmm20;
const XMMRegister C0 = xmm21;
const XMMRegister C1 = xmm22;
const XMMRegister C2 = xmm23;
const XMMRegister C3 = xmm24;
const XMMRegister C4 = xmm25;
const XMMRegister C5 = xmm26;
const XMMRegister R0 = xmm27;
const XMMRegister R1 = xmm28;
const XMMRegister R2 = xmm29;
const XMMRegister R1P = xmm30;
const XMMRegister R2P = xmm31;
// Spread accumulator into 44-bit limbs in quadwords C0,C1,C2
__ movq(t0, a0);
__ andq(t0, ExternalAddress(poly1305_mask44()), t1 /*rscratch*/); // First limb (Acc[43:0])
__ movq(C0, t0);
__ movq(t0, a1);
__ shrdq(a0, t0, 44);
__ andq(a0, ExternalAddress(poly1305_mask44()), t1 /*rscratch*/); // Second limb (Acc[77:52])
__ movq(C1, a0);
__ shrdq(a1, a2, 24);
__ andq(a1, ExternalAddress(poly1305_mask42()), t1 /*rscratch*/); // Third limb (Acc[129:88])
__ movq(C2, a1);
// To add accumulator, we must unroll first loop iteration
// Load first block of data (128 bytes) and pad
// A0 to have bits 0-43 of all 8 blocks in 8 qwords
// A1 to have bits 87-44 of all 8 blocks in 8 qwords
// A2 to have bits 127-88 of all 8 blocks in 8 qwords
__ evmovdquq(D0, Address(input, 0), Assembler::AVX_512bit);
__ evmovdquq(D1, Address(input, 64), Assembler::AVX_512bit);
poly1305_limbs_avx512(D0, D1, A0, A1, A2, true, TMP, t1 /*rscratch*/);
// Add accumulator to the fist message block
__ vpaddq(A0, A0, C0, Assembler::AVX_512bit);
__ vpaddq(A1, A1, C1, Assembler::AVX_512bit);
__ vpaddq(A2, A2, C2, Assembler::AVX_512bit);
// Load next blocks of data (128 bytes) and pad
// A3 to have bits 0-43 of all 8 blocks in 8 qwords
// A4 to have bits 87-44 of all 8 blocks in 8 qwords
// A5 to have bits 127-88 of all 8 blocks in 8 qwords
__ evmovdquq(D0, Address(input, 64*2), Assembler::AVX_512bit);
__ evmovdquq(D1, Address(input, 64*3), Assembler::AVX_512bit);
poly1305_limbs_avx512(D0, D1, A3, A4, A5, true, TMP, t1 /*rscratch*/);
__ subl(length, 16*16);
__ lea(input, Address(input,16*16));
// Compute the powers of R^1..R^4 and form 44-bit limbs of each
// T0 to have bits 0-127 in 4 quadword pairs
// T1 to have bits 128-129 in alternating 8 qwords
__ vpxorq(T1, T1, T1, Assembler::AVX_512bit);
__ movq(T2, r0);
__ vpinsrq(T2, T2, r1, 1);
__ vinserti32x4(T0, T0, T2, 3);
// Calculate R^2
__ movq(a0, r0);
__ movq(a1, r1);
// "Clever": a2 not set because poly1305_multiply_scalar has a flag to indicate 128-bit accumulator
poly1305_multiply_scalar(a0, a1, a2,
r0, r1, c1, true,
t0, t1, t2, mulql, mulqh);
__ movq(T2, a0);
__ vpinsrq(T2, T2, a1, 1);
__ vinserti32x4(T0, T0, T2, 2);
__ movq(T2, a2);
__ vinserti32x4(T1, T1, T2, 2);
// Calculate R^3
poly1305_multiply_scalar(a0, a1, a2,
r0, r1, c1, false,
t0, t1, t2, mulql, mulqh);
__ movq(T2, a0);
__ vpinsrq(T2, T2, a1, 1);
__ vinserti32x4(T0, T0, T2, 1);
__ movq(T2, a2);
__ vinserti32x4(T1, T1, T2, 1);
// Calculate R^4
poly1305_multiply_scalar(a0, a1, a2,
r0, r1, c1, false,
t0, t1, t2, mulql, mulqh);
__ movq(T2, a0);
__ vpinsrq(T2, T2, a1, 1);
__ vinserti32x4(T0, T0, T2, 0);
__ movq(T2, a2);
__ vinserti32x4(T1, T1, T2, 0);
// Interleave the powers of R^1..R^4 to form 44-bit limbs (half-empty)
// B0 to have bits 0-43 of all 4 blocks in alternating 8 qwords
// B1 to have bits 87-44 of all 4 blocks in alternating 8 qwords
// B2 to have bits 127-88 of all 4 blocks in alternating 8 qwords
__ vpxorq(T2, T2, T2, Assembler::AVX_512bit);
poly1305_limbs_avx512(T0, T2, B0, B1, B2, false, TMP, t1 /*rscratch*/);
// T1 contains the 2 highest bits of the powers of R
__ vpsllq(T1, T1, 40, Assembler::AVX_512bit);
__ evporq(B2, B2, T1, Assembler::AVX_512bit);
// Broadcast 44-bit limbs of R^4 into R0,R1,R2
__ mov(t0, a0);
__ andq(t0, ExternalAddress(poly1305_mask44()), t1 /*rscratch*/); // First limb (R^4[43:0])
__ evpbroadcastq(R0, t0, Assembler::AVX_512bit);
__ movq(t0, a1);
__ shrdq(a0, t0, 44);
__ andq(a0, ExternalAddress(poly1305_mask44()), t1 /*rscratch*/); // Second limb (R^4[87:44])
__ evpbroadcastq(R1, a0, Assembler::AVX_512bit);
__ shrdq(a1, a2, 24);
__ andq(a1, ExternalAddress(poly1305_mask42()), t1 /*rscratch*/); // Third limb (R^4[129:88])
__ evpbroadcastq(R2, a1, Assembler::AVX_512bit);
// Generate 4*5*R^4 into {R2P,R1P}
// Used as multiplier in poly1305_multiply8_avx512 so can
// ignore bottom limb and carry propagation
__ vpsllq(R1P, R1, 2, Assembler::AVX_512bit); // 4*R^4
__ vpsllq(R2P, R2, 2, Assembler::AVX_512bit);
__ vpaddq(R1P, R1P, R1, Assembler::AVX_512bit); // 5*R^4
__ vpaddq(R2P, R2P, R2, Assembler::AVX_512bit);
__ vpsllq(R1P, R1P, 2, Assembler::AVX_512bit); // 4*5*R^4
__ vpsllq(R2P, R2P, 2, Assembler::AVX_512bit);
// Move R^4..R^1 one element over
__ vpslldq(C0, B0, 8, Assembler::AVX_512bit);
__ vpslldq(C1, B1, 8, Assembler::AVX_512bit);
__ vpslldq(C2, B2, 8, Assembler::AVX_512bit);
// Calculate R^8-R^5
poly1305_multiply8_avx512(B0, B1, B2, // ACC=R^4..R^1
R0, R1, R2, R1P, R2P, // R^4..R^4, 4*5*R^4
T0, T1, T2, T3, T4, T5, TMP, t1 /*rscratch*/);
// Interleave powers of R: R^8 R^4 R^7 R^3 R^6 R^2 R^5 R
__ evporq(B0, B0, C0, Assembler::AVX_512bit);
__ evporq(B1, B1, C1, Assembler::AVX_512bit);
__ evporq(B2, B2, C2, Assembler::AVX_512bit);
// Store R^8-R for later use
__ evmovdquq(C0, B0, Assembler::AVX_512bit);
__ evmovdquq(C1, B1, Assembler::AVX_512bit);
__ evmovdquq(C2, B2, Assembler::AVX_512bit);
// Broadcast R^8
__ vpbroadcastq(R0, B0, Assembler::AVX_512bit);
__ vpbroadcastq(R1, B1, Assembler::AVX_512bit);
__ vpbroadcastq(R2, B2, Assembler::AVX_512bit);
// Generate 4*5*R^8
__ vpsllq(R1P, R1, 2, Assembler::AVX_512bit);
__ vpsllq(R2P, R2, 2, Assembler::AVX_512bit);
__ vpaddq(R1P, R1P, R1, Assembler::AVX_512bit); // 5*R^8
__ vpaddq(R2P, R2P, R2, Assembler::AVX_512bit);
__ vpsllq(R1P, R1P, 2, Assembler::AVX_512bit); // 4*5*R^8
__ vpsllq(R2P, R2P, 2, Assembler::AVX_512bit);
// Calculate R^16-R^9
poly1305_multiply8_avx512(B0, B1, B2, // ACC=R^8..R^1
R0, R1, R2, R1P, R2P, // R^8..R^8, 4*5*R^8
T0, T1, T2, T3, T4, T5, TMP, t1 /*rscratch*/);
// Store R^16-R^9 for later use
__ evmovdquq(C3, B0, Assembler::AVX_512bit);
__ evmovdquq(C4, B1, Assembler::AVX_512bit);
__ evmovdquq(C5, B2, Assembler::AVX_512bit);
// Broadcast R^16
__ vpbroadcastq(R0, B0, Assembler::AVX_512bit);
__ vpbroadcastq(R1, B1, Assembler::AVX_512bit);
__ vpbroadcastq(R2, B2, Assembler::AVX_512bit);
// Generate 4*5*R^16
__ vpsllq(R1P, R1, 2, Assembler::AVX_512bit);
__ vpsllq(R2P, R2, 2, Assembler::AVX_512bit);
__ vpaddq(R1P, R1P, R1, Assembler::AVX_512bit); // 5*R^16
__ vpaddq(R2P, R2P, R2, Assembler::AVX_512bit);
__ vpsllq(R1P, R1P, 2, Assembler::AVX_512bit); // 4*5*R^16
__ vpsllq(R2P, R2P, 2, Assembler::AVX_512bit);
// VECTOR LOOP: process 16 * 16-byte message block at a time
__ bind(L_process256Loop);
__ cmpl(length, 16*16);
__ jcc(Assembler::less, L_process256LoopDone);
// Load and interleave next block of data (128 bytes)
__ evmovdquq(D0, Address(input, 0), Assembler::AVX_512bit);
__ evmovdquq(D1, Address(input, 64), Assembler::AVX_512bit);
poly1305_limbs_avx512(D0, D1, B0, B1, B2, true, TMP, t1 /*rscratch*/);
// Load and interleave next block of data (128 bytes)
__ evmovdquq(D0, Address(input, 64*2), Assembler::AVX_512bit);
__ evmovdquq(D1, Address(input, 64*3), Assembler::AVX_512bit);
poly1305_limbs_avx512(D0, D1, B3, B4, B5, true, TMP, t1 /*rscratch*/);
poly1305_multiply8_avx512(A0, A1, A2, // MSG/ACC 16 blocks
R0, R1, R2, R1P, R2P, // R^16..R^16, 4*5*R^16
T0, T1, T2, T3, T4, T5, TMP, t1 /*rscratch*/);
poly1305_multiply8_avx512(A3, A4, A5, // MSG/ACC 16 blocks
R0, R1, R2, R1P, R2P, // R^16..R^16, 4*5*R^16
T0, T1, T2, T3, T4, T5, TMP, t1 /*rscratch*/);
__ vpaddq(A0, A0, B0, Assembler::AVX_512bit); // Add low 42-bit bits from new blocks to accumulator
__ vpaddq(A1, A1, B1, Assembler::AVX_512bit); // Add medium 42-bit bits from new blocks to accumulator
__ vpaddq(A2, A2, B2, Assembler::AVX_512bit); // Add highest bits from new blocks to accumulator
__ vpaddq(A3, A3, B3, Assembler::AVX_512bit); // Add low 42-bit bits from new blocks to accumulator
__ vpaddq(A4, A4, B4, Assembler::AVX_512bit); // Add medium 42-bit bits from new blocks to accumulator
__ vpaddq(A5, A5, B5, Assembler::AVX_512bit); // Add highest bits from new blocks to accumulator
__ subl(length, 16*16);
__ lea(input, Address(input,16*16));
__ jmp(L_process256Loop);
__ bind(L_process256LoopDone);
// Tail processing: Need to multiply ACC by R^16..R^1 and add it all up into a single scalar value
// Generate 4*5*[R^16..R^9] (ignore lowest limb)
// Use D0 ~ R1P, D1 ~ R2P for higher powers
__ vpsllq(R1P, C4, 2, Assembler::AVX_512bit);
__ vpsllq(R2P, C5, 2, Assembler::AVX_512bit);
__ vpaddq(R1P, R1P, C4, Assembler::AVX_512bit); // 5*R^8
__ vpaddq(R2P, R2P, C5, Assembler::AVX_512bit);
__ vpsllq(D0, R1P, 2, Assembler::AVX_512bit); // 4*5*R^8
__ vpsllq(D1, R2P, 2, Assembler::AVX_512bit);
// Generate 4*5*[R^8..R^1] (ignore lowest limb)
__ vpsllq(R1P, C1, 2, Assembler::AVX_512bit);
__ vpsllq(R2P, C2, 2, Assembler::AVX_512bit);
__ vpaddq(R1P, R1P, C1, Assembler::AVX_512bit); // 5*R^8
__ vpaddq(R2P, R2P, C2, Assembler::AVX_512bit);
__ vpsllq(R1P, R1P, 2, Assembler::AVX_512bit); // 4*5*R^8
__ vpsllq(R2P, R2P, 2, Assembler::AVX_512bit);
poly1305_multiply8_avx512(A0, A1, A2, // MSG/ACC 16 blocks
C3, C4, C5, D0, D1, // R^16-R^9, R1P, R2P
T0, T1, T2, T3, T4, T5, TMP, t1 /*rscratch*/);
poly1305_multiply8_avx512(A3, A4, A5, // MSG/ACC 16 blocks
C0, C1, C2, R1P, R2P, // R^8-R, R1P, R2P
T0, T1, T2, T3, T4, T5, TMP, t1 /*rscratch*/);
// Add all blocks (horizontally)
// 16->8 blocks
__ vpaddq(A0, A0, A3, Assembler::AVX_512bit);
__ vpaddq(A1, A1, A4, Assembler::AVX_512bit);
__ vpaddq(A2, A2, A5, Assembler::AVX_512bit);
// 8 -> 4 blocks
__ vextracti64x4(T0, A0, 1);
__ vextracti64x4(T1, A1, 1);
__ vextracti64x4(T2, A2, 1);
__ vpaddq(A0, A0, T0, Assembler::AVX_256bit);
__ vpaddq(A1, A1, T1, Assembler::AVX_256bit);
__ vpaddq(A2, A2, T2, Assembler::AVX_256bit);
// 4 -> 2 blocks
__ vextracti32x4(T0, A0, 1);
__ vextracti32x4(T1, A1, 1);
__ vextracti32x4(T2, A2, 1);
__ vpaddq(A0, A0, T0, Assembler::AVX_128bit);
__ vpaddq(A1, A1, T1, Assembler::AVX_128bit);
__ vpaddq(A2, A2, T2, Assembler::AVX_128bit);
// 2 -> 1 blocks
__ vpsrldq(T0, A0, 8, Assembler::AVX_128bit);
__ vpsrldq(T1, A1, 8, Assembler::AVX_128bit);
__ vpsrldq(T2, A2, 8, Assembler::AVX_128bit);
// Finish folding and clear second qword
__ mov64(t0, 0xfd);
__ kmovql(k1, t0);
__ evpaddq(A0, k1, A0, T0, false, Assembler::AVX_512bit);
__ evpaddq(A1, k1, A1, T1, false, Assembler::AVX_512bit);
__ evpaddq(A2, k1, A2, T2, false, Assembler::AVX_512bit);
// Carry propagation
__ vpsrlq(D0, A0, 44, Assembler::AVX_512bit);
__ evpandq(A0, A0, ExternalAddress(poly1305_mask44()), Assembler::AVX_512bit, t1 /*rscratch*/); // Clear top 20 bits
__ vpaddq(A1, A1, D0, Assembler::AVX_512bit);
__ vpsrlq(D0, A1, 44, Assembler::AVX_512bit);
__ evpandq(A1, A1, ExternalAddress(poly1305_mask44()), Assembler::AVX_512bit, t1 /*rscratch*/); // Clear top 20 bits
__ vpaddq(A2, A2, D0, Assembler::AVX_512bit);
__ vpsrlq(D0, A2, 42, Assembler::AVX_512bit);
__ evpandq(A2, A2, ExternalAddress(poly1305_mask42()), Assembler::AVX_512bit, t1 /*rscratch*/); // Clear top 22 bits
__ vpsllq(D1, D0, 2, Assembler::AVX_512bit);
__ vpaddq(D0, D0, D1, Assembler::AVX_512bit);
__ vpaddq(A0, A0, D0, Assembler::AVX_512bit);
// Put together A (accumulator)
__ movq(a0, A0);
__ movq(t0, A1);
__ movq(t1, t0);
__ shlq(t1, 44);
__ shrq(t0, 20);
__ movq(a2, A2);
__ movq(a1, a2);
__ shlq(a1, 24);
__ shrq(a2, 40);
__ addq(a0, t1);
__ adcq(a1, t0);
__ adcq(a2, 0);
// Cleanup
// Zero out zmm0-zmm31.
__ vzeroall();
for (XMMRegister rxmm = xmm16; rxmm->is_valid(); rxmm = rxmm->successor()) {
__ vpxorq(rxmm, rxmm, rxmm, Assembler::AVX_512bit);
}
}
// This function consumes as many whole 16-byte blocks as available in input
// After execution, input and length will point at remaining (unprocessed) data
// and accumulator will point to the current accumulator value
address StubGenerator::generate_poly1305_processBlocks() {
__ align(CodeEntryAlignment);
StubGenStubId stub_id = StubGenStubId::poly1305_processBlocks_id;
StubCodeMark mark(this, stub_id);
address start = __ pc();
__ enter();
// Save all 'SOE' registers
__ push(rbx);
#ifdef _WIN64
__ push(rsi);
__ push(rdi);
#endif
__ push(r12);
__ push(r13);
__ push(r14);
__ push(r15);
// Register Map
const Register input = rdi; // msg
const Register length = rbx; // msg length in bytes
const Register accumulator = rcx;
const Register R = r8;
const Register a0 = rsi; // [in/out] accumulator bits 63..0
const Register a1 = r9; // [in/out] accumulator bits 127..64
const Register a2 = r10; // [in/out] accumulator bits 195..128
const Register r0 = r11; // R constant bits 63..0
const Register r1 = r12; // R constant bits 127..64
const Register c1 = r8; // 5*R (upper limb only)
const Register t0 = r13;
const Register t1 = r14;
const Register t2 = r15;
const Register mulql = rax;
const Register mulqh = rdx;
// Normalize input
// pseudo-signature: void poly1305_processBlocks(byte[] input, int length, int[5] accumulator, int[5] R)
// input, a, r pointers point at first array element
// java headers bypassed in LibraryCallKit::inline_poly1305_processBlocks
#ifdef _WIN64
// c_rarg0 - rcx
// c_rarg1 - rdx
// c_rarg2 - r8
// c_rarg3 - r9
__ mov(input, c_rarg0);
__ mov(length, c_rarg1);
__ mov(accumulator, c_rarg2);
__ mov(R, c_rarg3);
#else
// c_rarg0 - rdi
// c_rarg1 - rsi
// c_rarg2 - rdx
// c_rarg3 - rcx
// dont clobber R, args copied out-of-order
__ mov(length, c_rarg1);
__ mov(R, c_rarg3);
__ mov(accumulator, c_rarg2);
#endif
Label L_process16Loop, L_process16LoopDone;
// Load R into r1:r0
poly1305_limbs(R, r0, r1, noreg, t0, t1);
// Compute 5*R (Upper limb only)
__ movq(c1, r1);
__ shrq(c1, 2);
__ addq(c1, r1); // c1 = r1 + (r1 >> 2)
// Load accumulator into a2:a1:a0
poly1305_limbs(accumulator, a0, a1, a2, t0, t1);
// VECTOR LOOP: Minimum of 256 bytes to run vectorized code
__ cmpl(length, 16*16);
__ jcc(Assembler::less, L_process16Loop);
if (UseAVX > 2) {
poly1305_process_blocks_avx512(input, length,
a0, a1, a2,
r0, r1, c1);
} else {
poly1305_process_blocks_avx2(input, length,
a0, a1, a2,
r0, r1, c1);
}
// SCALAR LOOP: process one 16-byte message block at a time
__ bind(L_process16Loop);
__ cmpl(length, 16);
__ jcc(Assembler::less, L_process16LoopDone);
__ addq(a0, Address(input,0));
__ adcq(a1, Address(input,8));
__ adcq(a2,1);
poly1305_multiply_scalar(a0, a1, a2,
r0, r1, c1, false,
t0, t1, t2, mulql, mulqh);
__ subl(length, 16);
__ lea(input, Address(input,16));
__ jmp(L_process16Loop);
__ bind(L_process16LoopDone);
// Write output
poly1305_limbs_out(a0, a1, a2, accumulator, t0, t1);
__ pop(r15);
__ pop(r14);
__ pop(r13);
__ pop(r12);
#ifdef _WIN64
__ pop(rdi);
__ pop(rsi);
#endif
__ pop(rbx);
__ leave();
__ ret(0);
return start;
}
/*
The AVX2 implementation below is directly based on the AVX2 Poly1305 hash computation as
implemented in Intel(R) Multi-Buffer Crypto for IPsec Library.
(url: https://github.com/intel/intel-ipsec-mb/blob/main/lib/avx2_t3/poly_fma_avx2.asm)
Additional references:
[1] Goll M, Gueron S., "Vectorization of Poly1305 message authentication code",
12th International Conference on Information Technology-New Generations,
2015 Apr 13 (pp. 145-150). IEEE.
[2] Bhattacharyya S, Sarkar P., "Improved SIMD implementation of Poly1305",
IET Information Security. 2020 Sep;14(5):521-30.
Note: a compact summary of the Goll-Gueron AVX2 algorithm developed in [1] is presented in [2].
[3] Wikipedia, "Parallel evaluation of Horner's method",
(url: https://en.wikipedia.org/wiki/Horner%27s_method)
----------------------------------------------------------
Poly1305 AVX2 algorithm:
Let the 32-byte one-time key be partitioned into two equal parts R and K.
Let R be the 16-byte secret key used for polynomial evaluation.
Let K be the 16-byte secret key.
Let Z_P be prime field over which the polynomial is evaluated. Let P = 2^130 - 5 be the prime.
Let M be the message which can be represented as a concatenation (||) of 'l' 16-byte blocks M[i].
i.e., M = M[0] || M[1] || ... || M[i] || ... || M[l-2] || M[l-1]
To create the coefficients C[i] for polynomial evaluation over Z_P, each 16-byte (i.e., 128-bit)
message block M[i] is concatenated with bits '10' to make a 130-bit block.
The last block (<= 16-byte length) is concatenated with 1 followed by 0s to make a 130-bit block.
Therefore, we define
C[i] = M[i] || '10' for 0 <= i <= l-2 ;
C[l-1] = M[i] || '10...0'
such that, length(C[i]) = 130 bits, for i ∈ [0, l).
Let * indicate scalar multiplication (i.e., w = u * v);
Let × indicate scalar multiplication followed by reduction modulo P (i.e., z = u × v = {(u * v) mod P})
POLY1305_MAC = (POLY1305_EVAL_POLYNOMIAL(C, R, P) + K) mod 2^128; where,
POLY1305_EVAL_POLYNOMIAL(C, R, P) = {C[0] * R^l + C[1] * R^(l-1) + ... + C[l-2] * R^2 + C[l-1] * R} mod P
= R × {C[0] × R^(l-1) + C[1] × R^(l-2) + ... + C[l-2] × R + C[l-1]}
= R × Polynomial(R; C[0], C[1], ... ,C[l-2], C[l-1])
Where,
Polynomial(R; C[0], C[1], ... ,C[l-2], C[l-1]) = Σ{C[i] × R^(l-i-1)} for i ∈ [0, l)
----------------------------------------------------------
Parallel evaluation of POLY1305_EVAL_POLYNOMIAL(C, R, P):
Let the number of message blocks l = 4*l' + ρ where ρ = l mod 4.
Using k-way parallel Horner's evaluation [3], for k = 4, we define SUM below:
SUM = R^4 × Polynomial(R^4; C[0], C[4], C[8] ... , C[4l'-4]) +
R^3 × Polynomial(R^4; C[1], C[5], C[9] ... , C[4l'-3]) +
R^2 × Polynomial(R^4; C[2], C[6], C[10] ... , C[4l'-2]) +
R^1 × Polynomial(R^4; C[3], C[7], C[11] ... , C[4l'-1]) +
Then,
POLY1305_EVAL_POLYNOMIAL(C, R, P) = SUM if ρ = 0 (i.e., l is multiple of 4)
= R × Polynomial(R; SUM + C[l-ρ], C[l-ρ+1], ... , C[l-1]) if ρ > 0
----------------------------------------------------------
Gall-Gueron[1] 4-way SIMD Algorithm[2] for POLY1305_EVAL_POLYNOMIAL(C, R, P):
Define mathematical vectors (not same as SIMD vector lanes) as below:
R4321 = [R^4, R^3, R^2, R^1];
R4444 = [R^4, R^4, R^4, R^4];
COEF[i] = [C[4i], C[4i+1], C[4i+2], C[4i+3]] for i ∈ [0, l'). For example, COEF[0] and COEF[1] shown below.
COEF[0] = [C0, C1, C2, C3]
COEF[1] = [C4, C5, C6, C7]
T = [T0, T1, T2, T3] be a temporary vector
ACC = [acc, 0, 0, 0]; acc has hash from previous computations (if any), otherwise 0.
⊗ indicates component-wise vector multiplication followed by modulo reduction
⊕ indicates component-wise vector addition, + indicates scalar addition
POLY1305_EVAL_POLYNOMIAL(C, R, P) {
T ← ACC; # load accumulator
T ← T ⊕ COEF[0]; # add accumulator to the first 4 blocks
Compute R4321, R4444;
# SIMD loop
l' = floor(l/4); # operate on 4 blocks at a time
for (i = 1 to l'-1):
T ← (R4444 ⊗ T) ⊕ COEF[i];
T ← R4321 ⊗ T;
SUM ← T0 + T1 + T2 + T3;
# Scalar tail processing
if (ρ > 0):
SUM ← R × Polynomial(R; SUM + C[l-ρ], C[l-ρ+1], ... , C[l-1]);
return SUM;
}
Notes:
(1) Each 130-bit block is represented using three 44-bit limbs (most significant limb is only 42-bit).
(The Goll-Gueron implementation[1] uses five 26-bit limbs instead).
(2) Each component of the mathematical vectors is a 130-bit value. The above mathemetical vectors are not to be confused with SIMD vector lanes.
(3) Each AVX2 YMM register can store four 44-bit limbs in quadwords. Since each 130-bit message block is represented using 3 limbs,
to store all the limbs of 4 different 130-bit message blocks, we need 3 YMM registers in total.
(4) In the AVX2 implementation, multiplication followed by modulo reduction and addition are performed for 4 blocks at a time.
*/
void StubGenerator::poly1305_process_blocks_avx2(
const Register input, const Register length,
const Register a0, const Register a1, const Register a2,
const Register r0, const Register r1, const Register c1)
{
Label L_process256Loop, L_process256LoopDone;
const Register t0 = r13;
const Register t1 = r14;
const Register t2 = r15;
const Register mulql = rax;
const Register mulqh = rdx;
const XMMRegister YMM_ACC0 = xmm0;
const XMMRegister YMM_ACC1 = xmm1;
const XMMRegister YMM_ACC2 = xmm2;
const XMMRegister YTMP1 = xmm3;
const XMMRegister YTMP2 = xmm4;
const XMMRegister YTMP3 = xmm5;
const XMMRegister YTMP4 = xmm6;
const XMMRegister YTMP5 = xmm7;
const XMMRegister YTMP6 = xmm8;
const XMMRegister YTMP7 = xmm9;
const XMMRegister YTMP8 = xmm10;
const XMMRegister YTMP9 = xmm11;
const XMMRegister YTMP10 = xmm12;
const XMMRegister YTMP11 = xmm13;
const XMMRegister YTMP12 = xmm14;
const XMMRegister YTMP13 = xmm15;
const XMMRegister YMM_R0 = YTMP11;
const XMMRegister YMM_R1 = YTMP12;
const XMMRegister YMM_R2 = YTMP13;
// XWORD aliases of YMM registers (for convenience)
const XMMRegister XTMP1 = YTMP1;
const XMMRegister XTMP2 = YTMP2;
const XMMRegister XTMP3 = YTMP3;
// Setup stack frame
// Save rbp and rsp
__ push(rbp);
__ movq(rbp, rsp);
// Align stack and reserve space
__ andq(rsp, -32);
__ subptr(rsp, 32*8);
/* Compute the following steps of POLY1305_EVAL_POLYNOMIAL algorithm
T ← ACC
T ← T ⊕ COEF[0];
*/
// Spread accumulator into 44-bit limbs in quadwords
// Accumulator limbs to be stored in YTMP1,YTMP2,YTMP3
// First limb (Acc[43:0])
__ movq(t0, a0);
__ andq(t0, ExternalAddress(poly1305_mask44()), t1 /*rscratch*/);
__ movq(XTMP1, t0);
// Second limb (Acc[87:44])
__ movq(t0, a1);
__ shrdq(a0, t0, 44);
__ andq(a0, ExternalAddress(poly1305_mask44()), t1 /*rscratch*/);
__ movq(XTMP2, a0);
// Third limb (Acc[129:88])
__ shrdq(a1, a2, 24);
__ andq(a1, ExternalAddress(poly1305_mask42()), t1 /*rscratch*/);
__ movq(XTMP3, a1);
// --- end of spread accumulator
// To add accumulator, we must unroll first loop iteration
// Load first four 16-byte message blocks of data (64 bytes)
__ vmovdqu(YTMP4, Address(input, 0));
__ vmovdqu(YTMP5, Address(input, 32));
// Interleave the input message data to form 44-bit limbs
// YMM_ACC0 to have bits 0-43 of all 4 blocks in 4 qwords
// YMM_ACC1 to have bits 87-44 of all 4 blocks in 4 qwords
// YMM_ACC2 to have bits 127-88 of all 4 blocks in 4 qwords
// Interleave blocks of data
__ vpunpckhqdq(YMM_ACC2, YTMP4, YTMP5, Assembler::AVX_256bit);
__ vpunpcklqdq(YMM_ACC0, YTMP4, YTMP5, Assembler::AVX_256bit);
// Middle 44-bit limbs of new blocks
__ vpsrlq(YMM_ACC1, YMM_ACC0, 44, Assembler::AVX_256bit);
__ vpsllq(YTMP4, YMM_ACC2, 20, Assembler::AVX_256bit);
__ vpor(YMM_ACC1, YMM_ACC1, YTMP4, Assembler::AVX_256bit);
__ vpand(YMM_ACC1, YMM_ACC1, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, t1);
// Lowest 44-bit limbs of new blocks
__ vpand(YMM_ACC0, YMM_ACC0, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, t1);
// Highest 42-bit limbs of new blocks; pad the msg with 2^128
__ vpsrlq(YMM_ACC2, YMM_ACC2, 24, Assembler::AVX_256bit);
// Add 2^128 to all 4 final qwords for the message
__ vpor(YMM_ACC2, YMM_ACC2, ExternalAddress(poly1305_pad_msg()), Assembler::AVX_256bit, t1);
// --- end of input interleaving and message padding
// Add accumulator to the fist message block
// Accumulator limbs in YTMP1,YTMP2,YTMP3
__ vpaddq(YMM_ACC0, YMM_ACC0, YTMP1, Assembler::AVX_256bit);
__ vpaddq(YMM_ACC1, YMM_ACC1, YTMP2, Assembler::AVX_256bit);
__ vpaddq(YMM_ACC2, YMM_ACC2, YTMP3, Assembler::AVX_256bit);
/* Compute the following steps of POLY1305_EVAL_POLYNOMIAL algorithm
Compute R4321, R4444;
R4321 = [R^4, R^3, R^2, R^1];
R4444 = [R^4, R^4, R^4, R^4];
*/
// Compute the powers of R^1..R^4 and form 44-bit limbs of each
// YTMP5 to have bits 0-127 for R^1 and R^2
// YTMP6 to have bits 128-129 for R^1 and R^2
__ movq(XTMP1, r0);
__ vpinsrq(XTMP1, XTMP1, r1, 1);
__ vinserti128(YTMP5, YTMP5, XTMP1, 1);
// clear registers
__ vpxor(YTMP10, YTMP10, YTMP10, Assembler::AVX_256bit);
__ vpxor(YTMP6, YTMP6, YTMP6, Assembler::AVX_256bit);
// Calculate R^2
// a ← R
__ movq(a0, r0);
__ movq(a1, r1);
// a ← a * R = R^2
poly1305_multiply_scalar(a0, a1, a2,
r0, r1, c1, true,
t0, t1, t2, mulql, mulqh);
// Store R^2 in YTMP5, YTM6
__ movq(XTMP1, a0);
__ vpinsrq(XTMP1, XTMP1, a1, 1);
__ vinserti128(YTMP5, YTMP5, XTMP1, 0);
__ movq(XTMP1, a2);
__ vinserti128(YTMP6, YTMP6, XTMP1, 0);
// Calculate R^3
// a ← a * R = R^3
poly1305_multiply_scalar(a0, a1, a2,
r0, r1, c1, false,
t0, t1, t2, mulql, mulqh);
// Store R^3 in YTMP7, YTM2
__ movq(XTMP1, a0);
__ vpinsrq(XTMP1, XTMP1, a1, 1);
__ vinserti128(YTMP7, YTMP7, XTMP1, 1);
__ movq(XTMP1, a2);
__ vinserti128(YTMP2, YTMP2, XTMP1, 1);
// Calculate R^4
// a ← a * R = R^4
poly1305_multiply_scalar(a0, a1, a2,
r0, r1, c1, false,
t0, t1, t2, mulql, mulqh);
// Store R^4 in YTMP7, YTM2
__ movq(XTMP1, a0);
__ vpinsrq(XTMP1, XTMP1, a1, 1);
__ vinserti128(YTMP7, YTMP7, XTMP1, 0);
__ movq(XTMP1, a2);
__ vinserti128(YTMP2, YTMP2, XTMP1, 0);
// Interleave the powers of R^1..R^4 to form 44-bit limbs (half-empty)
__ vpunpckhqdq(YMM_R2, YTMP5, YTMP10, Assembler::AVX_256bit);
__ vpunpcklqdq(YMM_R0, YTMP5, YTMP10, Assembler::AVX_256bit);
__ vpunpckhqdq(YTMP3, YTMP7, YTMP10, Assembler::AVX_256bit);
__ vpunpcklqdq(YTMP4, YTMP7, YTMP10, Assembler::AVX_256bit);
__ vpslldq(YMM_R2, YMM_R2, 8, Assembler::AVX_256bit);
__ vpslldq(YTMP6, YTMP6, 8, Assembler::AVX_256bit);
__ vpslldq(YMM_R0, YMM_R0, 8, Assembler::AVX_256bit);
__ vpor(YMM_R2, YMM_R2, YTMP3, Assembler::AVX_256bit);
__ vpor(YMM_R0, YMM_R0, YTMP4, Assembler::AVX_256bit);
__ vpor(YTMP6, YTMP6, YTMP2, Assembler::AVX_256bit);
// Move 2 MSbits to top 24 bits, to be OR'ed later
__ vpsllq(YTMP6, YTMP6, 40, Assembler::AVX_256bit);
__ vpsrlq(YMM_R1, YMM_R0, 44, Assembler::AVX_256bit);
__ vpsllq(YTMP5, YMM_R2, 20, Assembler::AVX_256bit);
__ vpor(YMM_R1, YMM_R1, YTMP5, Assembler::AVX_256bit);
__ vpand(YMM_R1, YMM_R1, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, t1);
__ vpand(YMM_R0, YMM_R0, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, t1);
__ vpsrlq(YMM_R2, YMM_R2, 24, Assembler::AVX_256bit);
__ vpor(YMM_R2, YMM_R2, YTMP6, Assembler::AVX_256bit);
// YMM_R0, YMM_R1, YMM_R2 have the limbs of R^1, R^2, R^3, R^4
// Store R^4-R on stack for later use
int _r4_r1_save = 0;
__ vmovdqu(Address(rsp, _r4_r1_save + 0), YMM_R0);
__ vmovdqu(Address(rsp, _r4_r1_save + 32), YMM_R1);
__ vmovdqu(Address(rsp, _r4_r1_save + 32*2), YMM_R2);
// Broadcast 44-bit limbs of R^4
__ mov(t0, a0);
__ andq(t0, ExternalAddress(poly1305_mask44()), t1 /*rscratch*/); // First limb (R^4[43:0])
__ movq(YMM_R0, t0);
__ vpermq(YMM_R0, YMM_R0, 0x0, Assembler::AVX_256bit);
__ movq(t0, a1);
__ shrdq(a0, t0, 44);
__ andq(a0, ExternalAddress(poly1305_mask44()), t1 /*rscratch*/); // Second limb (R^4[87:44])
__ movq(YMM_R1, a0);
__ vpermq(YMM_R1, YMM_R1, 0x0, Assembler::AVX_256bit);
__ shrdq(a1, a2, 24);
__ andq(a1, ExternalAddress(poly1305_mask42()), t1 /*rscratch*/); // Third limb (R^4[129:88])
__ movq(YMM_R2, a1);
__ vpermq(YMM_R2, YMM_R2, 0x0, Assembler::AVX_256bit);
// YMM_R0, YMM_R1, YMM_R2 have the limbs of R^4, R^4, R^4, R^4
// Generate 4*5*R^4
// 4*R^4
__ vpsllq(YTMP1, YMM_R1, 2, Assembler::AVX_256bit);
__ vpsllq(YTMP2, YMM_R2, 2, Assembler::AVX_256bit);
// 5*R^4
__ vpaddq(YTMP1, YTMP1, YMM_R1, Assembler::AVX_256bit);
__ vpaddq(YTMP2, YTMP2, YMM_R2, Assembler::AVX_256bit);
// 4*5*R^4
__ vpsllq(YTMP1, YTMP1, 2, Assembler::AVX_256bit);
__ vpsllq(YTMP2, YTMP2, 2, Assembler::AVX_256bit);
//Store broadcasted R^4 and 4*5*R^4 on stack for later use
int _r4_save = 32*3;
int _r4p_save = 32*6;
__ vmovdqu(Address(rsp, _r4_save + 0), YMM_R0);
__ vmovdqu(Address(rsp, _r4_save + 32), YMM_R1);
__ vmovdqu(Address(rsp, _r4_save + 32*2), YMM_R2);
__ vmovdqu(Address(rsp, _r4p_save), YTMP1);
__ vmovdqu(Address(rsp, _r4p_save + 32), YTMP2);
// Get the number of multiples of 4 message blocks (64 bytes) for vectorization
__ movq(t0, length);
__ andq(t0, 0xffffffc0); // 0xffffffffffffffc0 after sign extension
// VECTOR LOOP: process 4 * 16-byte message blocks at a time
__ bind(L_process256Loop);
__ cmpl(t0, 16*4); //64 bytes (4 blocks at a time)
__ jcc(Assembler::belowEqual, L_process256LoopDone);
/*
Compute the following steps of POLY1305_EVAL_POLYNOMIAL algorithm
l' = floor(l/4)
for (i = 1 to l'-1):
T ← (R4444 ⊗ T) ⊕ COEF[i];
*/
// Perform multiply and reduce while loading the next block and adding it in interleaved manner
// The logic to advance the SIMD loop counter (i.e. length -= 64) is inside the function below.
// The function below also includes the logic to load the next 4 blocks of data for efficient port utilization.
poly1305_msg_mul_reduce_vec4_avx2(YMM_ACC0, YMM_ACC1, YMM_ACC2,
Address(rsp, _r4_save + 0), Address(rsp, _r4_save + 32), Address(rsp, _r4_save + 32*2),
Address(rsp, _r4p_save), Address(rsp, _r4p_save + 32),
YTMP1, YTMP2, YTMP3, YTMP4, YTMP5, YTMP6,
YTMP7, YTMP8, YTMP9, YTMP10, YTMP11, YTMP12,
input, t0, t1 /*rscratch*/);
__ jmp(L_process256Loop);
// end of vector loop
__ bind(L_process256LoopDone);
/*
Compute the following steps of POLY1305_EVAL_POLYNOMIAL algorithm
T ← R4321 ⊗ T;
*/
// Need to multiply by R^4, R^3, R^2, R
//Read R^4-R;
__ vmovdqu(YMM_R0, Address(rsp, _r4_r1_save + 0));
__ vmovdqu(YMM_R1, Address(rsp, _r4_r1_save + 32));
__ vmovdqu(YMM_R2, Address(rsp, _r4_r1_save + 32*2));
// Generate 4*5*[R^4..R^1] (ignore lowest limb)
// YTMP1 to have bits 87-44 of all 1-4th powers of R' in 4 qwords
// YTMP2 to have bits 129-88 of all 1-4th powers of R' in 4 qwords
__ vpsllq(YTMP10, YMM_R1, 2, Assembler::AVX_256bit);
__ vpaddq(YTMP1, YMM_R1, YTMP10, Assembler::AVX_256bit); //R1' (R1*5)
__ vpsllq(YTMP10, YMM_R2, 2, Assembler::AVX_256bit);
__ vpaddq(YTMP2, YMM_R2, YTMP10, Assembler::AVX_256bit); //R2' (R2*5)
// 4*5*R
__ vpsllq(YTMP1, YTMP1, 2, Assembler::AVX_256bit);
__ vpsllq(YTMP2, YTMP2, 2, Assembler::AVX_256bit);
poly1305_mul_reduce_vec4_avx2(YMM_ACC0, YMM_ACC1, YMM_ACC2,
YMM_R0, YMM_R1, YMM_R2, YTMP1, YTMP2,
YTMP3, YTMP4, YTMP5, YTMP6,
YTMP7, YTMP8, YTMP9, t1);
/*
Compute the following steps of POLY1305_EVAL_POLYNOMIAL algorithm
SUM ← T0 + T1 + T2 + T3;
*/
// 4 -> 2 blocks
__ vextracti128(YTMP1, YMM_ACC0, 1);
__ vextracti128(YTMP2, YMM_ACC1, 1);
__ vextracti128(YTMP3, YMM_ACC2, 1);
__ vpaddq(YMM_ACC0, YMM_ACC0, YTMP1, Assembler::AVX_128bit);
__ vpaddq(YMM_ACC1, YMM_ACC1, YTMP2, Assembler::AVX_128bit);
__ vpaddq(YMM_ACC2, YMM_ACC2, YTMP3, Assembler::AVX_128bit);
// 2 -> 1 blocks
__ vpsrldq(YTMP1, YMM_ACC0, 8, Assembler::AVX_128bit);
__ vpsrldq(YTMP2, YMM_ACC1, 8, Assembler::AVX_128bit);
__ vpsrldq(YTMP3, YMM_ACC2, 8, Assembler::AVX_128bit);
// Finish folding
__ vpaddq(YMM_ACC0, YMM_ACC0, YTMP1, Assembler::AVX_128bit);
__ vpaddq(YMM_ACC1, YMM_ACC1, YTMP2, Assembler::AVX_128bit);
__ vpaddq(YMM_ACC2, YMM_ACC2, YTMP3, Assembler::AVX_128bit);
__ movq(YMM_ACC0, YMM_ACC0);
__ movq(YMM_ACC1, YMM_ACC1);
__ movq(YMM_ACC2, YMM_ACC2);
__ lea(input, Address(input,16*4));
__ andq(length, 63); // remaining bytes < length 64
// carry propagation
__ vpsrlq(YTMP1, YMM_ACC0, 44, Assembler::AVX_128bit);
__ vpand(YMM_ACC0, YMM_ACC0, ExternalAddress(poly1305_mask44()), Assembler::AVX_128bit, t1); // Clear top 20 bits
__ vpaddq(YMM_ACC1, YMM_ACC1, YTMP1, Assembler::AVX_128bit);
__ vpsrlq(YTMP1, YMM_ACC1, 44, Assembler::AVX_128bit);
__ vpand(YMM_ACC1, YMM_ACC1, ExternalAddress(poly1305_mask44()), Assembler::AVX_128bit, t1); // Clear top 20 bits
__ vpaddq(YMM_ACC2, YMM_ACC2, YTMP1, Assembler::AVX_128bit);
__ vpsrlq(YTMP1, YMM_ACC2, 42, Assembler::AVX_128bit);
__ vpand(YMM_ACC2, YMM_ACC2, ExternalAddress(poly1305_mask42()), Assembler::AVX_128bit, t1); // Clear top 20 bits
__ vpsllq(YTMP2, YTMP1, 2, Assembler::AVX_128bit);
__ vpaddq(YTMP1, YTMP1, YTMP2, Assembler::AVX_128bit);
__ vpaddq(YMM_ACC0, YMM_ACC0, YTMP1, Assembler::AVX_128bit);
// Put together A
__ movq(a0, YMM_ACC0);
__ movq(t0, YMM_ACC1);
__ movq(t1, t0);
__ shlq(t1, 44);
__ orq(a0, t1);
__ shrq(t0, 20);
__ movq(a2, YMM_ACC2);
__ movq(a1, a2);
__ shlq(a1, 24);
__ orq(a1, t0);
__ shrq(a2, 40);
// cleanup
__ vzeroall(); // clears all ymm registers (ymm0 through ymm15)
// SAFE DATA (clear powers of R)
__ vmovdqu(Address(rsp, _r4_r1_save + 0), YTMP1);
__ vmovdqu(Address(rsp, _r4_r1_save + 32), YTMP1);
__ vmovdqu(Address(rsp, _r4_r1_save + 32*2), YTMP1);
__ vmovdqu(Address(rsp, _r4_save + 0), YTMP1);
__ vmovdqu(Address(rsp, _r4_save + 32), YTMP1);
__ vmovdqu(Address(rsp, _r4_save + 32*2), YTMP1);
__ vmovdqu(Address(rsp, _r4p_save), YTMP1);
__ vmovdqu(Address(rsp, _r4p_save + 32), YTMP1);
// Save rbp and rsp; clear stack frame
__ movq(rsp, rbp);
__ pop(rbp);
}
// Compute component-wise product for 4 16-byte message blocks,
// i.e. For each block, compute [a2 a1 a0] = [a2 a1 a0] x [r2 r1 r0]
//
// Each block/number is represented by 3 44-bit limb digits, start with multiplication
//
// a2 a1 a0
// x r2 r1 r0
// ----------------------------------
// a2xr0 a1xr0 a0xr0
// + a1xr1 a0xr1 5xa2xr1' (r1' = r1<<2)
// + a0xr2 5xa2xr2' 5xa1xr2' (r2' = r2<<2)
// ----------------------------------
// p2 p1 p0
//
void StubGenerator::poly1305_mul_reduce_vec4_avx2(
const XMMRegister A0, const XMMRegister A1, const XMMRegister A2,
const XMMRegister R0, const XMMRegister R1, const XMMRegister R2,
const XMMRegister R1P, const XMMRegister R2P,
const XMMRegister P0L, const XMMRegister P0H,
const XMMRegister P1L, const XMMRegister P1H,
const XMMRegister P2L, const XMMRegister P2H,
const XMMRegister YTMP1, const Register rscratch)
{
// Reset accumulator
__ vpxor(P0L, P0L, P0L, Assembler::AVX_256bit);
__ vpxor(P0H, P0H, P0H, Assembler::AVX_256bit);
__ vpxor(P1L, P1L, P1L, Assembler::AVX_256bit);
__ vpxor(P1H, P1H, P1H, Assembler::AVX_256bit);
__ vpxor(P2L, P2L, P2L, Assembler::AVX_256bit);
__ vpxor(P2H, P2H, P2H, Assembler::AVX_256bit);
// Calculate partial products
// p0 = a2xr1'
// p1 = a2xr2'
// p0 += a0xr0
__ vpmadd52luq(P0L, A2, R1P, Assembler::AVX_256bit);
__ vpmadd52huq(P0H, A2, R1P, Assembler::AVX_256bit);
__ vpmadd52luq(P1L, A2, R2P, Assembler::AVX_256bit);
__ vpmadd52huq(P1H, A2, R2P, Assembler::AVX_256bit);
__ vpmadd52luq(P0L, A0, R0, Assembler::AVX_256bit);
__ vpmadd52huq(P0H, A0, R0, Assembler::AVX_256bit);
// p2 = a2xr0
// p1 += a0xr1
// p0 += a1xr2'
// p2 += a0Xr2
__ vpmadd52luq(P2L, A2, R0, Assembler::AVX_256bit);
__ vpmadd52huq(P2H, A2, R0, Assembler::AVX_256bit);
__ vpmadd52luq(P1L, A0, R1, Assembler::AVX_256bit);
__ vpmadd52huq(P1H, A0, R1, Assembler::AVX_256bit);
__ vpmadd52luq(P0L, A1, R2P, Assembler::AVX_256bit);
__ vpmadd52huq(P0H, A1, R2P, Assembler::AVX_256bit);
__ vpmadd52luq(P2L, A0, R2, Assembler::AVX_256bit);
__ vpmadd52huq(P2H, A0, R2, Assembler::AVX_256bit);
// Carry propgation (first pass)
__ vpsrlq(YTMP1, P0L, 44, Assembler::AVX_256bit);
__ vpsllq(P0H, P0H, 8, Assembler::AVX_256bit);
__ vpmadd52luq(P1L, A1, R0, Assembler::AVX_256bit);
__ vpmadd52huq(P1H, A1, R0, Assembler::AVX_256bit);
// Carry propagation (first pass) - continue
__ vpand(A0, P0L, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, rscratch); // Clear top 20 bits
__ vpaddq(P0H, P0H, YTMP1, Assembler::AVX_256bit);
__ vpmadd52luq(P2L, A1, R1, Assembler::AVX_256bit);
__ vpmadd52huq(P2H, A1, R1, Assembler::AVX_256bit);
// Carry propagation (first pass) - continue 2
__ vpaddq(P1L, P1L, P0H, Assembler::AVX_256bit);
__ vpsllq(P1H, P1H, 8, Assembler::AVX_256bit);
__ vpsrlq(YTMP1, P1L, 44, Assembler::AVX_256bit);
__ vpand(A1, P1L, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, rscratch); // Clear top 20 bits
__ vpaddq(P2L, P2L, P1H, Assembler::AVX_256bit);
__ vpaddq(P2L, P2L, YTMP1, Assembler::AVX_256bit);
__ vpand(A2, P2L, ExternalAddress(poly1305_mask42()), Assembler::AVX_256bit, rscratch); // Clear top 22 bits
__ vpsrlq(YTMP1, P2L, 42, Assembler::AVX_256bit);
__ vpsllq(P2H, P2H, 10, Assembler::AVX_256bit);
__ vpaddq(P2H, P2H, YTMP1, Assembler::AVX_256bit);
// Carry propagation (second pass)
// Multiply by 5 the highest bits (above 130 bits)
__ vpaddq(A0, A0, P2H, Assembler::AVX_256bit);
__ vpsllq(P2H, P2H, 2, Assembler::AVX_256bit);
__ vpaddq(A0, A0, P2H, Assembler::AVX_256bit);
__ vpsrlq(YTMP1, A0, 44, Assembler::AVX_256bit);
__ vpand(A0, A0, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, rscratch); // Clear top 20 bits
__ vpaddq(A1, A1, YTMP1, Assembler::AVX_256bit);
}
// Compute component-wise product for 4 16-byte message blocks and adds the next 4 blocks
// i.e. For each block, compute [a2 a1 a0] = [a2 a1 a0] x [r2 r1 r0],
// followed by [a2 a1 a0] += [n2 n1 n0], where n contains the next 4 blocks of the message.
//
// Each block/number is represented by 3 44-bit limb digits, start with multiplication
//
// a2 a1 a0
// x r2 r1 r0
// ----------------------------------
// a2xr0 a1xr0 a0xr0
// + a1xr1 a0xr1 5xa2xr1' (r1' = r1<<2)
// + a0xr2 5xa2xr2' 5xa1xr2' (r2' = r2<<2)
// ----------------------------------
// p2 p1 p0
//
void StubGenerator::poly1305_msg_mul_reduce_vec4_avx2(
const XMMRegister A0, const XMMRegister A1, const XMMRegister A2,
const Address R0, const Address R1, const Address R2,
const Address R1P, const Address R2P,
const XMMRegister P0L, const XMMRegister P0H,
const XMMRegister P1L, const XMMRegister P1H,
const XMMRegister P2L, const XMMRegister P2H,
const XMMRegister YTMP1, const XMMRegister YTMP2,
const XMMRegister YTMP3, const XMMRegister YTMP4,
const XMMRegister YTMP5, const XMMRegister YTMP6,
const Register input, const Register length, const Register rscratch)
{
// Reset accumulator
__ vpxor(P0L, P0L, P0L, Assembler::AVX_256bit);
__ vpxor(P0H, P0H, P0H, Assembler::AVX_256bit);
__ vpxor(P1L, P1L, P1L, Assembler::AVX_256bit);
__ vpxor(P1H, P1H, P1H, Assembler::AVX_256bit);
__ vpxor(P2L, P2L, P2L, Assembler::AVX_256bit);
__ vpxor(P2H, P2H, P2H, Assembler::AVX_256bit);
// Calculate partial products
// p0 = a2xr1'
// p1 = a2xr2'
// p2 = a2xr0
__ vpmadd52luq(P0L, A2, R1P, Assembler::AVX_256bit);
__ vpmadd52huq(P0H, A2, R1P, Assembler::AVX_256bit);
// Interleave input loading with hash computation
__ lea(input, Address(input,16*4));
__ subl(length, 16*4);
__ vpmadd52luq(P1L, A2, R2P, Assembler::AVX_256bit);
__ vpmadd52huq(P1H, A2, R2P, Assembler::AVX_256bit);
// Load next block of data (64 bytes)
__ vmovdqu(YTMP1, Address(input, 0));
__ vmovdqu(YTMP2, Address(input, 32));
// interleave new blocks of data
__ vpunpckhqdq(YTMP3, YTMP1, YTMP2, Assembler::AVX_256bit);
__ vpunpcklqdq(YTMP1, YTMP1, YTMP2, Assembler::AVX_256bit);
__ vpmadd52luq(P0L, A0, R0, Assembler::AVX_256bit);
__ vpmadd52huq(P0H, A0, R0, Assembler::AVX_256bit);
// Highest 42-bit limbs of new blocks
__ vpsrlq(YTMP6, YTMP3, 24, Assembler::AVX_256bit);
__ vpor(YTMP6, YTMP6, ExternalAddress(poly1305_pad_msg()), Assembler::AVX_256bit, rscratch);
//Middle 44-bit limbs of new blocks
__ vpsrlq(YTMP2, YTMP1, 44, Assembler::AVX_256bit);
__ vpsllq(YTMP4, YTMP3, 20, Assembler::AVX_256bit);
// p2 = a2xr0
__ vpmadd52luq(P2L, A2, R0, Assembler::AVX_256bit);
__ vpmadd52huq(P2H, A2, R0, Assembler::AVX_256bit);
__ vpor(YTMP2, YTMP2, YTMP4, Assembler::AVX_256bit);
__ vpand(YTMP2, YTMP2, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, rscratch);
// Lowest 44-bit limbs of new blocks
__ vpand(YTMP1, YTMP1, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, rscratch);
__ vpmadd52luq(P1L, A0, R1, Assembler::AVX_256bit);
__ vpmadd52huq(P1H, A0, R1, Assembler::AVX_256bit);
__ vpmadd52luq(P0L, A1, R2P, Assembler::AVX_256bit);
__ vpmadd52huq(P0H, A1, R2P, Assembler::AVX_256bit);
__ vpmadd52luq(P2L, A0, R2, Assembler::AVX_256bit);
__ vpmadd52huq(P2H, A0, R2, Assembler::AVX_256bit);
// Carry propgation (first pass)
__ vpsrlq(YTMP5, P0L, 44, Assembler::AVX_256bit);
__ vpsllq(P0H, P0H, 8, Assembler::AVX_256bit);
__ vpmadd52luq(P1L, A1, R0, Assembler::AVX_256bit);
__ vpmadd52huq(P1H, A1, R0, Assembler::AVX_256bit);
// Carry propagation (first pass) - continue
__ vpand(A0, P0L, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, rscratch); // Clear top 20 bits
__ vpaddq(P0H, P0H, YTMP5, Assembler::AVX_256bit);
__ vpmadd52luq(P2L, A1, R1, Assembler::AVX_256bit);
__ vpmadd52huq(P2H, A1, R1, Assembler::AVX_256bit);
// Carry propagation (first pass) - continue 2
__ vpaddq(P1L, P1L, P0H, Assembler::AVX_256bit);
__ vpsllq(P1H, P1H, 8, Assembler::AVX_256bit);
__ vpsrlq(YTMP5, P1L, 44, Assembler::AVX_256bit);
__ vpand(A1, P1L, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, rscratch); // Clear top 20 bits
__ vpaddq(P2L, P2L, P1H, Assembler::AVX_256bit);
__ vpaddq(P2L, P2L, YTMP5, Assembler::AVX_256bit);
__ vpand(A2, P2L, ExternalAddress(poly1305_mask42()), Assembler::AVX_256bit, rscratch); // Clear top 22 bits
__ vpaddq(A2, A2, YTMP6, Assembler::AVX_256bit); // Add highest bits from new blocks to accumulator
__ vpsrlq(YTMP5, P2L, 42, Assembler::AVX_256bit);
__ vpsllq(P2H, P2H, 10, Assembler::AVX_256bit);
__ vpaddq(P2H, P2H, YTMP5, Assembler::AVX_256bit);
// Carry propagation (second pass)
// Multiply by 5 the highest bits (above 130 bits)
__ vpaddq(A0, A0, P2H, Assembler::AVX_256bit);
__ vpsllq(P2H, P2H, 2, Assembler::AVX_256bit);
__ vpaddq(A0, A0, P2H, Assembler::AVX_256bit);
__ vpsrlq(YTMP5, A0, 44, Assembler::AVX_256bit);
__ vpand(A0, A0, ExternalAddress(poly1305_mask44()), Assembler::AVX_256bit, rscratch); // Clear top 20 bits
__ vpaddq(A0, A0, YTMP1, Assembler::AVX_256bit); //Add low 42-bit bits from new blocks to accumulator
__ vpaddq(A1, A1, YTMP2, Assembler::AVX_256bit); //Add medium 42-bit bits from new blocks to accumulator
__ vpaddq(A1, A1, YTMP5, Assembler::AVX_256bit);
}