/*
 * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2014, 2021, Red Hat Inc. 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 "asm/assembler.hpp"
#include "asm/assembler.inline.hpp"
#include "macroAssembler_aarch64.hpp"
#include "memory/resourceArea.hpp"
#include "runtime/stubRoutines.hpp"

void MacroAssembler::aesecb_decrypt(Register from, Register to, Register key, Register keylen) {
  Label L_doLast;

  ld1(v0, T16B, from); // get 16 bytes of input

  ld1(v5, T16B, post(key, 16));
  rev32(v5, T16B, v5);

  ld1(v1, v2, v3, v4, T16B, post(key, 64));
  rev32(v1, T16B, v1);
  rev32(v2, T16B, v2);
  rev32(v3, T16B, v3);
  rev32(v4, T16B, v4);
  aesd(v0, v1);
  aesimc(v0, v0);
  aesd(v0, v2);
  aesimc(v0, v0);
  aesd(v0, v3);
  aesimc(v0, v0);
  aesd(v0, v4);
  aesimc(v0, v0);

  ld1(v1, v2, v3, v4, T16B, post(key, 64));
  rev32(v1, T16B, v1);
  rev32(v2, T16B, v2);
  rev32(v3, T16B, v3);
  rev32(v4, T16B, v4);
  aesd(v0, v1);
  aesimc(v0, v0);
  aesd(v0, v2);
  aesimc(v0, v0);
  aesd(v0, v3);
  aesimc(v0, v0);
  aesd(v0, v4);
  aesimc(v0, v0);

  ld1(v1, v2, T16B, post(key, 32));
  rev32(v1, T16B, v1);
  rev32(v2, T16B, v2);

  cmpw(keylen, 44);
  br(Assembler::EQ, L_doLast);

  aesd(v0, v1);
  aesimc(v0, v0);
  aesd(v0, v2);
  aesimc(v0, v0);

  ld1(v1, v2, T16B, post(key, 32));
  rev32(v1, T16B, v1);
  rev32(v2, T16B, v2);

  cmpw(keylen, 52);
  br(Assembler::EQ, L_doLast);

  aesd(v0, v1);
  aesimc(v0, v0);
  aesd(v0, v2);
  aesimc(v0, v0);

  ld1(v1, v2, T16B, post(key, 32));
  rev32(v1, T16B, v1);
  rev32(v2, T16B, v2);

  bind(L_doLast);

  aesd(v0, v1);
  aesimc(v0, v0);
  aesd(v0, v2);

  eor(v0, T16B, v0, v5);

  st1(v0, T16B, to);

  // Preserve the address of the start of the key
  sub(key, key, keylen, LSL, exact_log2(sizeof (jint)));
}

// Load expanded key into v17..v31
void MacroAssembler::aesenc_loadkeys(Register key, Register keylen) {
  Label L_loadkeys_44, L_loadkeys_52;
  cmpw(keylen, 52);
  br(Assembler::LO, L_loadkeys_44);
  br(Assembler::EQ, L_loadkeys_52);

  ld1(v17, v18,  T16B,  post(key, 32));
  rev32(v17,  T16B, v17);
  rev32(v18,  T16B, v18);
  bind(L_loadkeys_52);
  ld1(v19, v20,  T16B,  post(key, 32));
  rev32(v19,  T16B, v19);
  rev32(v20,  T16B, v20);
  bind(L_loadkeys_44);
  ld1(v21, v22, v23, v24,  T16B,  post(key, 64));
  rev32(v21,  T16B, v21);
  rev32(v22,  T16B, v22);
  rev32(v23,  T16B, v23);
  rev32(v24,  T16B, v24);
  ld1(v25, v26, v27, v28,  T16B,  post(key, 64));
  rev32(v25,  T16B, v25);
  rev32(v26,  T16B, v26);
  rev32(v27,  T16B, v27);
  rev32(v28,  T16B, v28);
  ld1(v29, v30, v31,  T16B, post(key, 48));
  rev32(v29,  T16B, v29);
  rev32(v30,  T16B, v30);
  rev32(v31,  T16B, v31);

  // Preserve the address of the start of the key
  sub(key, key, keylen, LSL, exact_log2(sizeof (jint)));
}

// NeoverseTM N1Software Optimization Guide:
// Adjacent AESE/AESMC instruction pairs and adjacent AESD/AESIMC
// instruction pairs will exhibit the performance characteristics
// described in Section 4.6.
void MacroAssembler::aes_round(FloatRegister input, FloatRegister subkey) {
  aese(input, subkey); aesmc(input, input);
}

// KernelGenerator
//
// The abstract base class of an unrolled function generator.
// Subclasses override generate(), length(), and next() to generate
// unrolled and interleaved functions.
//
// The core idea is that a subclass defines a method which generates
// the base case of a function and a method to generate a clone of it,
// shifted to a different set of registers. KernelGenerator will then
// generate several interleaved copies of the function, with each one
// using a different set of registers.

// The subclass must implement three methods: length(), which is the
// number of instruction bundles in the intrinsic, generate(int n)
// which emits the nth instruction bundle in the intrinsic, and next()
// which takes an instance of the generator and returns a version of it,
// shifted to a new set of registers.

class KernelGenerator: public MacroAssembler {
protected:
  const int _unrolls;
public:
  KernelGenerator(Assembler *as, int unrolls)
    : MacroAssembler(as->code()), _unrolls(unrolls) { }
  virtual void generate(int index) = 0;
  virtual int length() = 0;
  virtual KernelGenerator *next() = 0;
  int unrolls() { return _unrolls; }
  void unroll();
};

void KernelGenerator::unroll() {
  ResourceMark rm;
  KernelGenerator **generators
    = NEW_RESOURCE_ARRAY(KernelGenerator *, unrolls());

  generators[0] = this;
  for (int i = 1; i < unrolls(); i++) {
    generators[i] = generators[i-1]->next();
  }

  for (int j = 0; j < length(); j++) {
    for (int i = 0; i < unrolls(); i++) {
      generators[i]->generate(j);
    }
  }
}

// An unrolled and interleaved generator for AES encryption.
class AESKernelGenerator: public KernelGenerator {
  Register _from, _to;
  const Register _keylen;
  FloatRegister _data;
  const FloatRegister _subkeys;
  bool _once;
  Label _rounds_44, _rounds_52;

public:
  AESKernelGenerator(Assembler *as, int unrolls,
                     Register from, Register to, Register keylen, FloatRegister data,
                     FloatRegister subkeys, bool once = true)
    : KernelGenerator(as, unrolls),
      _from(from), _to(to), _keylen(keylen), _data(data),
      _subkeys(subkeys), _once(once) {
  }

  virtual void generate(int index) {
    switch (index) {
    case  0:
      if (_from != noreg) {
        ld1(_data, T16B, _from); // get 16 bytes of input
      }
      break;
    case  1:
      if (_once) {
        cmpw(_keylen, 52);
        br(Assembler::LO, _rounds_44);
        br(Assembler::EQ, _rounds_52);
      }
      break;
    case  2:  aes_round(_data, as_FloatRegister(_subkeys->encoding() + 0));  break;
    case  3:  aes_round(_data, as_FloatRegister(_subkeys->encoding() + 1));  break;
    case  4:
      if (_once)  bind(_rounds_52);
      break;
    case  5:  aes_round(_data, as_FloatRegister(_subkeys->encoding() + 2));  break;
    case  6:  aes_round(_data, as_FloatRegister(_subkeys->encoding() + 3));  break;
    case  7:
      if (_once)  bind(_rounds_44);
      break;
    case  8:  aes_round(_data, as_FloatRegister(_subkeys->encoding() +  4));  break;
    case  9:  aes_round(_data, as_FloatRegister(_subkeys->encoding() +  5));  break;
    case 10:  aes_round(_data, as_FloatRegister(_subkeys->encoding() +  6));  break;
    case 11:  aes_round(_data, as_FloatRegister(_subkeys->encoding() +  7));  break;
    case 12:  aes_round(_data, as_FloatRegister(_subkeys->encoding() +  8));  break;
    case 13:  aes_round(_data, as_FloatRegister(_subkeys->encoding() +  9));  break;
    case 14:  aes_round(_data, as_FloatRegister(_subkeys->encoding() + 10));  break;
    case 15:  aes_round(_data, as_FloatRegister(_subkeys->encoding() + 11));  break;
    case 16:  aes_round(_data, as_FloatRegister(_subkeys->encoding() + 12));  break;
    case 17:  aese(_data, as_FloatRegister(_subkeys->encoding() + 13));  break;
    case 18:  eor(_data, T16B, _data, as_FloatRegister(_subkeys->encoding() + 14));  break;
    case 19:
      if (_to != noreg) {
        st1(_data, T16B, _to);
      }
      break;
    default: ShouldNotReachHere();
    }
  }

  virtual KernelGenerator *next() {
    return new AESKernelGenerator(this, _unrolls,
                                  _from, _to, _keylen,
                                  _data->successor(), _subkeys, /*once*/false);
  }

  virtual int length() { return 20; }
};

