// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This is an optimized implementation of AES-GCM using AES-NI and CLMUL-NI
// The implementation uses some optimization as described in:
// [1] Gueron, S., Kounavis, M.E.: Intel® Carry-Less Multiplication
//     Instruction and its Usage for Computing the GCM Mode rev. 2.02
// [2] Gueron, S., Krasnov, V.: Speeding up Counter Mode in Software and
//     Hardware

#include "textflag.h"

#define B0 X0
#define B1 X1
#define B2 X2
#define B3 X3
#define B4 X4
#define B5 X5
#define B6 X6
#define B7 X7

#define ACC0 X8
#define ACC1 X9
#define ACCM X10

#define T0 X11
#define T1 X12
#define T2 X13
#define POLY X14
#define BSWAP X15

DATA bswapMask<>+0x00(SB)/8, $0x08090a0b0c0d0e0f
DATA bswapMask<>+0x08(SB)/8, $0x0001020304050607

DATA gcmPoly<>+0x00(SB)/8, $0x0000000000000001
DATA gcmPoly<>+0x08(SB)/8, $0xc200000000000000

DATA andMask<>+0x00(SB)/8, $0x00000000000000ff
DATA andMask<>+0x08(SB)/8, $0x0000000000000000
DATA andMask<>+0x10(SB)/8, $0x000000000000ffff
DATA andMask<>+0x18(SB)/8, $0x0000000000000000
DATA andMask<>+0x20(SB)/8, $0x0000000000ffffff
DATA andMask<>+0x28(SB)/8, $0x0000000000000000
DATA andMask<>+0x30(SB)/8, $0x00000000ffffffff
DATA andMask<>+0x38(SB)/8, $0x0000000000000000
DATA andMask<>+0x40(SB)/8, $0x000000ffffffffff
DATA andMask<>+0x48(SB)/8, $0x0000000000000000
DATA andMask<>+0x50(SB)/8, $0x0000ffffffffffff
DATA andMask<>+0x58(SB)/8, $0x0000000000000000
DATA andMask<>+0x60(SB)/8, $0x00ffffffffffffff
DATA andMask<>+0x68(SB)/8, $0x0000000000000000
DATA andMask<>+0x70(SB)/8, $0xffffffffffffffff
DATA andMask<>+0x78(SB)/8, $0x0000000000000000
DATA andMask<>+0x80(SB)/8, $0xffffffffffffffff
DATA andMask<>+0x88(SB)/8, $0x00000000000000ff
DATA andMask<>+0x90(SB)/8, $0xffffffffffffffff
DATA andMask<>+0x98(SB)/8, $0x000000000000ffff
DATA andMask<>+0xa0(SB)/8, $0xffffffffffffffff
DATA andMask<>+0xa8(SB)/8, $0x0000000000ffffff
DATA andMask<>+0xb0(SB)/8, $0xffffffffffffffff
DATA andMask<>+0xb8(SB)/8, $0x00000000ffffffff
DATA andMask<>+0xc0(SB)/8, $0xffffffffffffffff
DATA andMask<>+0xc8(SB)/8, $0x000000ffffffffff
DATA andMask<>+0xd0(SB)/8, $0xffffffffffffffff
DATA andMask<>+0xd8(SB)/8, $0x0000ffffffffffff
DATA andMask<>+0xe0(SB)/8, $0xffffffffffffffff
DATA andMask<>+0xe8(SB)/8, $0x00ffffffffffffff

GLOBL bswapMask<>(SB), (NOPTR+RODATA), $16
GLOBL gcmPoly<>(SB), (NOPTR+RODATA), $16
GLOBL andMask<>(SB), (NOPTR+RODATA), $240

// func hasGCMAsm() bool
// returns whether AES-NI AND CLMUL-NI are supported
TEXT ·hasGCMAsm(SB),NOSPLIT,$0
	XORQ AX, AX
	INCL AX
	CPUID
	MOVQ CX, DX
	SHRQ $25, CX
	SHRQ $1, DX
	ANDQ DX, CX
	ANDQ $1, CX
	MOVB CX, ret+0(FP)
	RET

// func aesEncBlock(dst, src *[16]byte, ks []uint32)
TEXT ·aesEncBlock(SB),NOSPLIT,$0
	MOVQ dst+0(FP), DI
	MOVQ src+8(FP), SI
	MOVQ ks_base+16(FP), DX
	MOVQ ks_len+24(FP), CX

	SHRQ $2, CX
	DECQ CX

	MOVOU (SI), X0
	MOVOU (16*0)(DX), X1
	PXOR X1, X0
	MOVOU (16*1)(DX), X1
	AESENC X1, X0
	MOVOU (16*2)(DX), X1
	AESENC X1, X0
	MOVOU (16*3)(DX), X1
	AESENC X1, X0
	MOVOU (16*4)(DX), X1
	AESENC X1, X0
	MOVOU (16*5)(DX), X1
	AESENC X1, X0
	MOVOU (16*6)(DX), X1
	AESENC X1, X0
	MOVOU (16*7)(DX), X1
	AESENC X1, X0
	MOVOU (16*8)(DX), X1
	AESENC X1, X0
	MOVOU (16*9)(DX), X1
	AESENC X1, X0
	MOVOU (16*10)(DX), X1
	CMPQ CX, $12
	JB encLast
	AESENC X1, X0
	MOVOU (16*11)(DX), X1
	AESENC X1, X0
	MOVOU (16*12)(DX), X1
	JE encLast
	AESENC X1, X0
	MOVOU (16*13)(DX), X1
	AESENC X1, X0
	MOVOU (16*14)(DX), X1

encLast:
	AESENCLAST X1, X0
	MOVOU X0, (DI)

	RET

