// Code generated by command: go run asm.go -out out/aegis_amd64.s -stubs out/stub_amd64.go -pkg aegis. DO NOT EDIT.

//go:build gc && !purego
// +build gc,!purego

#include "textflag.h"

DATA rev64mask<>+0(SB)/8, $0x0001020304050607
DATA rev64mask<>+8(SB)/8, $0x08090a0b0c0d0e0f
GLOBL rev64mask<>(SB), RODATA|NOPTR, $16

DATA C0<>+0(SB)/8, $0x0d08050302010100
DATA C0<>+8(SB)/8, $0x6279e99059372215
GLOBL C0<>(SB), RODATA|NOPTR, $16

DATA C1<>+0(SB)/8, $0xf12fc26d55183ddb
DATA C1<>+8(SB)/8, $0xdd28b57342311120
GLOBL C1<>(SB), RODATA|NOPTR, $16

// func aesRoundAsm(out *[16]byte, in *[16]byte, rk *[16]byte)
// Requires: AES, SSE2
TEXT ·aesRoundAsm(SB), NOSPLIT, $0-24
	MOVQ   in+8(FP), AX
	MOVQ   rk+16(FP), CX
	MOVQ   out+0(FP), DX
	MOVOU  (AX), X0
	MOVOU  (CX), X1
	AESENC X1, X0
	MOVOU  X0, (DX)
	RET

// func seal128LAsm(key *[16]byte, nonce *[16]byte, out []byte, plaintext []byte, additionalData []byte)
// Requires: AES, SSE2, SSE4.1
TEXT ·seal128LAsm(SB), NOSPLIT, $32-88
	MOVQ  out_base+16(FP), AX
	MOVQ  key+0(FP), CX
	MOVQ  nonce+8(FP), DX
	MOVOU (CX), X8
	MOVOU (DX), X9

	// S0 = key ^ nonce
	MOVOU X8, X0
	PXOR  X9, X0

	// S1 = C1
	MOVOU C1<>+0(SB), X1

	// S2 = C0
	MOVOU C0<>+0(SB), X2

	// S3 = C1
	MOVOU C1<>+0(SB), X3

	// S4 = key ^ nonce
	MOVOU X0, X4

	// S5 = key ^ C0
	MOVOU (CX), X5
	PXOR  C0<>+0(SB), X5

	// S6 = key ^ C1
	MOVOU (CX), X6
	PXOR  C1<>+0(SB), X6

	// S7 = key ^ C0
	MOVOU X5, X7

	// Repeat(10, Update(nonce, key))
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR X8, X4
	MOVQ additionalData_base+64(FP), DX

	// ctr := len(additionalData) / 32
	// if ctr == 0 { goto authPartial }
	MOVQ additionalData_len+72(FP), CX
	SHRQ $0x05, CX
	JZ   authPartial

authFull:
	MOVOU (DX), X8
	MOVOU 16(DX), X9
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR X9, X4

	// additionalData = additionalData[32:]
	// ctr--
	// if ctr != 0 { goto authFull }
	ADDQ $0x20, DX
	SUBQ $0x01, CX
	JNZ  authFull

authPartial:
	// adRemain := len(additionalData) % 32
	// if adRemain == 0 { goto encrypt }
	MOVQ  additionalData_len+72(FP), CX
	ANDQ  $0x1f, CX
	JZ    encrypt
	PXOR  X8, X8
	MOVOU X8, (SP)
	MOVOU X8, 16(SP)
	TESTQ CX, CX
	JEQ   move_0__9acb0443
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb0443
	CMPQ  CX, $0x04
	JB    move_3__9acb0443
	JBE   move_4__9acb0443
	CMPQ  CX, $0x08
	JB    move_5through7__9acb0443
	JBE   move_8__9acb0443
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb0443

	// move_17through32
	MOVOU (DX), X8
	MOVOU -16(DX)(CX*1), X9
	MOVOU X8, (SP)
	MOVOU X9, -16(SP)(CX*1)
	JMP   move_0__9acb0443

move_9through16__9acb0443:
	MOVQ (DX), BX
	MOVQ -8(DX)(CX*1), DX
	MOVQ BX, (SP)
	MOVQ DX, -8(SP)(CX*1)
	JMP  move_0__9acb0443

move_8__9acb0443:
	MOVQ (DX), CX
	MOVQ CX, (SP)
	JMP  move_0__9acb0443

move_5through7__9acb0443:
	MOVL (DX), BX
	MOVL -4(DX)(CX*1), DX
	MOVL BX, (SP)
	MOVL DX, -4(SP)(CX*1)
	JMP  move_0__9acb0443

move_4__9acb0443:
	MOVL (DX), CX
	MOVL CX, (SP)
	JMP  move_0__9acb0443

move_3__9acb0443:
	MOVW (DX), CX
	MOVB 2(DX), DL
	MOVW CX, (SP)
	MOVB DL, 2(SP)
	JMP  move_0__9acb0443

move_1or2__9acb0443:
	MOVB (DX), BL
	MOVB -1(DX)(CX*1), DL
	MOVB BL, (SP)
	MOVB DL, -1(SP)(CX*1)

move_0__9acb0443:
	MOVOU (SP), X8
	MOVOU 16(SP), X9
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR X9, X4

encrypt:
	MOVQ plaintext_base+40(FP), DX

	// ctr := len(plaintext) / 32
	// if ctr == 0 { goto encryptPartial }
	MOVQ plaintext_len+48(FP), CX
	SHRQ $0x05, CX
	JZ   encryptPartial

encryptFull:
	// z0 = S6 ^ S1 ^ (S2 & S3)
	MOVOU X3, X8
	PAND  X2, X8
	PXOR  X1, X8
	PXOR  X6, X8

	// z1 = S2 ^ S5 ^ (S6 & S7)
	MOVOU X7, X9
	PAND  X6, X9
	PXOR  X5, X9
	PXOR  X2, X9
	MOVOU (DX), X10
	MOVOU 16(DX), X11

	// outN = tN ^ zN
	PXOR X10, X8
	PXOR X11, X9

	// Update(t0, t1)
	MOVOU X7, X12

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X13
	AESENC X7, X13
	MOVOU  X13, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X13
	AESENC X6, X13
	MOVOU  X13, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X13
	AESENC X5, X13
	MOVOU  X13, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X13
	AESENC X4, X13
	MOVOU  X13, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X13
	AESENC X3, X13
	MOVOU  X13, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X13
	AESENC X2, X13
	MOVOU  X13, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X13
	AESENC X1, X13
	MOVOU  X13, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X12
	MOVOU  X12, X0

	// S[0] ^= M0
	PXOR X10, X0

	// S[4] ^= M1
	PXOR X11, X4

	// ci = out0 || out1
	MOVOU X8, (AX)
	MOVOU X9, 16(AX)

	// ciphertext = ciphertext[32:]
	// plaintext = plaintext[32:]
	// ctr--
	// if ctr != 0 { goto encryptFull }
	ADDQ $0x20, AX
	ADDQ $0x20, DX
	SUBQ $0x01, CX
	JNZ  encryptFull