// Uses expanded key in v17..v31
// Returns encrypted values in inputs.
// If to != noreg, store value at to; likewise from
// Preserves key, keylen
// Increments from, to
// Input data in v0, v1, ...
// unrolls controls the number of times to unroll the generated function
void MacroAssembler::aesecb_encrypt(Register from, Register to, Register keylen,
                                    FloatRegister data, int unrolls) {
  AESKernelGenerator(this, unrolls, from, to, keylen, data, v17) .unroll();
}

// ghash_multiply and ghash_reduce are the non-unrolled versions of
// the GHASH function generators.
void MacroAssembler::ghash_multiply(FloatRegister result_lo, FloatRegister result_hi,
                                     FloatRegister a, FloatRegister b, FloatRegister a1_xor_a0,
                                     FloatRegister tmp1, FloatRegister tmp2, FloatRegister tmp3) {
  // Karatsuba multiplication performs a 128*128 -> 256-bit
  // multiplication in three 128-bit multiplications and a few
  // additions.
  //
  // (C1:C0) = A1*B1, (D1:D0) = A0*B0, (E1:E0) = (A0+A1)(B0+B1)
  // (A1:A0)(B1:B0) = C1:(C0+C1+D1+E1):(D1+C0+D0+E0):D0
  //
  // Inputs:
  //
  // A0 in a.d[0]     (subkey)
  // A1 in a.d[1]
  // (A1+A0) in a1_xor_a0.d[0]
  //
  // B0 in b.d[0]     (state)
  // B1 in b.d[1]

  ext(tmp1, T16B, b, b, 0x08);
  pmull2(result_hi, T1Q, b, a, T2D);  // A1*B1
  eor(tmp1, T16B, tmp1, b);           // (B1+B0)
  pmull(result_lo,  T1Q, b, a, T1D);  // A0*B0
  pmull(tmp2, T1Q, tmp1, a1_xor_a0, T1D); // (A1+A0)(B1+B0)

  ext(tmp1, T16B, result_lo, result_hi, 0x08);
  eor(tmp3, T16B, result_hi, result_lo); // A1*B1+A0*B0
  eor(tmp2, T16B, tmp2, tmp1);
  eor(tmp2, T16B, tmp2, tmp3);

  // Register pair <result_hi:result_lo> holds the result of carry-less multiplication
  ins(result_hi, D, tmp2, 0, 1);
  ins(result_lo, D, tmp2, 1, 0);
}