// func gcmAesFinish(productTable *[256]byte, tagMask, T *[16]byte, pLen, dLen uint64)
TEXT ·gcmAesFinish(SB),NOSPLIT,$0
#define pTbl DI
#define tMsk SI
#define tPtr DX
#define plen AX
#define dlen CX

	MOVQ productTable+0(FP), pTbl
	MOVQ tagMask+8(FP), tMsk
	MOVQ T+16(FP), tPtr
	MOVQ pLen+24(FP), plen
	MOVQ dLen+32(FP), dlen

	MOVOU (tPtr), ACC0
	MOVOU (tMsk), T2

	MOVOU bswapMask<>(SB), BSWAP
	MOVOU gcmPoly<>(SB), POLY

	SHLQ $3, plen
	SHLQ $3, dlen

	MOVQ plen, B0
	PINSRQ $1, dlen, B0

	PXOR ACC0, B0

	MOVOU (16*14)(pTbl), ACC0
	MOVOU (16*15)(pTbl), ACCM
	MOVOU ACC0, ACC1

	PCLMULQDQ $0x00, B0, ACC0
	PCLMULQDQ $0x11, B0, ACC1
	PSHUFD $78, B0, T0
	PXOR B0, T0
	PCLMULQDQ $0x00, T0, ACCM

	PXOR ACC0, ACCM
	PXOR ACC1, ACCM
	MOVOU ACCM, T0
	PSRLDQ $8, ACCM
	PSLLDQ $8, T0
	PXOR ACCM, ACC1
	PXOR T0, ACC0

	MOVOU POLY, T0
	PCLMULQDQ $0x01, ACC0, T0
	PSHUFD $78, ACC0, ACC0
	PXOR T0, ACC0

	MOVOU POLY, T0
	PCLMULQDQ $0x01, ACC0, T0
	PSHUFD $78, ACC0, ACC0
	PXOR T0, ACC0

	PXOR ACC1, ACC0

	PSHUFB BSWAP, ACC0
	PXOR T2, ACC0
	MOVOU ACC0, (tPtr)

	RET
#undef pTbl
#undef tMsk
#undef tPtr
#undef plen
#undef dlen

// func gcmAesInit(productTable *[256]byte, ks []uint32)
TEXT ·gcmAesInit(SB),NOSPLIT,$0
#define dst DI
#define KS SI
#define NR DX

	MOVQ productTable+0(FP), dst
	MOVQ ks_base+8(FP), KS
	MOVQ ks_len+16(FP), NR

	SHRQ $2, NR
	DECQ NR

	MOVOU bswapMask<>(SB), BSWAP
	MOVOU gcmPoly<>(SB), POLY

	// Encrypt block 0, with the AES key to generate the hash key H
	MOVOU (16*0)(KS), B0
	MOVOU (16*1)(KS), T0
	AESENC T0, B0
	MOVOU (16*2)(KS), T0
	AESENC T0, B0
	MOVOU (16*3)(KS), T0
	AESENC T0, B0
	MOVOU (16*4)(KS), T0
	AESENC T0, B0
	MOVOU (16*5)(KS), T0
	AESENC T0, B0
	MOVOU (16*6)(KS), T0
	AESENC T0, B0
	MOVOU (16*7)(KS), T0
	AESENC T0, B0
	MOVOU (16*8)(KS), T0
	AESENC T0, B0
	MOVOU (16*9)(KS), T0
	AESENC T0, B0
	MOVOU (16*10)(KS), T0
	CMPQ NR, $12
	JB initEncLast
	AESENC T0, B0
	MOVOU (16*11)(KS), T0
	AESENC T0, B0
	MOVOU (16*12)(KS), T0
	JE initEncLast
	AESENC T0, B0
	MOVOU (16*13)(KS), T0
	AESENC T0, B0
	MOVOU (16*14)(KS), T0
initEncLast:
	AESENCLAST T0, B0

	PSHUFB BSWAP, B0
	// H * 2
	PSHUFD $0xff, B0, T0
	MOVOU B0, T1
	PSRAL $31, T0
	PAND POLY, T0
	PSRLL $31, T1
	PSLLDQ $4, T1
	PSLLL $1, B0
	PXOR T0, B0
	PXOR T1, B0
	// Karatsuba pre-computations
	MOVOU B0, (16*14)(dst)
	PSHUFD $78, B0, B1
	PXOR B0, B1
	MOVOU B1, (16*15)(dst)

	MOVOU B0, B2
	MOVOU B1, B3
	// Now prepare powers of H and pre-computations for them
	MOVQ $7, AX

initLoop:
		MOVOU B2, T0
		MOVOU B2, T1
		MOVOU B3, T2
		PCLMULQDQ $0x00, B0, T0
		PCLMULQDQ $0x11, B0, T1
		PCLMULQDQ $0x00, B1, T2

		PXOR T0, T2
		PXOR T1, T2
		MOVOU T2, B4
		PSLLDQ $8, B4
		PSRLDQ $8, T2
		PXOR B4, T0
		PXOR T2, T1

		MOVOU POLY, B2
		PCLMULQDQ $0x01, T0, B2
		PSHUFD $78, T0, T0
		PXOR B2, T0
		MOVOU POLY, B2
		PCLMULQDQ $0x01, T0, B2
		PSHUFD $78, T0, T0
		PXOR T0, B2
		PXOR T1, B2

		MOVOU B2, (16*12)(dst)
		PSHUFD $78, B2, B3
		PXOR B2, B3
		MOVOU B3, (16*13)(dst)

		DECQ AX
		LEAQ (-16*2)(dst), dst
	JNE initLoop

	RET
#undef NR
#undef KS
#undef dst

// func gcmAesData(productTable *[256]byte, data []byte, T *[16]byte)
TEXT ·gcmAesData(SB),NOSPLIT,$0
#define pTbl DI
#define aut SI
#define tPtr CX
#define autLen DX