encryptPartial:
	// ptRemain := len(plaintext) % 32
	// if ptRemain == 0 { goto finalize }
	MOVQ plaintext_len+48(FP), CX
	ANDQ $0x1f, CX
	JZ   finalize

	// z0 = S6 ^ S1 ^ (S2 & S3)
	MOVOU X3, X8
	PAND  X2, X8
	PXOR  X1, X8
	PXOR  X6, X8

	// z1 = S2 ^ S5 ^ (S6 & S7)
	MOVOU X7, X9
	PAND  X6, X9
	PXOR  X5, X9
	PXOR  X2, X9

	// Clear 32 bytes on the stack, copy over the remaining
	// plaintext, then read back t0 and t1 from the stack.
	PXOR  X10, X10
	MOVOU X10, (SP)
	MOVOU X10, 16(SP)
	TESTQ CX, CX
	JEQ   move_0__9acb0444
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb0444
	CMPQ  CX, $0x04
	JB    move_3__9acb0444
	JBE   move_4__9acb0444
	CMPQ  CX, $0x08
	JB    move_5through7__9acb0444
	JBE   move_8__9acb0444
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb0444

	// move_17through32
	MOVOU (DX), X10
	MOVOU -16(DX)(CX*1), X11
	MOVOU X10, (SP)
	MOVOU X11, -16(SP)(CX*1)
	JMP   move_0__9acb0444

move_9through16__9acb0444:
	MOVQ (DX), BX
	MOVQ -8(DX)(CX*1), DX
	MOVQ BX, (SP)
	MOVQ DX, -8(SP)(CX*1)
	JMP  move_0__9acb0444

move_8__9acb0444:
	MOVQ (DX), DX
	MOVQ DX, (SP)
	JMP  move_0__9acb0444

move_5through7__9acb0444:
	MOVL (DX), BX
	MOVL -4(DX)(CX*1), DX
	MOVL BX, (SP)
	MOVL DX, -4(SP)(CX*1)
	JMP  move_0__9acb0444

move_4__9acb0444:
	MOVL (DX), DX
	MOVL DX, (SP)
	JMP  move_0__9acb0444

move_3__9acb0444:
	MOVW (DX), BX
	MOVB 2(DX), DL
	MOVW BX, (SP)
	MOVB DL, 2(SP)
	JMP  move_0__9acb0444

move_1or2__9acb0444:
	MOVB (DX), BL
	MOVB -1(DX)(CX*1), DL
	MOVB BL, (SP)
	MOVB DL, -1(SP)(CX*1)

move_0__9acb0444:
	MOVOU (SP), X10
	MOVOU 16(SP), X11

	// outN = tN ^ zN
	PXOR X10, X8
	PXOR X11, X9

	// Update(t0, t1)
	MOVOU X7, X12

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X13
	AESENC X7, X13
	MOVOU  X13, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X13
	AESENC X6, X13
	MOVOU  X13, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X13
	AESENC X5, X13
	MOVOU  X13, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X13
	AESENC X4, X13
	MOVOU  X13, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X13
	AESENC X3, X13
	MOVOU  X13, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X13
	AESENC X2, X13
	MOVOU  X13, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X13
	AESENC X1, X13
	MOVOU  X13, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X12
	MOVOU  X12, X0

	// S[0] ^= M0
	PXOR X10, X0

	// S[4] ^= M1
	PXOR X11, X4

	// ci = out0 || out1
	MOVOU X8, (SP)
	MOVOU X9, 16(SP)
	TESTQ CX, CX
	JEQ   move_0__9acb0445
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb0445
	CMPQ  CX, $0x04
	JB    move_3__9acb0445
	JBE   move_4__9acb0445
	CMPQ  CX, $0x08
	JB    move_5through7__9acb0445
	JBE   move_8__9acb0445
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb0445

	// move_17through32
	MOVOU (SP), X8
	MOVOU -16(SP)(CX*1), X9
	MOVOU X8, (AX)
	MOVOU X9, -16(AX)(CX*1)
	JMP   move_0__9acb0445

move_9through16__9acb0445:
	MOVQ (SP), DX
	MOVQ -8(SP)(CX*1), BX
	MOVQ DX, (AX)
	MOVQ BX, -8(AX)(CX*1)
	JMP  move_0__9acb0445

move_8__9acb0445:
	MOVQ (SP), DX
	MOVQ DX, (AX)
	JMP  move_0__9acb0445

move_5through7__9acb0445:
	MOVL (SP), DX
	MOVL -4(SP)(CX*1), BX
	MOVL DX, (AX)
	MOVL BX, -4(AX)(CX*1)
	JMP  move_0__9acb0445

move_4__9acb0445:
	MOVL (SP), DX
	MOVL DX, (AX)
	JMP  move_0__9acb0445

move_3__9acb0445:
	MOVW (SP), DX
	MOVB 2(SP), BL
	MOVW DX, (AX)
	MOVB BL, 2(AX)
	JMP  move_0__9acb0445

move_1or2__9acb0445:
	MOVB (SP), DL
	MOVB -1(SP)(CX*1), BL
	MOVB DL, (AX)
	MOVB BL, -1(AX)(CX*1)

move_0__9acb0445:
	ADDQ CX, AX

finalize:
	MOVQ additionalData_len+72(FP), CX
	MOVQ plaintext_len+48(FP), DX

	// t = S2 ^ (LE64(ad_len) || LE64(msg_len))
	SHLQ   $0x03, CX
	SHLQ   $0x03, DX
	MOVQ   CX, X8
	PINSRQ $0x01, DX, X8
	PXOR   X2, X8
	MOVOU  X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X7
	AESENC X6, X7
	MOVOU  X7, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X7
	AESENC X5, X7
	MOVOU  X7, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X7
	AESENC X4, X7
	MOVOU  X7, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X7
	AESENC X3, X7
	MOVOU  X7, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X7
	AESENC X2, X7
	MOVOU  X7, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X7
	AESENC X1, X7
	MOVOU  X7, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR X8, X4

	// tag = S0 ^ S1 ^ S2 ^ S3 ^ S4 ^ S5 ^ S6
	MOVOU X0, X0
	PXOR  X1, X0
	PXOR  X2, X0
	PXOR  X3, X0
	PXOR  X4, X0
	PXOR  X5, X0
	PXOR  X6, X0
	MOVOU X0, (AX)
	PXOR  X0, X0
	PXOR  X1, X1
	PXOR  X2, X2
	PXOR  X3, X3
	PXOR  X4, X4
	PXOR  X5, X5
	PXOR  X6, X6
	PXOR  X7, X7
	RET

// func open128LAsm(key *[16]byte, nonce *[16]byte, out []byte, ciphertext []byte, tag []byte, additionalData []byte) (ok bool)
// Requires: AES, SSE2, SSE4.1
TEXT ·open128LAsm(SB), NOSPLIT, $32-113
	MOVQ  out_base+16(FP), AX
	MOVQ  key+0(FP), CX
	MOVQ  nonce+8(FP), DX
	MOVOU (CX), X8
	MOVOU (DX), X9

	// S0 = key ^ nonce
	MOVOU X8, X0
	PXOR  X9, X0

	// S1 = C1
	MOVOU C1<>+0(SB), X1

	// S2 = C0
	MOVOU C0<>+0(SB), X2

	// S3 = C1
	MOVOU C1<>+0(SB), X3

	// S4 = key ^ nonce
	MOVOU X0, X4

	// S5 = key ^ C0
	MOVOU (CX), X5
	PXOR  C0<>+0(SB), X5

	// S6 = key ^ C1
	MOVOU (CX), X6
	PXOR  C1<>+0(SB), X6

	// S7 = key ^ C0
	MOVOU X5, X7

	// Repeat(10, Update(nonce, key))
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X9, X0

	// S[4] ^= M1
	PXOR X8, X4
	MOVQ additionalData_base+88(FP), DX

	// ctr := len(additionalData) / 32
	// if ctr == 0 { goto authPartial }
	MOVQ additionalData_len+96(FP), CX
	SHRQ $0x05, CX
	JZ   authPartial