void MacroAssembler::ghash_reduce(FloatRegister result, FloatRegister lo, FloatRegister hi,
                  FloatRegister p, FloatRegister vzr, FloatRegister t1) {
  const FloatRegister t0 = result;

  // The GCM field polynomial f is z^128 + p(z), where p =
  // z^7+z^2+z+1.
  //
  //    z^128 === -p(z)  (mod (z^128 + p(z)))
  //
  // so, given that the product we're reducing is
  //    a == lo + hi * z^128
  // substituting,
  //      === lo - hi * p(z)  (mod (z^128 + p(z)))
  //
  // we reduce by multiplying hi by p(z) and subtracting the result
  // from (i.e. XORing it with) lo.  Because p has no nonzero high
  // bits we can do this with two 64-bit multiplications, lo*p and
  // hi*p.

  pmull2(t0, T1Q, hi, p, T2D);
  ext(t1, T16B, t0, vzr, 8);
  eor(hi, T16B, hi, t1);
  ext(t1, T16B, vzr, t0, 8);
  eor(lo, T16B, lo, t1);
  pmull(t0, T1Q, hi, p, T1D);
  eor(result, T16B, lo, t0);
}

class GHASHMultiplyGenerator: public KernelGenerator {
  FloatRegister _result_lo, _result_hi, _b,
    _a, _vzr, _a1_xor_a0, _p,
    _tmp1, _tmp2, _tmp3;

public:
  GHASHMultiplyGenerator(Assembler *as, int unrolls,
                         /* offsetted registers */
                         FloatRegister result_lo, FloatRegister result_hi,
                         FloatRegister b,
                         /* non-offsetted (shared) registers */
                         FloatRegister a, FloatRegister a1_xor_a0, FloatRegister p, FloatRegister vzr,
                         /* offsetted (temp) registers */
                         FloatRegister tmp1, FloatRegister tmp2, FloatRegister tmp3)
    : KernelGenerator(as, unrolls),
      _result_lo(result_lo), _result_hi(result_hi), _b(b),
      _a(a), _vzr(vzr), _a1_xor_a0(a1_xor_a0), _p(p),
      _tmp1(tmp1), _tmp2(tmp2), _tmp3(tmp3) { }