#define reduceRound(a) 	MOVOU POLY, T0;	PCLMULQDQ $0x01, a, T0; PSHUFD $78, a, a; PXOR T0, a
#define mulRoundAAD(X ,i) \
	MOVOU (16*(i*2))(pTbl), T1;\
	MOVOU T1, T2;\
	PCLMULQDQ $0x00, X, T1;\
	PXOR T1, ACC0;\
	PCLMULQDQ $0x11, X, T2;\
	PXOR T2, ACC1;\
	PSHUFD $78, X, T1;\
	PXOR T1, X;\
	MOVOU (16*(i*2+1))(pTbl), T1;\
	PCLMULQDQ $0x00, X, T1;\
	PXOR T1, ACCM

	MOVQ productTable+0(FP), pTbl
	MOVQ data_base+8(FP), aut
	MOVQ data_len+16(FP), autLen
	MOVQ T+32(FP), tPtr

	PXOR ACC0, ACC0
	MOVOU bswapMask<>(SB), BSWAP
	MOVOU gcmPoly<>(SB), POLY

	TESTQ autLen, autLen
	JEQ dataBail

	CMPQ autLen, $13	// optimize the TLS case
	JE dataTLS
	CMPQ autLen, $128
	JB startSinglesLoop
	JMP dataOctaLoop

dataTLS:
	MOVOU (16*14)(pTbl), T1
	MOVOU (16*15)(pTbl), T2
	PXOR B0, B0
	MOVQ (aut), B0
	PINSRD $2, 8(aut), B0
	PINSRB $12, 12(aut), B0
	XORQ autLen, autLen
	JMP dataMul

dataOctaLoop:
		CMPQ autLen, $128
		JB startSinglesLoop
		SUBQ $128, autLen

		MOVOU (16*0)(aut), X0
		MOVOU (16*1)(aut), X1
		MOVOU (16*2)(aut), X2
		MOVOU (16*3)(aut), X3
		MOVOU (16*4)(aut), X4
		MOVOU (16*5)(aut), X5
		MOVOU (16*6)(aut), X6
		MOVOU (16*7)(aut), X7
		LEAQ (16*8)(aut), aut
		PSHUFB BSWAP, X0
		PSHUFB BSWAP, X1
		PSHUFB BSWAP, X2
		PSHUFB BSWAP, X3
		PSHUFB BSWAP, X4
		PSHUFB BSWAP, X5
		PSHUFB BSWAP, X6
		PSHUFB BSWAP, X7
		PXOR ACC0, X0

		MOVOU (16*0)(pTbl), ACC0
		MOVOU (16*1)(pTbl), ACCM
		MOVOU ACC0, ACC1
		PSHUFD $78, X0, T1
		PXOR X0, T1
		PCLMULQDQ $0x00, X0, ACC0
		PCLMULQDQ $0x11, X0, ACC1
		PCLMULQDQ $0x00, T1, ACCM

		mulRoundAAD(X1, 1)
		mulRoundAAD(X2, 2)
		mulRoundAAD(X3, 3)
		mulRoundAAD(X4, 4)
		mulRoundAAD(X5, 5)
		mulRoundAAD(X6, 6)
		mulRoundAAD(X7, 7)

		PXOR ACC0, ACCM
		PXOR ACC1, ACCM
		MOVOU ACCM, T0
		PSRLDQ $8, ACCM
		PSLLDQ $8, T0
		PXOR ACCM, ACC1
		PXOR T0, ACC0
		reduceRound(ACC0)
		reduceRound(ACC0)
		PXOR ACC1, ACC0
	JMP dataOctaLoop

startSinglesLoop:
	MOVOU (16*14)(pTbl), T1
	MOVOU (16*15)(pTbl), T2

dataSinglesLoop:

		CMPQ autLen, $16
		JB dataEnd
		SUBQ $16, autLen

		MOVOU (aut), B0
dataMul:
		PSHUFB BSWAP, B0
		PXOR ACC0, B0

		MOVOU T1, ACC0
		MOVOU T2, ACCM
		MOVOU T1, ACC1

		PSHUFD $78, B0, T0
		PXOR B0, T0
		PCLMULQDQ $0x00, B0, ACC0
		PCLMULQDQ $0x11, B0, ACC1
		PCLMULQDQ $0x00, T0, ACCM

		PXOR ACC0, ACCM
		PXOR ACC1, ACCM
		MOVOU ACCM, T0
		PSRLDQ $8, ACCM
		PSLLDQ $8, T0
		PXOR ACCM, ACC1
		PXOR T0, ACC0

		MOVOU POLY, T0
		PCLMULQDQ $0x01, ACC0, T0
		PSHUFD $78, ACC0, ACC0
		PXOR T0, ACC0

		MOVOU POLY, T0
		PCLMULQDQ $0x01, ACC0, T0
		PSHUFD $78, ACC0, ACC0
		PXOR T0, ACC0
		PXOR ACC1, ACC0

		LEAQ 16(aut), aut

	JMP dataSinglesLoop

dataEnd:

	TESTQ autLen, autLen
	JEQ dataBail

	PXOR B0, B0
	LEAQ -1(aut)(autLen*1), aut

dataLoadLoop:

		PSLLDQ $1, B0
		PINSRB $0, (aut), B0

		LEAQ -1(aut), aut
		DECQ autLen
		JNE dataLoadLoop

	JMP dataMul

dataBail:
	MOVOU ACC0, (tPtr)
	RET
#undef pTbl
#undef aut
#undef tPtr
#undef autLen