authFull:
	MOVOU (DX), X8
	MOVOU 16(DX), X9
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR X9, X4

	// additionalData = additionalData[32:]
	// ctr--
	// if ctr != 0 { goto authFull }
	ADDQ $0x20, DX
	SUBQ $0x01, CX
	JNZ  authFull

authPartial:
	// adRemain := len(additionalData) % 32
	// if adRemain == 0 { goto decrypt }
	MOVQ  additionalData_len+96(FP), CX
	ANDQ  $0x1f, CX
	JZ    decrypt
	PXOR  X8, X8
	MOVOU X8, (SP)
	MOVOU X8, 16(SP)
	TESTQ CX, CX
	JEQ   move_0__9acb0446
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb0446
	CMPQ  CX, $0x04
	JB    move_3__9acb0446
	JBE   move_4__9acb0446
	CMPQ  CX, $0x08
	JB    move_5through7__9acb0446
	JBE   move_8__9acb0446
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb0446

	// move_17through32
	MOVOU (DX), X8
	MOVOU -16(DX)(CX*1), X9
	MOVOU X8, (SP)
	MOVOU X9, -16(SP)(CX*1)
	JMP   move_0__9acb0446

move_9through16__9acb0446:
	MOVQ (DX), BX
	MOVQ -8(DX)(CX*1), DX
	MOVQ BX, (SP)
	MOVQ DX, -8(SP)(CX*1)
	JMP  move_0__9acb0446

move_8__9acb0446:
	MOVQ (DX), CX
	MOVQ CX, (SP)
	JMP  move_0__9acb0446

move_5through7__9acb0446:
	MOVL (DX), BX
	MOVL -4(DX)(CX*1), DX
	MOVL BX, (SP)
	MOVL DX, -4(SP)(CX*1)
	JMP  move_0__9acb0446

move_4__9acb0446:
	MOVL (DX), CX
	MOVL CX, (SP)
	JMP  move_0__9acb0446

move_3__9acb0446:
	MOVW (DX), CX
	MOVB 2(DX), DL
	MOVW CX, (SP)
	MOVB DL, 2(SP)
	JMP  move_0__9acb0446

move_1or2__9acb0446:
	MOVB (DX), BL
	MOVB -1(DX)(CX*1), DL
	MOVB BL, (SP)
	MOVB DL, -1(SP)(CX*1)

move_0__9acb0446:
	MOVOU (SP), X8
	MOVOU 16(SP), X9
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR X9, X4

decrypt:
	MOVQ ciphertext_base+40(FP), DX

	// ctr := len(ciphertext) / 32
	// if ctr == 0 { goto decryptPartial }
	MOVQ ciphertext_len+48(FP), CX
	SHRQ $0x05, CX
	JZ   decryptPartial

decryptFull:
	// z0 = S6 ^ S1 ^ (S2 & S3)
	MOVOU X3, X8
	PAND  X2, X8
	PXOR  X1, X8
	PXOR  X6, X8

	// z1 = S2 ^ S5 ^ (S6 & S7)
	MOVOU X7, X9
	PAND  X6, X9
	PXOR  X5, X9
	PXOR  X2, X9
	MOVOU (DX), X10
	MOVOU 16(DX), X11

	// outN = tN ^ zN
	PXOR X10, X8
	PXOR X11, X9

	// Update(out0, out1)
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR X9, X4

	// xi = out0 || out1
	MOVOU X8, (AX)
	MOVOU X9, 16(AX)

	// plaintext = plaintext[32]
	// ciphertext = ciphertext[32:]
	// ctr--
	// if ctr != 0 { goto decryptFull }
	ADDQ $0x20, AX
	ADDQ $0x20, DX
	SUBQ $0x01, CX
	JNZ  decryptFull

decryptPartial:
	// ctRemain := len(ciphertext) % 32
	// if ctRemain == 0 { goto finalize }
	MOVQ ciphertext_len+48(FP), CX
	ANDQ $0x1f, CX
	JZ   finalize

	// z0 = S6 ^ S1 ^ (S2 & S3)
	MOVOU X3, X8
	PAND  X2, X8
	PXOR  X1, X8
	PXOR  X6, X8

	// z1 = S2 ^ S5 ^ (S6 & S7)
	MOVOU X7, X9
	PAND  X6, X9
	PXOR  X5, X9
	PXOR  X2, X9

	// Clear 32 bytes on the stack, copy over the remaining
	// ciphertext, then read back t0 and t1 from the stack.
	PXOR  X10, X10
	MOVOU X10, (SP)
	MOVOU X10, 16(SP)
	TESTQ CX, CX
	JEQ   move_0__9acb0447
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb0447
	CMPQ  CX, $0x04
	JB    move_3__9acb0447
	JBE   move_4__9acb0447
	CMPQ  CX, $0x08
	JB    move_5through7__9acb0447
	JBE   move_8__9acb0447
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb0447

	// move_17through32
	MOVOU (DX), X10
	MOVOU -16(DX)(CX*1), X11
	MOVOU X10, (SP)
	MOVOU X11, -16(SP)(CX*1)
	JMP   move_0__9acb0447

move_9through16__9acb0447:
	MOVQ (DX), BX
	MOVQ -8(DX)(CX*1), DX
	MOVQ BX, (SP)
	MOVQ DX, -8(SP)(CX*1)
	JMP  move_0__9acb0447

move_8__9acb0447:
	MOVQ (DX), DX
	MOVQ DX, (SP)
	JMP  move_0__9acb0447

move_5through7__9acb0447:
	MOVL (DX), BX
	MOVL -4(DX)(CX*1), DX
	MOVL BX, (SP)
	MOVL DX, -4(SP)(CX*1)
	JMP  move_0__9acb0447

move_4__9acb0447:
	MOVL (DX), DX
	MOVL DX, (SP)
	JMP  move_0__9acb0447

move_3__9acb0447:
	MOVW (DX), BX
	MOVB 2(DX), DL
	MOVW BX, (SP)
	MOVB DL, 2(SP)
	JMP  move_0__9acb0447

move_1or2__9acb0447:
	MOVB (DX), BL
	MOVB -1(DX)(CX*1), DL
	MOVB BL, (SP)
	MOVB DL, -1(SP)(CX*1)

move_0__9acb0447:
	MOVOU (SP), X10
	MOVOU 16(SP), X11

	// outN = tN ^ zN
	PXOR X10, X8
	PXOR X11, X9

	// xn = Truncate(out0 || out1, |cn|)
	MOVOU X8, (SP)
	MOVOU X9, 16(SP)
	TESTQ CX, CX
	JEQ   move_0__9acb0448
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb0448
	CMPQ  CX, $0x04
	JB    move_3__9acb0448
	JBE   move_4__9acb0448
	CMPQ  CX, $0x08
	JB    move_5through7__9acb0448
	JBE   move_8__9acb0448
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb0448

	// move_17through32
	MOVOU (SP), X8
	MOVOU -16(SP)(CX*1), X9
	MOVOU X8, (AX)
	MOVOU X9, -16(AX)(CX*1)
	JMP   move_0__9acb0448

move_9through16__9acb0448:
	MOVQ (SP), DX
	MOVQ -8(SP)(CX*1), BX
	MOVQ DX, (AX)
	MOVQ BX, -8(AX)(CX*1)
	JMP  move_0__9acb0448