  int register_stride = 7;

  virtual void generate(int index) {
    // Karatsuba multiplication performs a 128*128 -> 256-bit
    // multiplication in three 128-bit multiplications and a few
    // additions.
    //
    // (C1:C0) = A1*B1, (D1:D0) = A0*B0, (E1:E0) = (A0+A1)(B0+B1)
    // (A1:A0)(B1:B0) = C1:(C0+C1+D1+E1):(D1+C0+D0+E0):D0
    //
    // Inputs:
    //
    // A0 in a.d[0]     (subkey)
    // A1 in a.d[1]
    // (A1+A0) in a1_xor_a0.d[0]
    //
    // B0 in b.d[0]     (state)
    // B1 in b.d[1]

    switch (index) {
      case  0:  ext(_tmp1, T16B, _b, _b, 0x08);  break;
      case  1:  pmull2(_result_hi, T1Q, _b, _a, T2D);  // A1*B1
        break;
      case  2:  eor(_tmp1, T16B, _tmp1, _b);           // (B1+B0)
        break;
      case  3:  pmull(_result_lo,  T1Q, _b, _a, T1D);  // A0*B0
        break;
      case  4:  pmull(_tmp2, T1Q, _tmp1, _a1_xor_a0, T1D); // (A1+A0)(B1+B0)
        break;

      case  5:  ext(_tmp1, T16B, _result_lo, _result_hi, 0x08);  break;
      case  6:  eor(_tmp3, T16B, _result_hi, _result_lo); // A1*B1+A0*B0
        break;
      case  7:  eor(_tmp2, T16B, _tmp2, _tmp1);  break;
      case  8:  eor(_tmp2, T16B, _tmp2, _tmp3);  break;

        // Register pair <_result_hi:_result_lo> holds the _result of carry-less multiplication
      case  9:  ins(_result_hi, D, _tmp2, 0, 1);  break;
      case 10:  ins(_result_lo, D, _tmp2, 1, 0);  break;
      default: ShouldNotReachHere();
    }
  }

  virtual KernelGenerator* next() {
    GHASHMultiplyGenerator* result = new GHASHMultiplyGenerator(*this);
    result->_result_lo = as_FloatRegister(result->_result_lo->encoding() + register_stride);
    result->_result_hi = as_FloatRegister(result->_result_hi->encoding() + register_stride);
    result->_b         = as_FloatRegister(result->_b        ->encoding() + register_stride);
    result->_tmp1      = as_FloatRegister(result->_tmp1     ->encoding() + register_stride);
    result->_tmp2      = as_FloatRegister(result->_tmp2     ->encoding() + register_stride);
    result->_tmp3      = as_FloatRegister(result->_tmp3     ->encoding() + register_stride);
    return result;
  }