// func gcmAesEnc(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32)
TEXT ·gcmAesEnc(SB),0,$256-96
#define pTbl DI
#define ctx DX
#define ctrPtr CX
#define ptx SI
#define ks AX
#define tPtr R8
#define ptxLen R9
#define aluCTR R10
#define aluTMP R11
#define aluK R12
#define NR R13

#define increment(i) ADDL $1, aluCTR; MOVL aluCTR, aluTMP; XORL aluK, aluTMP; BSWAPL aluTMP; MOVL aluTMP, (3*4 + 8*16 + i*16)(SP)
#define aesRnd(k) AESENC k, B0; AESENC k, B1; AESENC k, B2; AESENC k, B3; AESENC k, B4; AESENC k, B5; AESENC k, B6; AESENC k, B7
#define aesRound(i) MOVOU (16*i)(ks), T0;AESENC T0, B0; AESENC T0, B1; AESENC T0, B2; AESENC T0, B3; AESENC T0, B4; AESENC T0, B5; AESENC T0, B6; AESENC T0, B7
#define aesRndLast(k) AESENCLAST k, B0; AESENCLAST k, B1; AESENCLAST k, B2; AESENCLAST k, B3; AESENCLAST k, B4; AESENCLAST k, B5; AESENCLAST k, B6; AESENCLAST k, B7
#define combinedRound(i) \
	MOVOU (16*i)(ks), T0;\
	AESENC T0, B0;\
	AESENC T0, B1;\
	AESENC T0, B2;\
	AESENC T0, B3;\
	 MOVOU (16*(i*2))(pTbl), T1;\
	 MOVOU T1, T2;\
	AESENC T0, B4;\
	AESENC T0, B5;\
	AESENC T0, B6;\
	AESENC T0, B7;\
	 MOVOU (16*i)(SP), T0;\
	 PCLMULQDQ $0x00, T0, T1;\
	 PXOR T1, ACC0;\
	 PSHUFD $78, T0, T1;\
	 PCLMULQDQ $0x11, T0, T2;\
	 PXOR T1, T0;\
	 PXOR T2, ACC1;\
	 MOVOU (16*(i*2+1))(pTbl), T2;\
	 PCLMULQDQ $0x00, T2, T0;\
	 PXOR T0, ACCM
#define mulRound(i) \
	MOVOU (16*i)(SP), T0;\
	MOVOU (16*(i*2))(pTbl), T1;\
	MOVOU T1, T2;\
	PCLMULQDQ $0x00, T0, T1;\
	PXOR T1, ACC0;\
	PCLMULQDQ $0x11, T0, T2;\
	PXOR T2, ACC1;\
	PSHUFD $78, T0, T1;\
	PXOR T1, T0;\
	MOVOU (16*(i*2+1))(pTbl), T1;\
	PCLMULQDQ $0x00, T0, T1;\
	PXOR T1, ACCM

	MOVQ productTable+0(FP), pTbl
	MOVQ dst+8(FP), ctx
	MOVQ src_base+32(FP), ptx
	MOVQ src_len+40(FP), ptxLen
	MOVQ ctr+56(FP), ctrPtr
	MOVQ T+64(FP), tPtr
	MOVQ ks_base+72(FP), ks
	MOVQ ks_len+80(FP), NR

	SHRQ $2, NR
	DECQ NR

	MOVOU bswapMask<>(SB), BSWAP
	MOVOU gcmPoly<>(SB), POLY

	MOVOU (tPtr), ACC0
	PXOR ACC1, ACC1
	PXOR ACCM, ACCM
	MOVOU (ctrPtr), B0
	MOVL (3*4)(ctrPtr), aluCTR
	MOVOU (ks), T0
	MOVL (3*4)(ks), aluK
	BSWAPL aluCTR
	BSWAPL aluK

	PXOR B0, T0
	MOVOU T0, (8*16 + 0*16)(SP)
	increment(0)

	CMPQ ptxLen, $128
	JB gcmAesEncSingles
	SUBQ $128, ptxLen

	// We have at least 8 blocks to encrypt, prepare the rest of the counters
	MOVOU T0, (8*16 + 1*16)(SP)
	increment(1)
	MOVOU T0, (8*16 + 2*16)(SP)
	increment(2)
	MOVOU T0, (8*16 + 3*16)(SP)
	increment(3)
	MOVOU T0, (8*16 + 4*16)(SP)
	increment(4)
	MOVOU T0, (8*16 + 5*16)(SP)
	increment(5)
	MOVOU T0, (8*16 + 6*16)(SP)
	increment(6)
	MOVOU T0, (8*16 + 7*16)(SP)
	increment(7)

	MOVOU (8*16 + 0*16)(SP), B0
	MOVOU (8*16 + 1*16)(SP), B1
	MOVOU (8*16 + 2*16)(SP), B2
	MOVOU (8*16 + 3*16)(SP), B3
	MOVOU (8*16 + 4*16)(SP), B4
	MOVOU (8*16 + 5*16)(SP), B5
	MOVOU (8*16 + 6*16)(SP), B6
	MOVOU (8*16 + 7*16)(SP), B7

	aesRound(1)
	increment(0)
	aesRound(2)
	increment(1)
	aesRound(3)
	increment(2)
	aesRound(4)
	increment(3)
	aesRound(5)
	increment(4)
	aesRound(6)
	increment(5)
	aesRound(7)
	increment(6)
	aesRound(8)
	increment(7)
	aesRound(9)
	MOVOU (16*10)(ks), T0
	CMPQ NR, $12
	JB encLast1
	aesRnd(T0)
	aesRound(11)
	MOVOU (16*12)(ks), T0
	JE encLast1
	aesRnd(T0)
	aesRound(13)
	MOVOU (16*14)(ks), T0
