/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "LICENSE" file accompanying this file. This file 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.
 */

// ----------------------------------------------------------------------------
// Triple modulo p_521, z := (3 * x) mod p_521, assuming x reduced
// Input x[9]; output z[9]
//
//    extern void bignum_triple_p521
//     (uint64_t z[static 9], uint64_t x[static 9]);
//
// Standard x86-64 ABI: RDI = z, RSI = x
// ----------------------------------------------------------------------------


        .globl  bignum_triple_p521
        .globl  _bignum_triple_p521
        .text

#define z %rdi
#define x %rsi

// d7 re-uses the input pointer when safe to do so

#define d0 %rax
#define d1 %rcx
#define d2 %r8
#define d3 %r9
#define d4 %r10
#define d5 %r11
#define d6 %r12
#define d7 %rsi
#define d8 %rdx

#define m %rbx
#define mshort %ebx



bignum_triple_p521:
_bignum_triple_p521:

// Save more registers to play with

                pushq   %rbx
                pushq   %r12

// Load the top (short) word first to compute the initial carry-in
// Set OF according to bit 520, but *always* set CF to get a +1 bump

                movq    64(x), m
                movq    m, d8
                shlq    $54, m
                addq    m, m
                stc

// Use a double carry chain to compute x' + x + 1 where x' is a
// 1-bit left rotation of x; this is then == 3 * x + 1 (mod p_521)
// This gives us s = [d8;d7;d6;d5;d4;d3;d2;d1;d0] = x + x' + 1.

                movq    (x), m
                movq    m, d0
                adcxq   m, m
                adoxq   m, d0
                movq    8(x), m
                movq    m, d1
                adcxq   m, m
                adoxq   m, d1
                movq    16(x), m
                movq    m, d2
                adcxq   m, m
                adoxq   m, d2
                movq    24(x), m
                movq    m, d3
                adcxq   m, m
                adoxq   m, d3
                movq    32(x), m
                movq    m, d4
                adcxq   m, m
                adoxq   m, d4
                movq    40(x), m
                movq    m, d5
                adcxq   m, m
                adoxq   m, d5
                movq    48(x), m
                movq    m, d6
                adcxq   m, m
                adoxq   m, d6
                movq    56(x), m
                movq    m, d7
                adcxq   m, m
                adoxq   m, d7

// The last word is slightly more intricate: we naturally end up adding
// 2 * top bit when we shouldn't (because it's a rotation and we've already
// added it at the LSB position) but then compensate by subtracting it.

                movq    d8, m
                adcxq   m, m
                adoxq   m, d8
                andq    $0x200, m
                subq    m, d8

// Now x + x' >= p_521 <=> s = x + x' + 1 >= 2^521
// Make m = 512 * [x + x' >= p_521]

                movl    $512, mshort
                andq    d8, m

// Now if x + x' >= p_521, we want (x + x') - p_521 = s - 2^521
// while otherwise we want x + x' = s - 1
// We use the mask m both as an operand and to generate the dual carry
// Write back the results as generated

                cmpq    $512, m

                sbbq    $0, d0
                movq    d0, (z)
                sbbq    $0, d1
                movq    d1, 8(z)
                sbbq    $0, d2
                movq    d2, 16(z)
                sbbq    $0, d3
                movq    d3, 24(z)
                sbbq    $0, d4
                movq    d4, 32(z)
                sbbq    $0, d5
                movq    d5, 40(z)
                sbbq    $0, d6
                movq    d6, 48(z)
                sbbq    $0, d7
                movq    d7, 56(z)
                sbbq    m, d8
                movq    d8, 64(z)

// Restore registers and return

                popq    %r12
                popq    %rbx

                ret

#if defined(__linux__) && defined(__ELF__)
.section .note.GNU-stack,"",%progbits
#endif