  virtual int length() { return 11; }
};

// Reduce the 128-bit product in hi:lo by the GCM field polynomial.
// The FloatRegister argument called data is optional: if it is a
// valid register, we interleave LD1 instructions with the
// reduction. This is to reduce latency next time around the loop.
class GHASHReduceGenerator: public KernelGenerator {
  FloatRegister _result, _lo, _hi, _p, _vzr, _data, _t1;
  int _once;
public:
  GHASHReduceGenerator(Assembler *as, int unrolls,
                       /* offsetted registers */
                       FloatRegister result, FloatRegister lo, FloatRegister hi,
                       /* non-offsetted (shared) registers */
                       FloatRegister p, FloatRegister vzr, FloatRegister data,
                       /* offsetted (temp) registers */
                       FloatRegister t1)
    : KernelGenerator(as, unrolls),
      _result(result), _lo(lo), _hi(hi),
      _p(p), _vzr(vzr), _data(data), _t1(t1), _once(true) { }

  int register_stride = 7;

  virtual void generate(int index) {
    const FloatRegister t0 = _result;

    switch (index) {
      // The GCM field polynomial f is z^128 + p(z), where p =
      // z^7+z^2+z+1.
      //
      //    z^128 === -p(z)  (mod (z^128 + p(z)))
      //
      // so, given that the product we're reducing is
      //    a == lo + hi * z^128
      // substituting,
      //      === lo - hi * p(z)  (mod (z^128 + p(z)))
      //
      // we reduce by multiplying hi by p(z) and subtracting the _result
      // from (i.e. XORing it with) lo.  Because p has no nonzero high
      // bits we can do this with two 64-bit multiplications, lo*p and
      // hi*p.

      case  0:  pmull2(t0, T1Q, _hi, _p, T2D);  break;
      case  1:  ext(_t1, T16B, t0, _vzr, 8);  break;
      case  2:  eor(_hi, T16B, _hi, _t1);  break;
      case  3:  ext(_t1, T16B, _vzr, t0, 8);  break;
      case  4:  eor(_lo, T16B, _lo, _t1);  break;
      case  5:  pmull(t0, T1Q, _hi, _p, T1D);  break;
      case  6:  eor(_result, T16B, _lo, t0);  break;
      default: ShouldNotReachHere();
    }

    // Sprinkle load instructions into the generated instructions
    if (_data->is_valid() && _once) {
      assert(length() >= unrolls(), "not enough room for inteleaved loads");
      if (index < unrolls()) {
        ld1(as_FloatRegister(_data->encoding() + index*register_stride), T16B, post(r2, 0x10));
      }
    }
  }

  virtual KernelGenerator *next() {
    GHASHReduceGenerator *result = new GHASHReduceGenerator(*this);
    result->_result = as_FloatRegister(result->_result->encoding() + register_stride);
    result->_hi     = as_FloatRegister(result->_hi    ->encoding() + register_stride);
    result->_lo     = as_FloatRegister(result->_lo    ->encoding() + register_stride);
    result->_t1     = as_FloatRegister(result->_t1    ->encoding() + register_stride);
    result->_once = false;
    return result;
  }

 int length() { return 7; }
};

// Perform a GHASH multiply/reduce on a single FloatRegister.
void MacroAssembler::ghash_modmul(FloatRegister result,
                                  FloatRegister result_lo, FloatRegister result_hi, FloatRegister b,
                                  FloatRegister a, FloatRegister vzr, FloatRegister a1_xor_a0, FloatRegister p,
                                  FloatRegister t1, FloatRegister t2, FloatRegister t3) {
  ghash_multiply(result_lo, result_hi, a, b, a1_xor_a0, t1, t2, t3);
  ghash_reduce(result, result_lo, result_hi, p, vzr, t1);
}