encLast1:
	aesRndLast(T0)

	MOVOU (16*0)(ptx), T0
	PXOR T0, B0
	MOVOU (16*1)(ptx), T0
	PXOR T0, B1
	MOVOU (16*2)(ptx), T0
	PXOR T0, B2
	MOVOU (16*3)(ptx), T0
	PXOR T0, B3
	MOVOU (16*4)(ptx), T0
	PXOR T0, B4
	MOVOU (16*5)(ptx), T0
	PXOR T0, B5
	MOVOU (16*6)(ptx), T0
	PXOR T0, B6
	MOVOU (16*7)(ptx), T0
	PXOR T0, B7

	MOVOU B0, (16*0)(ctx)
	PSHUFB BSWAP, B0
	PXOR ACC0, B0
	MOVOU B1, (16*1)(ctx)
	PSHUFB BSWAP, B1
	MOVOU B2, (16*2)(ctx)
	PSHUFB BSWAP, B2
	MOVOU B3, (16*3)(ctx)
	PSHUFB BSWAP, B3
	MOVOU B4, (16*4)(ctx)
	PSHUFB BSWAP, B4
	MOVOU B5, (16*5)(ctx)
	PSHUFB BSWAP, B5
	MOVOU B6, (16*6)(ctx)
	PSHUFB BSWAP, B6
	MOVOU B7, (16*7)(ctx)
	PSHUFB BSWAP, B7

	MOVOU B0, (16*0)(SP)
	MOVOU B1, (16*1)(SP)
	MOVOU B2, (16*2)(SP)
	MOVOU B3, (16*3)(SP)
	MOVOU B4, (16*4)(SP)
	MOVOU B5, (16*5)(SP)
	MOVOU B6, (16*6)(SP)
	MOVOU B7, (16*7)(SP)

	LEAQ 128(ptx), ptx
	LEAQ 128(ctx), ctx

gcmAesEncOctetsLoop:

		CMPQ ptxLen, $128
		JB gcmAesEncOctetsEnd
		SUBQ $128, ptxLen

		MOVOU (8*16 + 0*16)(SP), B0
		MOVOU (8*16 + 1*16)(SP), B1
		MOVOU (8*16 + 2*16)(SP), B2
		MOVOU (8*16 + 3*16)(SP), B3
		MOVOU (8*16 + 4*16)(SP), B4
		MOVOU (8*16 + 5*16)(SP), B5
		MOVOU (8*16 + 6*16)(SP), B6
		MOVOU (8*16 + 7*16)(SP), B7

		MOVOU (16*0)(SP), T0
		PSHUFD $78, T0, T1
		PXOR T0, T1

		MOVOU (16*0)(pTbl), ACC0
		MOVOU (16*1)(pTbl), ACCM
		MOVOU ACC0, ACC1

		PCLMULQDQ $0x00, T1, ACCM
		PCLMULQDQ $0x00, T0, ACC0
		PCLMULQDQ $0x11, T0, ACC1

		combinedRound(1)
		increment(0)
		combinedRound(2)
		increment(1)
		combinedRound(3)
		increment(2)
		combinedRound(4)
		increment(3)
		combinedRound(5)
		increment(4)
		combinedRound(6)
		increment(5)
		combinedRound(7)
		increment(6)

		aesRound(8)
		increment(7)

		PXOR ACC0, ACCM
		PXOR ACC1, ACCM
		MOVOU ACCM, T0
		PSRLDQ $8, ACCM
		PSLLDQ $8, T0
		PXOR ACCM, ACC1
		PXOR T0, ACC0

		reduceRound(ACC0)
		aesRound(9)

		reduceRound(ACC0)
		PXOR ACC1, ACC0

		MOVOU (16*10)(ks), T0
		CMPQ NR, $12
		JB encLast2
		aesRnd(T0)
		aesRound(11)
		MOVOU (16*12)(ks), T0
		JE encLast2
		aesRnd(T0)
		aesRound(13)
		MOVOU (16*14)(ks), T0
encLast2:
		aesRndLast(T0)

		MOVOU (16*0)(ptx), T0
		PXOR T0, B0
		MOVOU (16*1)(ptx), T0
		PXOR T0, B1
		MOVOU (16*2)(ptx), T0
		PXOR T0, B2
		MOVOU (16*3)(ptx), T0
		PXOR T0, B3
		MOVOU (16*4)(ptx), T0
		PXOR T0, B4
		MOVOU (16*5)(ptx), T0
		PXOR T0, B5
		MOVOU (16*6)(ptx), T0
		PXOR T0, B6
		MOVOU (16*7)(ptx), T0
		PXOR T0, B7

		MOVOU B0, (16*0)(ctx)
		PSHUFB BSWAP, B0
		PXOR ACC0, B0
		MOVOU B1, (16*1)(ctx)
		PSHUFB BSWAP, B1
		MOVOU B2, (16*2)(ctx)
		PSHUFB BSWAP, B2
		MOVOU B3, (16*3)(ctx)
		PSHUFB BSWAP, B3
		MOVOU B4, (16*4)(ctx)
		PSHUFB BSWAP, B4
		MOVOU B5, (16*5)(ctx)
		PSHUFB BSWAP, B5
		MOVOU B6, (16*6)(ctx)
		PSHUFB BSWAP, B6
		MOVOU B7, (16*7)(ctx)
		PSHUFB BSWAP, B7

		MOVOU B0, (16*0)(SP)
		MOVOU B1, (16*1)(SP)
		MOVOU B2, (16*2)(SP)
		MOVOU B3, (16*3)(SP)
		MOVOU B4, (16*4)(SP)
		MOVOU B5, (16*5)(SP)
		MOVOU B6, (16*6)(SP)
		MOVOU B7, (16*7)(SP)

		LEAQ 128(ptx), ptx
		LEAQ 128(ctx), ctx

		JMP gcmAesEncOctetsLoop