move_8__9acb0448:
	MOVQ (SP), DX
	MOVQ DX, (AX)
	JMP  move_0__9acb0448

move_5through7__9acb0448:
	MOVL (SP), DX
	MOVL -4(SP)(CX*1), BX
	MOVL DX, (AX)
	MOVL BX, -4(AX)(CX*1)
	JMP  move_0__9acb0448

move_4__9acb0448:
	MOVL (SP), DX
	MOVL DX, (AX)
	JMP  move_0__9acb0448

move_3__9acb0448:
	MOVW (SP), DX
	MOVB 2(SP), BL
	MOVW DX, (AX)
	MOVB BL, 2(AX)
	JMP  move_0__9acb0448

move_1or2__9acb0448:
	MOVB (SP), DL
	MOVB -1(SP)(CX*1), BL
	MOVB DL, (AX)
	MOVB BL, -1(AX)(CX*1)

move_0__9acb0448:
	// v0, v1 = Split(Pad(xn, 256), 128)
	MOVOU (SP), X8
	MOVOU 16(SP), X9

	// Update(v0, v1)
	MOVOU X7, X10

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X11
	AESENC X7, X11
	MOVOU  X11, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X11
	AESENC X6, X11
	MOVOU  X11, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X10
	MOVOU  X10, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR X9, X4

	// Fix s0 and s4 which were incorrectly calculated because
	// bytes 32-|cn| in |stack_ptr| weren't cleared before
	// loading (v0, v1). Alternatively, we could clear those
	// bits, but this results in simpler code.
	TESTQ CX, CX
	JEQ   clear_0__9acb0449
	CMPQ  CX, $0x02
	JBE   clear_1or2__9acb0449
	CMPQ  CX, $0x04
	JB    clear_3__9acb0449
	JBE   clear_4__9acb0449
	CMPQ  CX, $0x08
	JB    clear_5through7__9acb0449
	JBE   clear_8__9acb0449
	CMPQ  CX, $0x10
	JBE   clear_9through16__9acb0449

	// clear_17through32
	PXOR  X8, X8
	MOVOU X8, (SP)
	MOVOU X8, -16(SP)(CX*1)
	JMP   clear_0__9acb0449

clear_9through16__9acb0449:
	XORQ AX, AX
	MOVQ AX, (SP)
	MOVQ AX, -8(SP)(CX*1)
	JMP  clear_0__9acb0449

clear_8__9acb0449:
	XORQ AX, AX
	MOVQ AX, (SP)
	JMP  clear_0__9acb0449

clear_5through7__9acb0449:
	XORL AX, AX
	MOVL AX, (SP)
	MOVL AX, -4(SP)(CX*1)
	JMP  clear_0__9acb0449

clear_4__9acb0449:
	XORL AX, AX
	MOVL AX, (SP)
	JMP  clear_0__9acb0449

clear_3__9acb0449:
	XORW AX, AX
	MOVW AX, (SP)
	MOVB AL, 2(SP)
	JMP  clear_0__9acb0449

clear_1or2__9acb0449:
	XORB AL, AL
	MOVB AL, (SP)
	MOVB AL, -1(SP)(CX*1)

clear_0__9acb0449:
	MOVOU (SP), X8
	PXOR  X8, X0
	MOVOU 16(SP), X8
	PXOR  X8, X4

finalize:
	MOVQ additionalData_len+96(FP), AX
	MOVQ ciphertext_len+48(FP), CX

	// t = S2 ^ (LE64(ad_len) || LE64(msg_len))
	SHLQ   $0x03, AX
	SHLQ   $0x03, CX
	MOVQ   AX, X8
	PINSRQ $0x01, CX, X8
	PXOR   X2, X8
	MOVOU  X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X10
	AESENC X6, X10
	MOVOU  X10, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X10
	AESENC X5, X10
	MOVOU  X10, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X10
	AESENC X4, X10
	MOVOU  X10, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X10
	AESENC X3, X10
	MOVOU  X10, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X10
	AESENC X2, X10
	MOVOU  X10, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X10
	AESENC X1, X10
	MOVOU  X10, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR  X8, X4
	MOVOU X7, X9

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X6, X10
	AESENC X7, X10
	MOVOU  X10, X7

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X5, X7
	AESENC X6, X7
	MOVOU  X7, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X7
	AESENC X5, X7
	MOVOU  X7, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X7
	AESENC X4, X7
	MOVOU  X7, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X7
	AESENC X3, X7
	MOVOU  X7, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X7
	AESENC X2, X7
	MOVOU  X7, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X7
	AESENC X1, X7
	MOVOU  X7, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M0
	PXOR X8, X0

	// S[4] ^= M1
	PXOR X8, X4

	// tag = S0 ^ S1 ^ S2 ^ S3 ^ S4 ^ S5 ^ S6
	MOVOU X0, X0
	PXOR  X1, X0
	PXOR  X2, X0
	PXOR  X3, X0
	PXOR  X4, X0
	PXOR  X5, X0
	PXOR  X6, X0

	// Constant time tag comparison
	MOVQ     tag_base+64(FP), AX
	MOVOU    (AX), X1
	PCMPEQB  X1, X0
	PMOVMSKB X0, AX
	CMPL     AX, $0x0000ffff
	SETEQ    ok+112(FP)
	PXOR     X0, X0
	MOVOU    X0, (SP)
	MOVOU    X0, 16(SP)
	PXOR     X0, X0
	PXOR     X1, X1
	PXOR     X2, X2
	PXOR     X3, X3
	PXOR     X4, X4
	PXOR     X5, X5
	PXOR     X6, X6
	PXOR     X7, X7
	PXOR     X8, X8
	PXOR     X9, X9
	RET

// func update128LAsm(s *state128L, m *[32]byte)
// Requires: AES, SSE2, SSSE3
TEXT ·update128LAsm(SB), NOSPLIT, $0-16
	MOVQ  s+0(FP), AX
	MOVQ  m+8(FP), CX
	MOVOU rev64mask<>+0(SB), X0

	// Load state
	MOVOU  (AX), X1
	PSHUFB X0, X1
	MOVOU  16(AX), X2
	PSHUFB X0, X2
	MOVOU  32(AX), X3
	PSHUFB X0, X3
	MOVOU  48(AX), X4
	PSHUFB X0, X4
	MOVOU  64(AX), X5
	PSHUFB X0, X5
	MOVOU  80(AX), X6
	PSHUFB X0, X6
	MOVOU  96(AX), X7
	PSHUFB X0, X7
	MOVOU  112(AX), X8
	PSHUFB X0, X8
	MOVOU  (CX), X9
	MOVOU  16(CX), X10
	MOVOU  X8, X11

	// S[7] = AESRound(S[6], S[7])
	MOVOU  X7, X12
	AESENC X8, X12
	MOVOU  X12, X8

	// S[6] = AESRound(S[5], S[6])
	MOVOU  X6, X12
	AESENC X7, X12
	MOVOU  X12, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X5, X12
	AESENC X6, X12
	MOVOU  X12, X6

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X4, X12
	AESENC X5, X12
	MOVOU  X12, X5

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X3, X12
	AESENC X4, X12
	MOVOU  X12, X4

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X2, X12
	AESENC X3, X12
	MOVOU  X12, X3

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X1, X12
	AESENC X2, X12
	MOVOU  X12, X2

	// S[0] = AESRound(tmp, S[0])
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] ^= M0
	PXOR X9, X1

	// S[4] ^= M1
	PXOR X10, X5

	// Store state
	PSHUFB X0, X1
	MOVOU  X1, (AX)
	PSHUFB X0, X2
	MOVOU  X2, 16(AX)
	PSHUFB X0, X3
	MOVOU  X3, 32(AX)
	PSHUFB X0, X4
	MOVOU  X4, 48(AX)
	PSHUFB X0, X5
	MOVOU  X5, 64(AX)
	PSHUFB X0, X6
	MOVOU  X6, 80(AX)
	PSHUFB X0, X7
	MOVOU  X7, 96(AX)
	PSHUFB X0, X8
	MOVOU  X8, 112(AX)
	RET