// Interleaved GHASH processing.
//
// Clobbers all vector registers.
//
void MacroAssembler::ghash_processBlocks_wide(address field_polynomial, Register state,
                                              Register subkeyH,
                                              Register data, Register blocks, int unrolls) {
  int register_stride = 7;

  // Bafflingly, GCM uses little-endian for the byte order, but
  // big-endian for the bit order.  For example, the polynomial 1 is
  // represented as the 16-byte string 80 00 00 00 | 12 bytes of 00.
  //
  // So, we must either reverse the bytes in each word and do
  // everything big-endian or reverse the bits in each byte and do
  // it little-endian.  On AArch64 it's more idiomatic to reverse
  // the bits in each byte (we have an instruction, RBIT, to do
  // that) and keep the data in little-endian bit order through the
  // calculation, bit-reversing the inputs and outputs.

  assert(unrolls * register_stride < 32, "out of registers");

  FloatRegister a1_xor_a0 = v28;
  FloatRegister Hprime = v29;
  FloatRegister vzr = v30;
  FloatRegister p = v31;
  eor(vzr, T16B, vzr, vzr); // zero register

  ldrq(p, field_polynomial);    // The field polynomial

  ldrq(v0, Address(state));
  ldrq(Hprime, Address(subkeyH));

  rev64(v0, T16B, v0);          // Bit-reverse words in state and subkeyH
  rbit(v0, T16B, v0);
  rev64(Hprime, T16B, Hprime);
  rbit(Hprime, T16B, Hprime);

  // Powers of H -> Hprime

  Label already_calculated, done;
  {
    // The first time around we'll have to calculate H**2, H**3, etc.
    // Look at the largest power of H in the subkeyH array to see if
    // it's already been calculated.
    ldp(rscratch1, rscratch2, Address(subkeyH, 16 * (unrolls - 1)));
    orr(rscratch1, rscratch1, rscratch2);
    cbnz(rscratch1, already_calculated);

    orr(v6, T16B, Hprime, Hprime);  // Start with H in v6 and Hprime
    for (int i = 1; i < unrolls; i++) {
      ext(a1_xor_a0, T16B, Hprime, Hprime, 0x08); // long-swap subkeyH into a1_xor_a0
      eor(a1_xor_a0, T16B, a1_xor_a0, Hprime);    // xor subkeyH into subkeyL (Karatsuba: (A1+A0))
      ghash_modmul(/*result*/v6, /*result_lo*/v5, /*result_hi*/v4, /*b*/v6,
                   Hprime, vzr, a1_xor_a0, p,
                   /*temps*/v1, v3, v2);
      rev64(v1, T16B, v6);
      rbit(v1, T16B, v1);
      strq(v1, Address(subkeyH, 16 * i));
    }
    b(done);
  }
  {
    bind(already_calculated);

    // Load the largest power of H we need into v6.
    ldrq(v6, Address(subkeyH, 16 * (unrolls - 1)));
    rev64(v6, T16B, v6);
    rbit(v6, T16B, v6);
  }
  bind(done);

  orr(Hprime, T16B, v6, v6);     // Move H ** unrolls into Hprime

  // Hprime contains (H ** 1, H ** 2, ... H ** unrolls)
  // v0 contains the initial state. Clear the others.
  for (int i = 1; i < unrolls; i++) {
    int ofs = register_stride * i;
    FloatRegister v0_ofs = as_FloatRegister(v0->encoding() + ofs);
    eor(v0_ofs, T16B, v0_ofs, v0_ofs); // zero each state register
  }

  ext(a1_xor_a0, T16B, Hprime, Hprime, 0x08); // long-swap subkeyH into a1_xor_a0
  eor(a1_xor_a0, T16B, a1_xor_a0, Hprime);    // xor subkeyH into subkeyL (Karatsuba: (A1+A0))

  // Load #unrolls blocks of data
  for (int ofs = 0; ofs < unrolls * register_stride; ofs += register_stride) {
    FloatRegister v2_ofs = as_FloatRegister(v2->encoding() + ofs);
    ld1(v2_ofs, T16B, post(data, 0x10));
  }

  // Register assignments, replicated across 4 clones, v0 ... v23
  //
  // v0: input / output: current state, result of multiply/reduce
  // v1: temp
  // v2: input: one block of data (the ciphertext)
  //     also used as a temp once the data has been consumed
  // v3: temp
  // v4: output: high part of product
  // v5: output: low part ...
  // v6: unused
  //
  // Not replicated:
  //
  // v28: High part of H xor low part of H'
  // v29: H' (hash subkey)
  // v30: zero
  // v31: Reduction polynomial of the Galois field

  // Inner loop.
  // Do the whole load/add/multiply/reduce over all our data except
  // the last few rows.
  {
    Label L_ghash_loop;
    bind(L_ghash_loop);

    // Prefetching doesn't help here. In fact, on Neoverse N1 it's worse.
    // prfm(Address(data, 128), PLDL1KEEP);

    // Xor data into current state
    for (int ofs = 0; ofs < unrolls * register_stride; ofs += register_stride) {
      FloatRegister v0_ofs = as_FloatRegister(v0->encoding() + ofs);
      FloatRegister v2_ofs = as_FloatRegister(v2->encoding() + ofs);
      rbit(v2_ofs, T16B, v2_ofs);
      eor(v2_ofs, T16B, v0_ofs, v2_ofs);   // bit-swapped data ^ bit-swapped state
    }

    // Generate fully-unrolled multiply-reduce in two stages.

    GHASHMultiplyGenerator(this, unrolls,
                           /*result_lo*/v5, /*result_hi*/v4, /*data*/v2,
                           Hprime, a1_xor_a0, p, vzr,
                           /*temps*/v1, v3, /* reuse b*/v2) .unroll();

    // NB: GHASHReduceGenerator also loads the next #unrolls blocks of
    // data into v0, v0+ofs, the current state.
    GHASHReduceGenerator (this, unrolls,
                          /*result*/v0, /*lo*/v5, /*hi*/v4, p, vzr,
                          /*data*/v2, /*temp*/v3) .unroll();

    sub(blocks, blocks, unrolls);
    cmp(blocks, (unsigned char)(unrolls * 2));
    br(GE, L_ghash_loop);
  }

  // Merge the #unrolls states.  Note that the data for the next
  // iteration has already been loaded into v4, v4+ofs, etc...

  // First, we multiply/reduce each clone by the appropriate power of H.
  for (int i = 0; i < unrolls; i++) {
    int ofs = register_stride * i;
    FloatRegister v0_ofs = as_FloatRegister(v0->encoding() + ofs);
    FloatRegister v1_ofs = as_FloatRegister(v1->encoding() + ofs);
    FloatRegister v2_ofs = as_FloatRegister(v2->encoding() + ofs);
    FloatRegister v3_ofs = as_FloatRegister(v3->encoding() + ofs);
    FloatRegister v4_ofs = as_FloatRegister(v4->encoding() + ofs);
    FloatRegister v5_ofs = as_FloatRegister(v5->encoding() + ofs);

    ldrq(Hprime, Address(subkeyH, 16 * (unrolls - i - 1)));

    rbit(v2_ofs, T16B, v2_ofs);
    eor(v2_ofs, T16B, v0_ofs, v2_ofs);   // bit-swapped data ^ bit-swapped state

    rev64(Hprime, T16B, Hprime);
    rbit(Hprime, T16B, Hprime);
    ext(a1_xor_a0, T16B, Hprime, Hprime, 0x08); // long-swap subkeyH into a1_xor_a0
    eor(a1_xor_a0, T16B, a1_xor_a0, Hprime);    // xor subkeyH into subkeyL (Karatsuba: (A1+A0))
    ghash_modmul(/*result*/v0_ofs, /*result_lo*/v5_ofs, /*result_hi*/v4_ofs, /*b*/v2_ofs,
                 Hprime, vzr, a1_xor_a0, p,
                 /*temps*/v1_ofs, v3_ofs, /* reuse b*/v2_ofs);
  }

  // Then we sum the results.
  for (int i = 1; i < unrolls; i++) {
    FloatRegister v0_ofs = as_FloatRegister(v0->encoding() + register_stride * i);
    eor(v0, T16B, v0, v0_ofs);
  }

  sub(blocks, blocks, (unsigned char)unrolls);

  // And finally bit-reverse the state back to big endian.
  rev64(v0, T16B, v0);
  rbit(v0, T16B, v0);
  st1(v0, T16B, state);
}