gcmAesEncOctetsEnd:

	MOVOU (16*0)(SP), T0
	MOVOU (16*0)(pTbl), ACC0
	MOVOU (16*1)(pTbl), ACCM
	MOVOU ACC0, ACC1
	PSHUFD $78, T0, T1
	PXOR T0, T1
	PCLMULQDQ $0x00, T0, ACC0
	PCLMULQDQ $0x11, T0, ACC1
	PCLMULQDQ $0x00, T1, ACCM

	mulRound(1)
	mulRound(2)
	mulRound(3)
	mulRound(4)
	mulRound(5)
	mulRound(6)
	mulRound(7)

	PXOR ACC0, ACCM
	PXOR ACC1, ACCM
	MOVOU ACCM, T0
	PSRLDQ $8, ACCM
	PSLLDQ $8, T0
	PXOR ACCM, ACC1
	PXOR T0, ACC0

	reduceRound(ACC0)
	reduceRound(ACC0)
	PXOR ACC1, ACC0

	TESTQ ptxLen, ptxLen
	JE gcmAesEncDone

	SUBQ $7, aluCTR

gcmAesEncSingles:

	MOVOU (16*1)(ks), B1
	MOVOU (16*2)(ks), B2
	MOVOU (16*3)(ks), B3
	MOVOU (16*4)(ks), B4
	MOVOU (16*5)(ks), B5
	MOVOU (16*6)(ks), B6
	MOVOU (16*7)(ks), B7

	MOVOU (16*14)(pTbl), T2

gcmAesEncSinglesLoop:

		CMPQ ptxLen, $16
		JB gcmAesEncTail
		SUBQ $16, ptxLen

		MOVOU (8*16 + 0*16)(SP), B0
		increment(0)

		AESENC B1, B0
		AESENC B2, B0
		AESENC B3, B0
		AESENC B4, B0
		AESENC B5, B0
		AESENC B6, B0
		AESENC B7, B0
		MOVOU (16*8)(ks), T0
		AESENC T0, B0
		MOVOU (16*9)(ks), T0
		AESENC T0, B0
		MOVOU (16*10)(ks), T0
		CMPQ NR, $12
		JB encLast3
		AESENC T0, B0
		MOVOU (16*11)(ks), T0
		AESENC T0, B0
		MOVOU (16*12)(ks), T0
		JE encLast3
		AESENC T0, B0
		MOVOU (16*13)(ks), T0
		AESENC T0, B0
		MOVOU (16*14)(ks), T0
encLast3:
		AESENCLAST T0, B0

		MOVOU (ptx), T0
		PXOR T0, B0
		MOVOU B0, (ctx)

		PSHUFB BSWAP, B0
		PXOR ACC0, B0

		MOVOU T2, ACC0
		MOVOU T2, ACC1
		MOVOU (16*15)(pTbl), ACCM

		PSHUFD $78, B0, T0
		PXOR B0, T0
		PCLMULQDQ $0x00, B0, ACC0
		PCLMULQDQ $0x11, B0, ACC1
		PCLMULQDQ $0x00, T0, ACCM

		PXOR ACC0, ACCM
		PXOR ACC1, ACCM
		MOVOU ACCM, T0
		PSRLDQ $8, ACCM
		PSLLDQ $8, T0
		PXOR ACCM, ACC1
		PXOR T0, ACC0

		reduceRound(ACC0)
		reduceRound(ACC0)
		PXOR ACC1, ACC0

		LEAQ (16*1)(ptx), ptx
		LEAQ (16*1)(ctx), ctx

	JMP gcmAesEncSinglesLoop

gcmAesEncTail:
	TESTQ ptxLen, ptxLen
	JE gcmAesEncDone

	MOVOU (8*16 + 0*16)(SP), B0
	AESENC B1, B0
	AESENC B2, B0
	AESENC B3, B0
	AESENC B4, B0
	AESENC B5, B0
	AESENC B6, B0
	AESENC B7, B0
	MOVOU (16*8)(ks), T0
	AESENC T0, B0
	MOVOU (16*9)(ks), T0
	AESENC T0, B0
	MOVOU (16*10)(ks), T0
	CMPQ NR, $12
	JB encLast4
	AESENC T0, B0
	MOVOU (16*11)(ks), T0
	AESENC T0, B0
	MOVOU (16*12)(ks), T0
	JE encLast4
	AESENC T0, B0
	MOVOU (16*13)(ks), T0
	AESENC T0, B0
	MOVOU (16*14)(ks), T0
encLast4:
	AESENCLAST T0, B0
	MOVOU B0, T0

	LEAQ -1(ptx)(ptxLen*1), ptx

	MOVQ ptxLen, aluTMP
	SHLQ $4, aluTMP

	LEAQ andMask<>(SB), aluCTR
	MOVOU -16(aluCTR)(aluTMP*1), T1

	PXOR B0, B0
ptxLoadLoop:
		PSLLDQ $1, B0
		PINSRB $0, (ptx), B0
		LEAQ -1(ptx), ptx
		DECQ ptxLen
	JNE ptxLoadLoop

	PXOR T0, B0
	PAND T1, B0
	MOVOU B0, (ctx)	// I assume there is always space, due to TAG in the end of the CT

	PSHUFB BSWAP, B0
	PXOR ACC0, B0

	MOVOU T2, ACC0
	MOVOU T2, ACC1
	MOVOU (16*15)(pTbl), ACCM

	PSHUFD $78, B0, T0
	PXOR B0, T0
	PCLMULQDQ $0x00, B0, ACC0
	PCLMULQDQ $0x11, B0, ACC1
	PCLMULQDQ $0x00, T0, ACCM

	PXOR ACC0, ACCM
	PXOR ACC1, ACCM
	MOVOU ACCM, T0
	PSRLDQ $8, ACCM
	PSLLDQ $8, T0
	PXOR ACCM, ACC1
	PXOR T0, ACC0

	reduceRound(ACC0)
	reduceRound(ACC0)
	PXOR ACC1, ACC0