// func seal256Asm(key *[32]byte, nonce *[32]byte, out []byte, plaintext []byte, additionalData []byte)
// Requires: AES, SSE2, SSE4.1
TEXT ·seal256Asm(SB), NOSPLIT, $16-88
	MOVQ  out_base+16(FP), AX
	MOVQ  key+0(FP), CX
	MOVQ  nonce+8(FP), DX
	MOVOU (CX), X6
	MOVOU 16(CX), X7
	MOVOU (DX), X8
	MOVOU 16(DX), X9

	// S0 = k0 ^ n0
	MOVOU X6, X0
	PXOR  X8, X0

	// S1 = k1 ^ n1
	MOVOU X7, X1
	PXOR  X9, X1

	// S2 = C1
	MOVOU C1<>+0(SB), X2

	// S3 = C0
	MOVOU C0<>+0(SB), X3

	// S4 = k0 ^ C0
	MOVOU X6, X4
	PXOR  C0<>+0(SB), X4

	// S5 = k1 ^ C1
	MOVOU X7, X5
	PXOR  C1<>+0(SB), X5

	// Repeat(4,
	//  Update(k0)
	//  Update(k1)
	//  Update(k0 ^ n0)
	//  Update(k1 ^ n1)
	// )
	MOVOU X6, X10
	PXOR  X8, X10
	MOVOU X7, X8
	PXOR  X9, X8
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X7, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X10, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X8, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X7, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X10, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X8, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X7, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X10, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X8, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X9
	AESENC X5, X9
	MOVOU  X9, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X9
	AESENC X4, X9
	MOVOU  X9, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X9
	AESENC X3, X9
	MOVOU  X9, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X9
	AESENC X2, X9
	MOVOU  X9, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X9
	AESENC X1, X9
	MOVOU  X9, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X6
	MOVOU  X6, X0

	// S[0] ^= M
	PXOR  X7, X0
	MOVOU X5, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X7
	AESENC X5, X7
	MOVOU  X7, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X7
	AESENC X4, X7
	MOVOU  X7, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X7
	AESENC X3, X7
	MOVOU  X7, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X7
	AESENC X2, X7
	MOVOU  X7, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X7
	AESENC X1, X7
	MOVOU  X7, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X6
	MOVOU  X6, X0

	// S[0] ^= M
	PXOR  X10, X0
	MOVOU X5, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X7
	AESENC X5, X7
	MOVOU  X7, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X7
	AESENC X4, X7
	MOVOU  X7, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X7
	AESENC X3, X7
	MOVOU  X7, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X7
	AESENC X2, X7
	MOVOU  X7, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X7
	AESENC X1, X7
	MOVOU  X7, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X6
	MOVOU  X6, X0

	// S[0] ^= M
	PXOR X8, X0
	MOVQ additionalData_base+64(FP), DX

	// ctr := len(additionalData) / 16
	// if ctr == 0 { goto authPartial }
	MOVQ additionalData_len+72(FP), CX
	SHRQ $0x04, CX
	JZ   authPartial

authFull:
	MOVOU (DX), X6
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR X6, X0

	// additionalData = additionalData[16:]
	// ctr--
	// if ctr != 0 { goto authFull }
	ADDQ $0x10, DX
	SUBQ $0x01, CX
	JNZ  authFull

authPartial:
	// adRemain := len(additionalData) % 16
	// if adRemain == 0 { goto encrypt }
	MOVQ  additionalData_len+72(FP), CX
	ANDQ  $0x0f, CX
	JZ    encrypt
	PXOR  X6, X6
	MOVOU X6, (SP)
	TESTQ CX, CX
	JEQ   move_0__9acb044a
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb044a
	CMPQ  CX, $0x04
	JB    move_3__9acb044a
	JBE   move_4__9acb044a
	CMPQ  CX, $0x08
	JB    move_5through7__9acb044a
	JBE   move_8__9acb044a
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb044a

	// move_17through32
	MOVOU (DX), X6
	MOVOU -16(DX)(CX*1), X7
	MOVOU X6, (SP)
	MOVOU X7, -16(SP)(CX*1)
	JMP   move_0__9acb044a

move_9through16__9acb044a:
	MOVQ (DX), BX
	MOVQ -8(DX)(CX*1), DX
	MOVQ BX, (SP)
	MOVQ DX, -8(SP)(CX*1)
	JMP  move_0__9acb044a

move_8__9acb044a:
	MOVQ (DX), CX
	MOVQ CX, (SP)
	JMP  move_0__9acb044a

move_5through7__9acb044a:
	MOVL (DX), BX
	MOVL -4(DX)(CX*1), DX
	MOVL BX, (SP)
	MOVL DX, -4(SP)(CX*1)
	JMP  move_0__9acb044a

move_4__9acb044a:
	MOVL (DX), CX
	MOVL CX, (SP)
	JMP  move_0__9acb044a

move_3__9acb044a:
	MOVW (DX), CX
	MOVB 2(DX), DL
	MOVW CX, (SP)
	MOVB DL, 2(SP)
	JMP  move_0__9acb044a

move_1or2__9acb044a:
	MOVB (DX), BL
	MOVB -1(DX)(CX*1), DL
	MOVB BL, (SP)
	MOVB DL, -1(SP)(CX*1)

move_0__9acb044a:
	MOVOU (SP), X6
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR X6, X0

encrypt:
	MOVQ plaintext_base+40(FP), DX

	// ctr := len(plaintext) / 16
	// if ctr == 0 { goto encryptPartial }
	MOVQ plaintext_len+48(FP), CX
	SHRQ $0x04, CX
	JZ   encryptPartial

encryptFull:
	// z = S1 ^ S4 ^ S5 ^ (S2 & S3)
	MOVOU X3, X6
	PAND  X2, X6
	PXOR  X5, X6
	PXOR  X4, X6
	PXOR  X1, X6

	// Update(xi)
	MOVOU (DX), X7
	MOVOU X5, X8

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X9
	AESENC X5, X9
	MOVOU  X9, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X9
	AESENC X4, X9
	MOVOU  X9, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X9
	AESENC X3, X9
	MOVOU  X9, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X9
	AESENC X2, X9
	MOVOU  X9, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X9
	AESENC X1, X9
	MOVOU  X9, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X8
	MOVOU  X8, X0

	// S[0] ^= M
	PXOR X7, X0

	// ci = xi ^ z
	PXOR  X7, X6
	MOVOU X6, (AX)

	// ciphertext = ciphertext[16:]
	// plaintext = plaintext[16:]
	// ctr--
	// if ctr != 0 { goto encryptFull }
	ADDQ $0x10, AX
	ADDQ $0x10, DX
	SUBQ $0x01, CX
	JNZ  encryptFull

encryptPartial:
	// ptRemain := len(plaintext) % 16
	// if ptRemain == 0 { goto finalize }
	MOVQ plaintext_len+48(FP), CX
	ANDQ $0x0f, CX
	JZ   finalize

	// z = S1 ^ S4 ^ S5 ^ (S2 & S3)
	MOVOU X3, X6
	PAND  X2, X6
	PXOR  X5, X6
	PXOR  X4, X6
	PXOR  X1, X6

	// Clear 16 bytes on the stack, copy over the remaining
	// plaintext, then read back xi from the stack.
	PXOR  X7, X7
	MOVOU X7, (SP)
	TESTQ CX, CX
	JEQ   move_0__9acb044b
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb044b
	CMPQ  CX, $0x04
	JB    move_3__9acb044b
	JBE   move_4__9acb044b
	CMPQ  CX, $0x08
	JB    move_5through7__9acb044b
	JBE   move_8__9acb044b
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb044b

	// move_17through32
	MOVOU (DX), X7
	MOVOU -16(DX)(CX*1), X8
	MOVOU X7, (SP)
	MOVOU X8, -16(SP)(CX*1)
	JMP   move_0__9acb044b

move_9through16__9acb044b:
	MOVQ (DX), BX
	MOVQ -8(DX)(CX*1), DX
	MOVQ BX, (SP)
	MOVQ DX, -8(SP)(CX*1)
	JMP  move_0__9acb044b

move_8__9acb044b:
	MOVQ (DX), DX
	MOVQ DX, (SP)
	JMP  move_0__9acb044b

move_5through7__9acb044b:
	MOVL (DX), BX
	MOVL -4(DX)(CX*1), DX
	MOVL BX, (SP)
	MOVL DX, -4(SP)(CX*1)
	JMP  move_0__9acb044b

move_4__9acb044b:
	MOVL (DX), DX
	MOVL DX, (SP)
	JMP  move_0__9acb044b

move_3__9acb044b:
	MOVW (DX), BX
	MOVB 2(DX), DL
	MOVW BX, (SP)
	MOVB DL, 2(SP)
	JMP  move_0__9acb044b

move_1or2__9acb044b:
	MOVB (DX), BL
	MOVB -1(DX)(CX*1), DL
	MOVB BL, (SP)
	MOVB DL, -1(SP)(CX*1)

move_0__9acb044b:
	// Update(xi)
	MOVOU (SP), X7
	MOVOU X5, X8

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X9
	AESENC X5, X9
	MOVOU  X9, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X9
	AESENC X4, X9
	MOVOU  X9, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X9
	AESENC X3, X9
	MOVOU  X9, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X9
	AESENC X2, X9
	MOVOU  X9, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X9
	AESENC X1, X9
	MOVOU  X9, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X8
	MOVOU  X8, X0

	// S[0] ^= M
	PXOR X7, X0

	// ci = xi ^ z
	PXOR  X7, X6
	MOVOU X6, (SP)
	TESTQ CX, CX
	JEQ   move_0__9acb044c
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb044c
	CMPQ  CX, $0x04
	JB    move_3__9acb044c
	JBE   move_4__9acb044c
	CMPQ  CX, $0x08
	JB    move_5through7__9acb044c
	JBE   move_8__9acb044c
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb044c

	// move_17through32
	MOVOU (SP), X6
	MOVOU -16(SP)(CX*1), X7
	MOVOU X6, (AX)
	MOVOU X7, -16(AX)(CX*1)
	JMP   move_0__9acb044c

move_9through16__9acb044c:
	MOVQ (SP), DX
	MOVQ -8(SP)(CX*1), BX
	MOVQ DX, (AX)
	MOVQ BX, -8(AX)(CX*1)
	JMP  move_0__9acb044c

move_8__9acb044c:
	MOVQ (SP), DX
	MOVQ DX, (AX)
	JMP  move_0__9acb044c

move_5through7__9acb044c:
	MOVL (SP), DX
	MOVL -4(SP)(CX*1), BX
	MOVL DX, (AX)
	MOVL BX, -4(AX)(CX*1)
	JMP  move_0__9acb044c

move_4__9acb044c:
	MOVL (SP), DX
	MOVL DX, (AX)
	JMP  move_0__9acb044c

move_3__9acb044c:
	MOVW (SP), DX
	MOVB 2(SP), BL
	MOVW DX, (AX)
	MOVB BL, 2(AX)
	JMP  move_0__9acb044c

move_1or2__9acb044c:
	MOVB (SP), DL
	MOVB -1(SP)(CX*1), BL
	MOVB DL, (AX)
	MOVB BL, -1(AX)(CX*1)

move_0__9acb044c:
	ADDQ CX, AX

finalize:
	MOVQ additionalData_len+72(FP), CX
	MOVQ plaintext_len+48(FP), DX

	// t = S3 ^ (LE64(ad_len) || LE64(msg_len))
	SHLQ   $0x03, CX
	SHLQ   $0x03, DX
	MOVQ   CX, X6
	PINSRQ $0x01, DX, X6
	PXOR   X3, X6
	MOVOU  X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR X6, X0

	// tag = S0 ^ S1 ^ S2 ^ S3 ^ S4 ^ S5
	MOVOU X0, X0
	PXOR  X1, X0
	PXOR  X2, X0
	PXOR  X3, X0
	PXOR  X4, X0
	PXOR  X5, X0
	MOVOU X0, (AX)
	PXOR  X0, X0
	PXOR  X1, X1
	PXOR  X2, X2
	PXOR  X3, X3
	PXOR  X4, X4
	PXOR  X5, X5
	RET

// func open256Asm(key *[32]byte, nonce *[32]byte, out []byte, ciphertext []byte, tag []byte, additionalData []byte) (ok bool)
// Requires: AES, SSE2, SSE4.1
TEXT ·open256Asm(SB), NOSPLIT, $16-113
	MOVQ  out_base+16(FP), AX
	MOVQ  key+0(FP), CX
	MOVQ  nonce+8(FP), DX
	MOVOU (CX), X6
	MOVOU 16(CX), X7
	MOVOU (DX), X8
	MOVOU 16(DX), X9

	// S0 = k0 ^ n0
	MOVOU X6, X0
	PXOR  X8, X0

	// S1 = k1 ^ n1
	MOVOU X7, X1
	PXOR  X9, X1

	// S2 = C1
	MOVOU C1<>+0(SB), X2

	// S3 = C0
	MOVOU C0<>+0(SB), X3

	// S4 = k0 ^ C0
	MOVOU X6, X4
	PXOR  C0<>+0(SB), X4

	// S5 = k1 ^ C1
	MOVOU X7, X5
	PXOR  C1<>+0(SB), X5

	// Repeat(4,
	//  Update(k0)
	//  Update(k1)
	//  Update(k0 ^ n0)
	//  Update(k1 ^ n1)
	// )
	MOVOU X6, X10
	PXOR  X8, X10
	MOVOU X7, X8
	PXOR  X9, X8
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X7, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X10, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X8, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X7, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X10, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X8, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X7, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X10, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X8, X0
	MOVOU X5, X9

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X11
	AESENC X5, X11
	MOVOU  X11, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X11
	AESENC X4, X11
	MOVOU  X11, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X11
	AESENC X3, X11
	MOVOU  X11, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X11
	AESENC X2, X11
	MOVOU  X11, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X11
	AESENC X1, X11
	MOVOU  X11, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X9
	MOVOU  X9, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X9
	AESENC X5, X9
	MOVOU  X9, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X9
	AESENC X4, X9
	MOVOU  X9, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X9
	AESENC X3, X9
	MOVOU  X9, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X9
	AESENC X2, X9
	MOVOU  X9, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X9
	AESENC X1, X9
	MOVOU  X9, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X6
	MOVOU  X6, X0

	// S[0] ^= M
	PXOR  X7, X0
	MOVOU X5, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X7
	AESENC X5, X7
	MOVOU  X7, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X7
	AESENC X4, X7
	MOVOU  X7, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X7
	AESENC X3, X7
	MOVOU  X7, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X7
	AESENC X2, X7
	MOVOU  X7, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X7
	AESENC X1, X7
	MOVOU  X7, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X6
	MOVOU  X6, X0

	// S[0] ^= M
	PXOR  X10, X0
	MOVOU X5, X6

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X7
	AESENC X5, X7
	MOVOU  X7, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X7
	AESENC X4, X7
	MOVOU  X7, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X7
	AESENC X3, X7
	MOVOU  X7, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X7
	AESENC X2, X7
	MOVOU  X7, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X7
	AESENC X1, X7
	MOVOU  X7, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X6
	MOVOU  X6, X0

	// S[0] ^= M
	PXOR X8, X0
	MOVQ additionalData_base+88(FP), DX

	// ctr := len(additionalData) / 16
	// if ctr == 0 { goto authPartial }
	MOVQ additionalData_len+96(FP), CX
	SHRQ $0x04, CX
	JZ   authPartial