gcmAesEncDone:
	MOVOU ACC0, (tPtr)
	RET
#undef increment

// func gcmAesDec(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32)
TEXT ·gcmAesDec(SB),0,$128-96
#define increment(i) ADDL $1, aluCTR; MOVL aluCTR, aluTMP; XORL aluK, aluTMP; BSWAPL aluTMP; MOVL aluTMP, (3*4 + i*16)(SP)
#define combinedDecRound(i) \
	MOVOU (16*i)(ks), T0;\
	AESENC T0, B0;\
	AESENC T0, B1;\
	AESENC T0, B2;\
	AESENC T0, B3;\
	MOVOU (16*(i*2))(pTbl), T1;\
	MOVOU T1, T2;\
	AESENC T0, B4;\
	AESENC T0, B5;\
	AESENC T0, B6;\
	AESENC T0, B7;\
	MOVOU (16*i)(ctx), T0;\
	PSHUFB BSWAP, T0;\
	PCLMULQDQ $0x00, T0, T1;\
	PXOR T1, ACC0;\
	PSHUFD $78, T0, T1;\
	PCLMULQDQ $0x11, T0, T2;\
	PXOR T1, T0;\
	PXOR T2, ACC1;\
	MOVOU (16*(i*2+1))(pTbl), T2;\
	PCLMULQDQ $0x00, T2, T0;\
	PXOR T0, ACCM

	MOVQ productTable+0(FP), pTbl
	MOVQ dst+8(FP), ptx
	MOVQ src_base+32(FP), ctx
	MOVQ src_len+40(FP), ptxLen
	MOVQ ctr+56(FP), ctrPtr
	MOVQ T+64(FP), tPtr
	MOVQ ks_base+72(FP), ks
	MOVQ ks_len+80(FP), NR

	SHRQ $2, NR
	DECQ NR

	MOVOU bswapMask<>(SB), BSWAP
	MOVOU gcmPoly<>(SB), POLY

	MOVOU (tPtr), ACC0
	PXOR ACC1, ACC1
	PXOR ACCM, ACCM
	MOVOU (ctrPtr), B0
	MOVL (3*4)(ctrPtr), aluCTR
	MOVOU (ks), T0
	MOVL (3*4)(ks), aluK
	BSWAPL aluCTR
	BSWAPL aluK

	PXOR B0, T0
	MOVOU T0, (0*16)(SP)
	increment(0)

	CMPQ ptxLen, $128
	JB gcmAesDecSingles

	MOVOU T0, (1*16)(SP)
	increment(1)
	MOVOU T0, (2*16)(SP)
	increment(2)
	MOVOU T0, (3*16)(SP)
	increment(3)
	MOVOU T0, (4*16)(SP)
	increment(4)
	MOVOU T0, (5*16)(SP)
	increment(5)
	MOVOU T0, (6*16)(SP)
	increment(6)
	MOVOU T0, (7*16)(SP)
	increment(7)

gcmAesDecOctetsLoop:

		CMPQ ptxLen, $128
		JB gcmAesDecEndOctets
		SUBQ $128, ptxLen

		MOVOU (0*16)(SP), B0
		MOVOU (1*16)(SP), B1
		MOVOU (2*16)(SP), B2
		MOVOU (3*16)(SP), B3
		MOVOU (4*16)(SP), B4
		MOVOU (5*16)(SP), B5
		MOVOU (6*16)(SP), B6
		MOVOU (7*16)(SP), B7

		MOVOU (16*0)(ctx), T0
		PSHUFB BSWAP, T0
		PXOR ACC0, T0
		PSHUFD $78, T0, T1
		PXOR T0, T1

		MOVOU (16*0)(pTbl), ACC0
		MOVOU (16*1)(pTbl), ACCM
		MOVOU ACC0, ACC1

		PCLMULQDQ $0x00, T1, ACCM
		PCLMULQDQ $0x00, T0, ACC0
		PCLMULQDQ $0x11, T0, ACC1

		combinedDecRound(1)
		increment(0)
		combinedDecRound(2)
		increment(1)
		combinedDecRound(3)
		increment(2)
		combinedDecRound(4)
		increment(3)
		combinedDecRound(5)
		increment(4)
		combinedDecRound(6)
		increment(5)
		combinedDecRound(7)
		increment(6)

		aesRound(8)
		increment(7)

		PXOR ACC0, ACCM
		PXOR ACC1, ACCM
		MOVOU ACCM, T0
		PSRLDQ $8, ACCM
		PSLLDQ $8, T0
		PXOR ACCM, ACC1
		PXOR T0, ACC0

		reduceRound(ACC0)
		aesRound(9)

		reduceRound(ACC0)
		PXOR ACC1, ACC0

		MOVOU (16*10)(ks), T0
		CMPQ NR, $12
		JB decLast1
		aesRnd(T0)
		aesRound(11)
		MOVOU (16*12)(ks), T0
		JE decLast1
		aesRnd(T0)
		aesRound(13)
		MOVOU (16*14)(ks), T0