authFull:
	MOVOU (DX), X6
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR X6, X0

	// additionalData = additionalData[16:]
	// ctr--
	// if ctr != 0 { goto authFull }
	ADDQ $0x10, DX
	SUBQ $0x01, CX
	JNZ  authFull

authPartial:
	// adRemain := len(additionalData) % 16
	// if adRemain == 0 { goto decrypt }
	MOVQ  additionalData_len+96(FP), CX
	ANDQ  $0x0f, CX
	JZ    decrypt
	PXOR  X6, X6
	MOVOU X6, (SP)
	TESTQ CX, CX
	JEQ   move_0__9acb044d
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb044d
	CMPQ  CX, $0x04
	JB    move_3__9acb044d
	JBE   move_4__9acb044d
	CMPQ  CX, $0x08
	JB    move_5through7__9acb044d
	JBE   move_8__9acb044d
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb044d

	// move_17through32
	MOVOU (DX), X6
	MOVOU -16(DX)(CX*1), X7
	MOVOU X6, (SP)
	MOVOU X7, -16(SP)(CX*1)
	JMP   move_0__9acb044d

move_9through16__9acb044d:
	MOVQ (DX), BX
	MOVQ -8(DX)(CX*1), DX
	MOVQ BX, (SP)
	MOVQ DX, -8(SP)(CX*1)
	JMP  move_0__9acb044d

move_8__9acb044d:
	MOVQ (DX), CX
	MOVQ CX, (SP)
	JMP  move_0__9acb044d

move_5through7__9acb044d:
	MOVL (DX), BX
	MOVL -4(DX)(CX*1), DX
	MOVL BX, (SP)
	MOVL DX, -4(SP)(CX*1)
	JMP  move_0__9acb044d

move_4__9acb044d:
	MOVL (DX), CX
	MOVL CX, (SP)
	JMP  move_0__9acb044d

move_3__9acb044d:
	MOVW (DX), CX
	MOVB 2(DX), DL
	MOVW CX, (SP)
	MOVB DL, 2(SP)
	JMP  move_0__9acb044d

move_1or2__9acb044d:
	MOVB (DX), BL
	MOVB -1(DX)(CX*1), DL
	MOVB BL, (SP)
	MOVB DL, -1(SP)(CX*1)

move_0__9acb044d:
	MOVOU (SP), X6
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR X6, X0

decrypt:
	MOVQ ciphertext_base+40(FP), DX

	// ctr := len(ciphertext) / 16
	// if ctr == 0 { goto decryptPartial }
	MOVQ ciphertext_len+48(FP), CX
	SHRQ $0x04, CX
	JZ   decryptPartial

decryptFull:
	// z = S1 ^ S4 ^ S5 ^ (S2 & S3)
	MOVOU X3, X6
	PAND  X2, X6
	PXOR  X5, X6
	PXOR  X4, X6
	PXOR  X1, X6

	// xi = ci ^ z
	MOVOU (DX), X7
	PXOR  X7, X6
	MOVOU X6, (AX)

	// Update(xi)
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR X6, X0

	// plaintext = plaintext[16:]
	// ciphertext = ciphertext[16:]
	// ctr--
	// if ctr != 0 { goto decryptFull }
	ADDQ $0x10, AX
	ADDQ $0x10, DX
	SUBQ $0x01, CX
	JNZ  decryptFull

decryptPartial:
	// ctRemain := len(ciphertext) % 16
	// if ctRemain == 0 { goto finalize }
	MOVQ ciphertext_len+48(FP), CX
	ANDQ $0x0f, CX
	JZ   finalize

	// z = S1 ^ S4 ^ S5 ^ (S2 & S3)
	MOVOU X3, X6
	PAND  X2, X6
	PXOR  X5, X6
	PXOR  X4, X6
	PXOR  X1, X6

	// Clear 16 bytes on the stack, copy over the remaining
	// ciphertext, then read back t0 and t1 from the stack.
	PXOR  X7, X7
	MOVOU X7, (SP)
	TESTQ CX, CX
	JEQ   move_0__9acb044e
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb044e
	CMPQ  CX, $0x04
	JB    move_3__9acb044e
	JBE   move_4__9acb044e
	CMPQ  CX, $0x08
	JB    move_5through7__9acb044e
	JBE   move_8__9acb044e
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb044e

	// move_17through32
	MOVOU (DX), X7
	MOVOU -16(DX)(CX*1), X8
	MOVOU X7, (SP)
	MOVOU X8, -16(SP)(CX*1)
	JMP   move_0__9acb044e

move_9through16__9acb044e:
	MOVQ (DX), BX
	MOVQ -8(DX)(CX*1), DX
	MOVQ BX, (SP)
	MOVQ DX, -8(SP)(CX*1)
	JMP  move_0__9acb044e

move_8__9acb044e:
	MOVQ (DX), DX
	MOVQ DX, (SP)
	JMP  move_0__9acb044e

move_5through7__9acb044e:
	MOVL (DX), BX
	MOVL -4(DX)(CX*1), DX
	MOVL BX, (SP)
	MOVL DX, -4(SP)(CX*1)
	JMP  move_0__9acb044e

move_4__9acb044e:
	MOVL (DX), DX
	MOVL DX, (SP)
	JMP  move_0__9acb044e

move_3__9acb044e:
	MOVW (DX), BX
	MOVB 2(DX), DL
	MOVW BX, (SP)
	MOVB DL, 2(SP)
	JMP  move_0__9acb044e

move_1or2__9acb044e:
	MOVB (DX), BL
	MOVB -1(DX)(CX*1), DL
	MOVB BL, (SP)
	MOVB DL, -1(SP)(CX*1)

move_0__9acb044e:
	MOVOU (SP), X7

	// outN = tN ^ zN
	PXOR X7, X6

	// xn = Truncate(out, |cn|)
	MOVOU X6, (SP)
	TESTQ CX, CX
	JEQ   move_0__9acb044f
	CMPQ  CX, $0x02
	JBE   move_1or2__9acb044f
	CMPQ  CX, $0x04
	JB    move_3__9acb044f
	JBE   move_4__9acb044f
	CMPQ  CX, $0x08
	JB    move_5through7__9acb044f
	JBE   move_8__9acb044f
	CMPQ  CX, $0x10
	JBE   move_9through16__9acb044f

	// move_17through32
	MOVOU (SP), X6
	MOVOU -16(SP)(CX*1), X7
	MOVOU X6, (AX)
	MOVOU X7, -16(AX)(CX*1)
	JMP   move_0__9acb044f

move_9through16__9acb044f:
	MOVQ (SP), DX
	MOVQ -8(SP)(CX*1), BX
	MOVQ DX, (AX)
	MOVQ BX, -8(AX)(CX*1)
	JMP  move_0__9acb044f

move_8__9acb044f:
	MOVQ (SP), DX
	MOVQ DX, (AX)
	JMP  move_0__9acb044f

move_5through7__9acb044f:
	MOVL (SP), DX
	MOVL -4(SP)(CX*1), BX
	MOVL DX, (AX)
	MOVL BX, -4(AX)(CX*1)
	JMP  move_0__9acb044f

move_4__9acb044f:
	MOVL (SP), DX
	MOVL DX, (AX)
	JMP  move_0__9acb044f

move_3__9acb044f:
	MOVW (SP), DX
	MOVB 2(SP), BL
	MOVW DX, (AX)
	MOVB BL, 2(AX)
	JMP  move_0__9acb044f

move_1or2__9acb044f:
	MOVB (SP), DL
	MOVB -1(SP)(CX*1), BL
	MOVB DL, (AX)
	MOVB BL, -1(AX)(CX*1)

move_0__9acb044f:
	// v = Pad(xn, 128)
	// Update(v)
	MOVOU (SP), X6
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR X6, X0

	// Fix s0 which was incorrectly calculated because
	// bytes 16-|cn| in |stack_ptr| weren't cleared before
	// loading v. Alternatively, we could clear those
	// bits, but this results in simpler code.
	TESTQ CX, CX
	JEQ   clear_0__9acb0450
	CMPQ  CX, $0x02
	JBE   clear_1or2__9acb0450
	CMPQ  CX, $0x04
	JB    clear_3__9acb0450
	JBE   clear_4__9acb0450
	CMPQ  CX, $0x08
	JB    clear_5through7__9acb0450
	JBE   clear_8__9acb0450
	CMPQ  CX, $0x10
	JBE   clear_9through16__9acb0450

	// clear_17through32
	PXOR  X6, X6
	MOVOU X6, (SP)
	MOVOU X6, -16(SP)(CX*1)
	JMP   clear_0__9acb0450

clear_9through16__9acb0450:
	XORQ AX, AX
	MOVQ AX, (SP)
	MOVQ AX, -8(SP)(CX*1)
	JMP  clear_0__9acb0450

clear_8__9acb0450:
	XORQ AX, AX
	MOVQ AX, (SP)
	JMP  clear_0__9acb0450

clear_5through7__9acb0450:
	XORL AX, AX
	MOVL AX, (SP)
	MOVL AX, -4(SP)(CX*1)
	JMP  clear_0__9acb0450

clear_4__9acb0450:
	XORL AX, AX
	MOVL AX, (SP)
	JMP  clear_0__9acb0450

clear_3__9acb0450:
	XORW AX, AX
	MOVW AX, (SP)
	MOVB AL, 2(SP)
	JMP  clear_0__9acb0450

clear_1or2__9acb0450:
	XORB AL, AL
	MOVB AL, (SP)
	MOVB AL, -1(SP)(CX*1)

clear_0__9acb0450:
	MOVOU (SP), X6
	PXOR  X6, X0

finalize:
	MOVQ additionalData_len+96(FP), AX
	MOVQ ciphertext_len+48(FP), CX

	// t = S3 ^ (LE64(ad_len) || LE64(msg_len))
	SHLQ   $0x03, AX
	SHLQ   $0x03, CX
	MOVQ   AX, X6
	PINSRQ $0x01, CX, X6
	PXOR   X3, X6
	MOVOU  X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR  X6, X0
	MOVOU X5, X7

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X4, X8
	AESENC X5, X8
	MOVOU  X8, X5

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X3, X8
	AESENC X4, X8
	MOVOU  X8, X4

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X2, X8
	AESENC X3, X8
	MOVOU  X8, X3

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X1, X8
	AESENC X2, X8
	MOVOU  X8, X2

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X0, X8
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] = AESRound(tmp, S[0])
	AESENC X0, X7
	MOVOU  X7, X0

	// S[0] ^= M
	PXOR X6, X0

	// tag = S0 ^ S1 ^ S2 ^ S3 ^ S4 ^ S5
	MOVOU X0, X0
	PXOR  X1, X0
	PXOR  X2, X0
	PXOR  X3, X0
	PXOR  X4, X0
	PXOR  X5, X0

	// Constant time tag comparison
	MOVQ     tag_base+64(FP), AX
	MOVOU    (AX), X1
	PCMPEQB  X1, X0
	PMOVMSKB X0, AX
	CMPL     AX, $0x0000ffff
	SETEQ    ok+112(FP)
	PXOR     X0, X0
	MOVOU    X0, (SP)
	PXOR     X0, X0
	PXOR     X1, X1
	PXOR     X2, X2
	PXOR     X3, X3
	PXOR     X4, X4
	PXOR     X5, X5
	PXOR     X6, X6
	RET

// func update256Asm(s *state256, m *[16]byte)
// Requires: AES, SSE2, SSSE3
TEXT ·update256Asm(SB), NOSPLIT, $0-16
	MOVQ  s+0(FP), AX
	MOVQ  m+8(FP), CX
	MOVOU rev64mask<>+0(SB), X0

	// Load state
	MOVOU  (AX), X1
	PSHUFB X0, X1
	MOVOU  16(AX), X2
	PSHUFB X0, X2
	MOVOU  32(AX), X3
	PSHUFB X0, X3
	MOVOU  48(AX), X4
	PSHUFB X0, X4
	MOVOU  64(AX), X5
	PSHUFB X0, X5
	MOVOU  80(AX), X6
	PSHUFB X0, X6
	MOVOU  (CX), X7
	MOVOU  X6, X8

	// S[5] = AESRound(S[4], S[5])
	MOVOU  X5, X9
	AESENC X6, X9
	MOVOU  X9, X6

	// S[4] = AESRound(S[3], S[4])
	MOVOU  X4, X9
	AESENC X5, X9
	MOVOU  X9, X5

	// S[3] = AESRound(S[2], S[3])
	MOVOU  X3, X9
	AESENC X4, X9
	MOVOU  X9, X4

	// S[2] = AESRound(S[1], S[2])
	MOVOU  X2, X9
	AESENC X3, X9
	MOVOU  X9, X3

	// S[1] = AESRound(S[0], S[1])
	MOVOU  X1, X9
	AESENC X2, X9
	MOVOU  X9, X2

	// S[0] = AESRound(tmp, S[0])
	AESENC X1, X8
	MOVOU  X8, X1

	// S[0] ^= M
	PXOR X7, X1

	// Store state
	PSHUFB X0, X1
	MOVOU  X1, (AX)
	PSHUFB X0, X2
	MOVOU  X2, 16(AX)
	PSHUFB X0, X3
	MOVOU  X3, 32(AX)
	PSHUFB X0, X4
	MOVOU  X4, 48(AX)
	PSHUFB X0, X5
	MOVOU  X5, 64(AX)
	PSHUFB X0, X6
	MOVOU  X6, 80(AX)
	PXOR   X1, X1
	PXOR   X2, X2
	PXOR   X3, X3
	PXOR   X4, X4
	PXOR   X5, X5
	PXOR   X6, X6
	RET