decLast1:
		aesRndLast(T0)

		MOVOU (16*0)(ctx), T0
		PXOR T0, B0
		MOVOU (16*1)(ctx), T0
		PXOR T0, B1
		MOVOU (16*2)(ctx), T0
		PXOR T0, B2
		MOVOU (16*3)(ctx), T0
		PXOR T0, B3
		MOVOU (16*4)(ctx), T0
		PXOR T0, B4
		MOVOU (16*5)(ctx), T0
		PXOR T0, B5
		MOVOU (16*6)(ctx), T0
		PXOR T0, B6
		MOVOU (16*7)(ctx), T0
		PXOR T0, B7

		MOVOU B0, (16*0)(ptx)
		MOVOU B1, (16*1)(ptx)
		MOVOU B2, (16*2)(ptx)
		MOVOU B3, (16*3)(ptx)
		MOVOU B4, (16*4)(ptx)
		MOVOU B5, (16*5)(ptx)
		MOVOU B6, (16*6)(ptx)
		MOVOU B7, (16*7)(ptx)

		LEAQ 128(ptx), ptx
		LEAQ 128(ctx), ctx

		JMP gcmAesDecOctetsLoop

gcmAesDecEndOctets:

	SUBQ $7, aluCTR

gcmAesDecSingles:

	MOVOU (16*1)(ks), B1
	MOVOU (16*2)(ks), B2
	MOVOU (16*3)(ks), B3
	MOVOU (16*4)(ks), B4
	MOVOU (16*5)(ks), B5
	MOVOU (16*6)(ks), B6
	MOVOU (16*7)(ks), B7

	MOVOU (16*14)(pTbl), T2

gcmAesDecSinglesLoop:

		CMPQ ptxLen, $16
		JB gcmAesDecTail
		SUBQ $16, ptxLen

		MOVOU (ctx), B0
		MOVOU B0, T1
		PSHUFB BSWAP, B0
		PXOR ACC0, B0

		MOVOU T2, ACC0
		MOVOU T2, ACC1
		MOVOU (16*15)(pTbl), ACCM

		PCLMULQDQ $0x00, B0, ACC0
		PCLMULQDQ $0x11, B0, ACC1
		PSHUFD $78, B0, T0
		PXOR B0, T0
		PCLMULQDQ $0x00, T0, ACCM

		PXOR ACC0, ACCM
		PXOR ACC1, ACCM
		MOVOU ACCM, T0
		PSRLDQ $8, ACCM
		PSLLDQ $8, T0
		PXOR ACCM, ACC1
		PXOR T0, ACC0

		reduceRound(ACC0)
		reduceRound(ACC0)
		PXOR ACC1, ACC0

		MOVOU (0*16)(SP), B0
		increment(0)
		AESENC B1, B0
		AESENC B2, B0
		AESENC B3, B0
		AESENC B4, B0
		AESENC B5, B0
		AESENC B6, B0
		AESENC B7, B0
		MOVOU (16*8)(ks), T0
		AESENC T0, B0
		MOVOU (16*9)(ks), T0
		AESENC T0, B0
		MOVOU (16*10)(ks), T0
		CMPQ NR, $12
		JB decLast2
		AESENC T0, B0
		MOVOU (16*11)(ks), T0
		AESENC T0, B0
		MOVOU (16*12)(ks), T0
		JE decLast2
		AESENC T0, B0
		MOVOU (16*13)(ks), T0
		AESENC T0, B0
		MOVOU (16*14)(ks), T0
decLast2:
		AESENCLAST T0, B0

		PXOR T1, B0
		MOVOU B0, (ptx)

		LEAQ (16*1)(ptx), ptx
		LEAQ (16*1)(ctx), ctx

	JMP gcmAesDecSinglesLoop

gcmAesDecTail:

	TESTQ ptxLen, ptxLen
	JE gcmAesDecDone

	MOVQ ptxLen, aluTMP
	SHLQ $4, aluTMP
	LEAQ andMask<>(SB), aluCTR
	MOVOU -16(aluCTR)(aluTMP*1), T1

	MOVOU (ctx), B0	// I assume there is TAG attached to the ctx, and there is no read overflow
	PAND T1, B0

	MOVOU B0, T1
	PSHUFB BSWAP, B0
	PXOR ACC0, B0

	MOVOU (16*14)(pTbl), ACC0
	MOVOU (16*15)(pTbl), ACCM
	MOVOU ACC0, ACC1

	PCLMULQDQ $0x00, B0, ACC0
	PCLMULQDQ $0x11, B0, ACC1
	PSHUFD $78, B0, T0
	PXOR B0, T0
	PCLMULQDQ $0x00, T0, ACCM

	PXOR ACC0, ACCM
	PXOR ACC1, ACCM
	MOVOU ACCM, T0
	PSRLDQ $8, ACCM
	PSLLDQ $8, T0
	PXOR ACCM, ACC1
	PXOR T0, ACC0

	reduceRound(ACC0)
	reduceRound(ACC0)
	PXOR ACC1, ACC0

	MOVOU (0*16)(SP), B0
	increment(0)
	AESENC B1, B0
	AESENC B2, B0
	AESENC B3, B0
	AESENC B4, B0
	AESENC B5, B0
	AESENC B6, B0
	AESENC B7, B0
	MOVOU (16*8)(ks), T0
	AESENC T0, B0
	MOVOU (16*9)(ks), T0
	AESENC T0, B0
	MOVOU (16*10)(ks), T0
	CMPQ NR, $12
	JB decLast3
	AESENC T0, B0
	MOVOU (16*11)(ks), T0
	AESENC T0, B0
	MOVOU (16*12)(ks), T0
	JE decLast3
	AESENC T0, B0
	MOVOU (16*13)(ks), T0
	AESENC T0, B0
	MOVOU (16*14)(ks), T0
decLast3:
	AESENCLAST T0, B0
	PXOR T1, B0

ptxStoreLoop:
		PEXTRB $0, B0, (ptx)
		PSRLDQ $1, B0
		LEAQ 1(ptx), ptx
		DECQ ptxLen

	JNE ptxStoreLoop

gcmAesDecDone:

	MOVOU ACC0, (tPtr)
	RET