#! /usr/bin/env perl
# Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the OpenSSL license (the "License").  You may not use
# this file except in compliance with the License.  You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html

#
# ====================================================================
# Written by Andy Polyakov <appro@openssl.org> for the OpenSSL
# project. The module is, however, dual licensed under OpenSSL and
# CRYPTOGAMS licenses depending on where you obtain it. For further
# details see http://www.openssl.org/~appro/cryptogams/.
# ====================================================================
#
# December 2014
#
# ChaCha20 for ARMv4.
#
# Performance in cycles per byte out of large buffer.
#
#			IALU/gcc-4.4    1xNEON      3xNEON+1xIALU
#
# Cortex-A5		19.3(*)/+95%    21.8        14.1
# Cortex-A8		10.5(*)/+160%   13.9        6.35
# Cortex-A9		12.9(**)/+110%  14.3        6.50
# Cortex-A15		11.0/+40%       16.0        5.00
# Snapdragon S4		11.5/+125%      13.6        4.90
#
# (*)	most "favourable" result for aligned data on little-endian
#	processor, result for misaligned data is 10-15% lower;
# (**)	this result is a trade-off: it can be improved by 20%,
#	but then Snapdragon S4 and Cortex-A8 results get
#	20-25% worse;

$flavour = shift;
if ($flavour=~/\w[\w\-]*\.\w+$/) { $output=$flavour; undef $flavour; }
else { while (($output=shift) && ($output!~/\w[\w\-]*\.\w+$/)) {} }

if ($flavour && $flavour ne "void") {
    $0 =~ m/(.*[\/\\])[^\/\\]+$/; $dir=$1;
    ( $xlate="${dir}arm-xlate.pl" and -f $xlate ) or
    ( $xlate="${dir}../../perlasm/arm-xlate.pl" and -f $xlate) or
    die "can't locate arm-xlate.pl";

    open STDOUT,"| \"$^X\" $xlate $flavour $output";
} else {
    open STDOUT,">$output";
}

sub AUTOLOAD()		# thunk [simplified] x86-style perlasm
{ my $opcode = $AUTOLOAD; $opcode =~ s/.*:://; $opcode =~ s/_/\./;
  my $arg = pop;
    $arg = "#$arg" if ($arg*1 eq $arg);
    $code .= "\t$opcode\t".join(',',@_,$arg)."\n";
}

my @x=map("r$_",(0..7,"x","x","x","x",12,"x",14,"x"));
my @t=map("r$_",(8..11));

sub ROUND {
my ($a0,$b0,$c0,$d0)=@_;
my ($a1,$b1,$c1,$d1)=map(($_&~3)+(($_+1)&3),($a0,$b0,$c0,$d0));
my ($a2,$b2,$c2,$d2)=map(($_&~3)+(($_+1)&3),($a1,$b1,$c1,$d1));
my ($a3,$b3,$c3,$d3)=map(($_&~3)+(($_+1)&3),($a2,$b2,$c2,$d2));
my $odd = $d0&1;
my ($xc,$xc_) = (@t[0..1]);
my ($xd,$xd_) = $odd ? (@t[2],@x[$d1]) : (@x[$d0],@t[2]);
my @ret;

	# Consider order in which variables are addressed by their
	# index:
	#
	#       a   b   c   d
	#
	#       0   4   8  12 < even round
	#       1   5   9  13
	#       2   6  10  14
	#       3   7  11  15
	#       0   5  10  15 < odd round
	#       1   6  11  12
	#       2   7   8  13
	#       3   4   9  14
	#
	# 'a', 'b' are permanently allocated in registers, @x[0..7],
	# while 'c's and pair of 'd's are maintained in memory. If
	# you observe 'c' column, you'll notice that pair of 'c's is
	# invariant between rounds. This means that we have to reload
	# them once per round, in the middle. This is why you'll see
	# bunch of 'c' stores and loads in the middle, but none in
	# the beginning or end. If you observe 'd' column, you'll
	# notice that 15 and 13 are reused in next pair of rounds.
	# This is why these two are chosen for offloading to memory,
	# to make loads count more.
							push @ret,(
	"&add	(@x[$a0],@x[$a0],@x[$b0])",
	"&mov	($xd,$xd,'ror#16')",
	 "&add	(@x[$a1],@x[$a1],@x[$b1])",
	 "&mov	($xd_,$xd_,'ror#16')",
	"&eor	($xd,$xd,@x[$a0],'ror#16')",
	 "&eor	($xd_,$xd_,@x[$a1],'ror#16')",

	"&add	($xc,$xc,$xd)",
	"&mov	(@x[$b0],@x[$b0],'ror#20')",
	 "&add	($xc_,$xc_,$xd_)",
	 "&mov	(@x[$b1],@x[$b1],'ror#20')",
	"&eor	(@x[$b0],@x[$b0],$xc,'ror#20')",
	 "&eor	(@x[$b1],@x[$b1],$xc_,'ror#20')",

	"&add	(@x[$a0],@x[$a0],@x[$b0])",
	"&mov	($xd,$xd,'ror#24')",
	 "&add	(@x[$a1],@x[$a1],@x[$b1])",
	 "&mov	($xd_,$xd_,'ror#24')",
	"&eor	($xd,$xd,@x[$a0],'ror#24')",
	 "&eor	($xd_,$xd_,@x[$a1],'ror#24')",

	"&add	($xc,$xc,$xd)",
	"&mov	(@x[$b0],@x[$b0],'ror#25')"		);
							push @ret,(
	"&str	($xd,'[sp,#4*(16+$d0)]')",
	"&ldr	($xd,'[sp,#4*(16+$d2)]')"		) if ($odd);
							push @ret,(
	 "&add	($xc_,$xc_,$xd_)",
	 "&mov	(@x[$b1],@x[$b1],'ror#25')"		);
							push @ret,(
	 "&str	($xd_,'[sp,#4*(16+$d1)]')",
	 "&ldr	($xd_,'[sp,#4*(16+$d3)]')"		) if (!$odd);
							push @ret,(
	"&eor	(@x[$b0],@x[$b0],$xc,'ror#25')",
	 "&eor	(@x[$b1],@x[$b1],$xc_,'ror#25')"	);

	$xd=@x[$d2]					if (!$odd);
	$xd_=@x[$d3]					if ($odd);
							push @ret,(
	"&str	($xc,'[sp,#4*(16+$c0)]')",
	"&ldr	($xc,'[sp,#4*(16+$c2)]')",
	"&add	(@x[$a2],@x[$a2],@x[$b2])",
	"&mov	($xd,$xd,'ror#16')",
	 "&str	($xc_,'[sp,#4*(16+$c1)]')",
	 "&ldr	($xc_,'[sp,#4*(16+$c3)]')",
	 "&add	(@x[$a3],@x[$a3],@x[$b3])",
	 "&mov	($xd_,$xd_,'ror#16')",
	"&eor	($xd,$xd,@x[$a2],'ror#16')",
	 "&eor	($xd_,$xd_,@x[$a3],'ror#16')",

	"&add	($xc,$xc,$xd)",
	"&mov	(@x[$b2],@x[$b2],'ror#20')",
	 "&add	($xc_,$xc_,$xd_)",
	 "&mov	(@x[$b3],@x[$b3],'ror#20')",
	"&eor	(@x[$b2],@x[$b2],$xc,'ror#20')",
	 "&eor	(@x[$b3],@x[$b3],$xc_,'ror#20')",

	"&add	(@x[$a2],@x[$a2],@x[$b2])",
	"&mov	($xd,$xd,'ror#24')",
	 "&add	(@x[$a3],@x[$a3],@x[$b3])",
	 "&mov	($xd_,$xd_,'ror#24')",
	"&eor	($xd,$xd,@x[$a2],'ror#24')",
	 "&eor	($xd_,$xd_,@x[$a3],'ror#24')",

	"&add	($xc,$xc,$xd)",
	"&mov	(@x[$b2],@x[$b2],'ror#25')",
	 "&add	($xc_,$xc_,$xd_)",
	 "&mov	(@x[$b3],@x[$b3],'ror#25')",
	"&eor	(@x[$b2],@x[$b2],$xc,'ror#25')",
	 "&eor	(@x[$b3],@x[$b3],$xc_,'ror#25')"	);

	@ret;
}

$code.=<<___;
#include "arm_arch.h"

.text
#if defined(__thumb2__) || defined(__clang__)
.syntax	unified
#endif
#if defined(__thumb2__)
.thumb
#else
.code	32
#endif

#if defined(__thumb2__) || defined(__clang__)
#define ldrhsb	ldrbhs
#endif

.align	5
.Lsigma:
.long	0x61707865,0x3320646e,0x79622d32,0x6b206574	@ endian-neutral
.Lone:
.long	1,0,0,0
#if __ARM_MAX_ARCH__>=7
.LOPENSSL_armcap:
.word   OPENSSL_armcap_P-.LChaCha20_ctr32
#else
.word	-1
#endif

.globl	ChaCha20_ctr32
.type	ChaCha20_ctr32,%function
.align	5
ChaCha20_ctr32:
.LChaCha20_ctr32:
	ldr	r12,[sp,#0]		@ pull pointer to counter and nonce
	stmdb	sp!,{r0-r2,r4-r11,lr}
#if __ARM_ARCH__<7 && !defined(__thumb2__)
	sub	r14,pc,#16		@ ChaCha20_ctr32
#else
	adr	r14,.LChaCha20_ctr32
#endif
	cmp	r2,#0			@ len==0?
#ifdef	__thumb2__
	itt	eq
#endif
	addeq	sp,sp,#4*3
	beq	.Lno_data
#if __ARM_MAX_ARCH__>=7
	cmp	r2,#192			@ test len
	bls	.Lshort
	ldr	r4,[r14,#-32]
	ldr	r4,[r14,r4]
# ifdef	__APPLE__
	ldr	r4,[r4]
# endif
	tst	r4,#ARMV7_NEON
	bne	.LChaCha20_neon
.Lshort:
#endif
	ldmia	r12,{r4-r7}		@ load counter and nonce
	sub	sp,sp,#4*(16)		@ off-load area
	sub	r14,r14,#64		@ .Lsigma
	stmdb	sp!,{r4-r7}		@ copy counter and nonce
	ldmia	r3,{r4-r11}		@ load key
	ldmia	r14,{r0-r3}		@ load sigma
	stmdb	sp!,{r4-r11}		@ copy key
	stmdb	sp!,{r0-r3}		@ copy sigma
	str	r10,[sp,#4*(16+10)]	@ off-load "@x[10]"
	str	r11,[sp,#4*(16+11)]	@ off-load "@x[11]"
	b	.Loop_outer_enter

.align	4
.Loop_outer:
	ldmia	sp,{r0-r9}		@ load key material
	str	@t[3],[sp,#4*(32+2)]	@ save len
	str	r12,  [sp,#4*(32+1)]	@ save inp
	str	r14,  [sp,#4*(32+0)]	@ save out
.Loop_outer_enter:
	ldr	@t[3], [sp,#4*(15)]
	ldr	@x[12],[sp,#4*(12)]	@ modulo-scheduled load
	ldr	@t[2], [sp,#4*(13)]
	ldr	@x[14],[sp,#4*(14)]
	str	@t[3], [sp,#4*(16+15)]
	mov	@t[3],#10
	b	.Loop

.align	4
.Loop:
	subs	@t[3],@t[3],#1
___
	foreach (&ROUND(0, 4, 8,12)) { eval; }
	foreach (&ROUND(0, 5,10,15)) { eval; }
$code.=<<___;
	bne	.Loop

	ldr	@t[3],[sp,#4*(32+2)]	@ load len

	str	@t[0], [sp,#4*(16+8)]	@ modulo-scheduled store
	str	@t[1], [sp,#4*(16+9)]
	str	@x[12],[sp,#4*(16+12)]
	str	@t[2], [sp,#4*(16+13)]
	str	@x[14],[sp,#4*(16+14)]

	@ at this point we have first half of 512-bit result in
	@ @x[0-7] and second half at sp+4*(16+8)

	cmp	@t[3],#64		@ done yet?
#ifdef	__thumb2__
	itete	lo
#endif
	addlo	r12,sp,#4*(0)		@ shortcut or ...
	ldrhs	r12,[sp,#4*(32+1)]	@ ... load inp
	addlo	r14,sp,#4*(0)		@ shortcut or ...
	ldrhs	r14,[sp,#4*(32+0)]	@ ... load out

	ldr	@t[0],[sp,#4*(0)]	@ load key material
	ldr	@t[1],[sp,#4*(1)]

#if __ARM_ARCH__>=6 || !defined(__ARMEB__)
# if __ARM_ARCH__<7
	orr	@t[2],r12,r14
	tst	@t[2],#3		@ are input and output aligned?
	ldr	@t[2],[sp,#4*(2)]
	bne	.Lunaligned
	cmp	@t[3],#64		@ restore flags
# else
	ldr	@t[2],[sp,#4*(2)]
# endif
	ldr	@t[3],[sp,#4*(3)]

	add	@x[0],@x[0],@t[0]	@ accumulate key material
	add	@x[1],@x[1],@t[1]
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhs	@t[0],[r12],#16		@ load input
	ldrhs	@t[1],[r12,#-12]

	add	@x[2],@x[2],@t[2]
	add	@x[3],@x[3],@t[3]
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhs	@t[2],[r12,#-8]
	ldrhs	@t[3],[r12,#-4]
# if __ARM_ARCH__>=6 && defined(__ARMEB__)
	rev	@x[0],@x[0]
	rev	@x[1],@x[1]
	rev	@x[2],@x[2]
	rev	@x[3],@x[3]
# endif
# ifdef	__thumb2__
	itt	hs
# endif
	eorhs	@x[0],@x[0],@t[0]	@ xor with input
	eorhs	@x[1],@x[1],@t[1]
	 add	@t[0],sp,#4*(4)
	str	@x[0],[r14],#16		@ store output
# ifdef	__thumb2__
	itt	hs
# endif
	eorhs	@x[2],@x[2],@t[2]
	eorhs	@x[3],@x[3],@t[3]
	 ldmia	@t[0],{@t[0]-@t[3]}	@ load key material
	str	@x[1],[r14,#-12]
	str	@x[2],[r14,#-8]
	str	@x[3],[r14,#-4]

	add	@x[4],@x[4],@t[0]	@ accumulate key material
	add	@x[5],@x[5],@t[1]
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhs	@t[0],[r12],#16		@ load input
	ldrhs	@t[1],[r12,#-12]
	add	@x[6],@x[6],@t[2]
	add	@x[7],@x[7],@t[3]
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhs	@t[2],[r12,#-8]
	ldrhs	@t[3],[r12,#-4]
# if __ARM_ARCH__>=6 && defined(__ARMEB__)
	rev	@x[4],@x[4]
	rev	@x[5],@x[5]
	rev	@x[6],@x[6]
	rev	@x[7],@x[7]
# endif
# ifdef	__thumb2__
	itt	hs
# endif
	eorhs	@x[4],@x[4],@t[0]
	eorhs	@x[5],@x[5],@t[1]
	 add	@t[0],sp,#4*(8)
	str	@x[4],[r14],#16		@ store output
# ifdef	__thumb2__
	itt	hs
# endif
	eorhs	@x[6],@x[6],@t[2]
	eorhs	@x[7],@x[7],@t[3]
	str	@x[5],[r14,#-12]
	 ldmia	@t[0],{@t[0]-@t[3]}	@ load key material
	str	@x[6],[r14,#-8]
	 add	@x[0],sp,#4*(16+8)
	str	@x[7],[r14,#-4]

	ldmia	@x[0],{@x[0]-@x[7]}	@ load second half

	add	@x[0],@x[0],@t[0]	@ accumulate key material
	add	@x[1],@x[1],@t[1]
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhs	@t[0],[r12],#16		@ load input
	ldrhs	@t[1],[r12,#-12]
# ifdef	__thumb2__
	itt	hi
# endif
	 strhi	@t[2],[sp,#4*(16+10)]	@ copy "@x[10]" while at it
	 strhi	@t[3],[sp,#4*(16+11)]	@ copy "@x[11]" while at it
	add	@x[2],@x[2],@t[2]
	add	@x[3],@x[3],@t[3]
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhs	@t[2],[r12,#-8]
	ldrhs	@t[3],[r12,#-4]
# if __ARM_ARCH__>=6 && defined(__ARMEB__)
	rev	@x[0],@x[0]
	rev	@x[1],@x[1]
	rev	@x[2],@x[2]
	rev	@x[3],@x[3]
# endif
# ifdef	__thumb2__
	itt	hs
# endif
	eorhs	@x[0],@x[0],@t[0]
	eorhs	@x[1],@x[1],@t[1]
	 add	@t[0],sp,#4*(12)
	str	@x[0],[r14],#16		@ store output
# ifdef	__thumb2__
	itt	hs
# endif
	eorhs	@x[2],@x[2],@t[2]
	eorhs	@x[3],@x[3],@t[3]
	str	@x[1],[r14,#-12]
	 ldmia	@t[0],{@t[0]-@t[3]}	@ load key material
	str	@x[2],[r14,#-8]
	str	@x[3],[r14,#-4]

	add	@x[4],@x[4],@t[0]	@ accumulate key material
	add	@x[5],@x[5],@t[1]
# ifdef	__thumb2__
	itt	hi
# endif
	 addhi	@t[0],@t[0],#1		@ next counter value
	 strhi	@t[0],[sp,#4*(12)]	@ save next counter value
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhs	@t[0],[r12],#16		@ load input
	ldrhs	@t[1],[r12,#-12]
	add	@x[6],@x[6],@t[2]
	add	@x[7],@x[7],@t[3]
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhs	@t[2],[r12,#-8]
	ldrhs	@t[3],[r12,#-4]
# if __ARM_ARCH__>=6 && defined(__ARMEB__)
	rev	@x[4],@x[4]
	rev	@x[5],@x[5]
	rev	@x[6],@x[6]
	rev	@x[7],@x[7]
# endif
# ifdef	__thumb2__
	itt	hs
# endif
	eorhs	@x[4],@x[4],@t[0]
	eorhs	@x[5],@x[5],@t[1]
# ifdef	__thumb2__
	 it	ne
# endif
	 ldrne	@t[0],[sp,#4*(32+2)]	@ re-load len
# ifdef	__thumb2__
	itt	hs
# endif
	eorhs	@x[6],@x[6],@t[2]
	eorhs	@x[7],@x[7],@t[3]
	str	@x[4],[r14],#16		@ store output
	str	@x[5],[r14,#-12]
# ifdef	__thumb2__
	it	hs
# endif
	 subhs	@t[3],@t[0],#64		@ len-=64
	str	@x[6],[r14,#-8]
	str	@x[7],[r14,#-4]
	bhi	.Loop_outer

	beq	.Ldone
# if __ARM_ARCH__<7
	b	.Ltail

.align	4
.Lunaligned:				@ unaligned endian-neutral path
	cmp	@t[3],#64		@ restore flags
# endif
#endif
#if __ARM_ARCH__<7
	ldr	@t[3],[sp,#4*(3)]
___
for ($i=0;$i<16;$i+=4) {
my $j=$i&0x7;

$code.=<<___	if ($i==4);
	add	@x[0],sp,#4*(16+8)
___
$code.=<<___	if ($i==8);
	ldmia	@x[0],{@x[0]-@x[7]}		@ load second half
# ifdef	__thumb2__
	itt	hi
# endif
	strhi	@t[2],[sp,#4*(16+10)]		@ copy "@x[10]"
	strhi	@t[3],[sp,#4*(16+11)]		@ copy "@x[11]"
___
$code.=<<___;
	add	@x[$j+0],@x[$j+0],@t[0]		@ accumulate key material
___
$code.=<<___	if ($i==12);
# ifdef	__thumb2__
	itt	hi
# endif
	addhi	@t[0],@t[0],#1			@ next counter value
	strhi	@t[0],[sp,#4*(12)]		@ save next counter value
___
$code.=<<___;
	add	@x[$j+1],@x[$j+1],@t[1]
	add	@x[$j+2],@x[$j+2],@t[2]
# ifdef	__thumb2__
	itete	lo
# endif
	eorlo	@t[0],@t[0],@t[0]		@ zero or ...
	ldrhsb	@t[0],[r12],#16			@ ... load input
	eorlo	@t[1],@t[1],@t[1]
	ldrhsb	@t[1],[r12,#-12]

	add	@x[$j+3],@x[$j+3],@t[3]
# ifdef	__thumb2__
	itete	lo
# endif
	eorlo	@t[2],@t[2],@t[2]
	ldrhsb	@t[2],[r12,#-8]
	eorlo	@t[3],@t[3],@t[3]
	ldrhsb	@t[3],[r12,#-4]

	eor	@x[$j+0],@t[0],@x[$j+0]		@ xor with input (or zero)
	eor	@x[$j+1],@t[1],@x[$j+1]
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhsb	@t[0],[r12,#-15]		@ load more input
	ldrhsb	@t[1],[r12,#-11]
	eor	@x[$j+2],@t[2],@x[$j+2]
	 strb	@x[$j+0],[r14],#16		@ store output
	eor	@x[$j+3],@t[3],@x[$j+3]
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhsb	@t[2],[r12,#-7]
	ldrhsb	@t[3],[r12,#-3]
	 strb	@x[$j+1],[r14,#-12]
	eor	@x[$j+0],@t[0],@x[$j+0],lsr#8
	 strb	@x[$j+2],[r14,#-8]
	eor	@x[$j+1],@t[1],@x[$j+1],lsr#8
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhsb	@t[0],[r12,#-14]		@ load more input
	ldrhsb	@t[1],[r12,#-10]
	 strb	@x[$j+3],[r14,#-4]
	eor	@x[$j+2],@t[2],@x[$j+2],lsr#8
	 strb	@x[$j+0],[r14,#-15]
	eor	@x[$j+3],@t[3],@x[$j+3],lsr#8
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhsb	@t[2],[r12,#-6]
	ldrhsb	@t[3],[r12,#-2]
	 strb	@x[$j+1],[r14,#-11]
	eor	@x[$j+0],@t[0],@x[$j+0],lsr#8
	 strb	@x[$j+2],[r14,#-7]
	eor	@x[$j+1],@t[1],@x[$j+1],lsr#8
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhsb	@t[0],[r12,#-13]		@ load more input
	ldrhsb	@t[1],[r12,#-9]
	 strb	@x[$j+3],[r14,#-3]
	eor	@x[$j+2],@t[2],@x[$j+2],lsr#8
	 strb	@x[$j+0],[r14,#-14]
	eor	@x[$j+3],@t[3],@x[$j+3],lsr#8
# ifdef	__thumb2__
	itt	hs
# endif
	ldrhsb	@t[2],[r12,#-5]
	ldrhsb	@t[3],[r12,#-1]
	 strb	@x[$j+1],[r14,#-10]
	 strb	@x[$j+2],[r14,#-6]
	eor	@x[$j+0],@t[0],@x[$j+0],lsr#8
	 strb	@x[$j+3],[r14,#-2]
	eor	@x[$j+1],@t[1],@x[$j+1],lsr#8
	 strb	@x[$j+0],[r14,#-13]
	eor	@x[$j+2],@t[2],@x[$j+2],lsr#8
	 strb	@x[$j+1],[r14,#-9]
	eor	@x[$j+3],@t[3],@x[$j+3],lsr#8
	 strb	@x[$j+2],[r14,#-5]
	 strb	@x[$j+3],[r14,#-1]
___
$code.=<<___	if ($i<12);
	add	@t[0],sp,#4*(4+$i)
	ldmia	@t[0],{@t[0]-@t[3]}		@ load key material
___
}
$code.=<<___;
# ifdef	__thumb2__
	it	ne
# endif
	ldrne	@t[0],[sp,#4*(32+2)]		@ re-load len
# ifdef	__thumb2__
	it	hs
# endif
	subhs	@t[3],@t[0],#64			@ len-=64
	bhi	.Loop_outer

	beq	.Ldone
#endif

.Ltail:
	ldr	r12,[sp,#4*(32+1)]	@ load inp
	add	@t[1],sp,#4*(0)
	ldr	r14,[sp,#4*(32+0)]	@ load out

.Loop_tail:
	ldrb	@t[2],[@t[1]],#1	@ read buffer on stack
	ldrb	@t[3],[r12],#1		@ read input
	subs	@t[0],@t[0],#1
	eor	@t[3],@t[3],@t[2]
	strb	@t[3],[r14],#1		@ store output
	bne	.Loop_tail

.Ldone:
	add	sp,sp,#4*(32+3)
.Lno_data:
	ldmia	sp!,{r4-r11,pc}
.size	ChaCha20_ctr32,.-ChaCha20_ctr32
___

{{{
my ($a0,$b0,$c0,$d0,$a1,$b1,$c1,$d1,$a2,$b2,$c2,$d2,$t0,$t1,$t2,$t3) =
    map("q$_",(0..15));

sub NEONROUND {
my $odd = pop;
my ($a,$b,$c,$d,$t)=@_;

	(
	"&vadd_i32	($a,$a,$b)",
	"&veor		($d,$d,$a)",
	"&vrev32_16	($d,$d)",	# vrot ($d,16)

	"&vadd_i32	($c,$c,$d)",
	"&veor		($t,$b,$c)",
	"&vshr_u32	($b,$t,20)",
	"&vsli_32	($b,$t,12)",

	"&vadd_i32	($a,$a,$b)",
	"&veor		($t,$d,$a)",
	"&vshr_u32	($d,$t,24)",
	"&vsli_32	($d,$t,8)",

	"&vadd_i32	($c,$c,$d)",
	"&veor		($t,$b,$c)",
	"&vshr_u32	($b,$t,25)",
	"&vsli_32	($b,$t,7)",

	"&vext_8	($c,$c,$c,8)",
	"&vext_8	($b,$b,$b,$odd?12:4)",
	"&vext_8	($d,$d,$d,$odd?4:12)"
	);
}

$code.=<<___;
#if __ARM_MAX_ARCH__>=7
.arch	armv7-a
.fpu	neon

.type	ChaCha20_neon,%function
.align	5
ChaCha20_neon:
	ldr		r12,[sp,#0]		@ pull pointer to counter and nonce
	stmdb		sp!,{r0-r2,r4-r11,lr}
.LChaCha20_neon:
	adr		r14,.Lsigma
	vstmdb		sp!,{d8-d15}		@ ABI spec says so
	stmdb		sp!,{r0-r3}

	vld1.32		{$b0-$c0},[r3]		@ load key
	ldmia		r3,{r4-r11}		@ load key

	sub		sp,sp,#4*(16+16)
	vld1.32		{$d0},[r12]		@ load counter and nonce
	add		r12,sp,#4*8
	ldmia		r14,{r0-r3}		@ load sigma
	vld1.32		{$a0},[r14]!		@ load sigma
	vld1.32		{$t0},[r14]		@ one
	vst1.32		{$c0-$d0},[r12]		@ copy 1/2key|counter|nonce
	vst1.32		{$a0-$b0},[sp]		@ copy sigma|1/2key

	str		r10,[sp,#4*(16+10)]	@ off-load "@x[10]"
	str		r11,[sp,#4*(16+11)]	@ off-load "@x[11]"
	vshl.i32	$t1#lo,$t0#lo,#1	@ two
	vstr		$t0#lo,[sp,#4*(16+0)]
	vshl.i32	$t2#lo,$t0#lo,#2	@ four
	vstr		$t1#lo,[sp,#4*(16+2)]
	vmov		$a1,$a0
	vstr		$t2#lo,[sp,#4*(16+4)]
	vmov		$a2,$a0
	vmov		$b1,$b0
	vmov		$b2,$b0
	b		.Loop_neon_enter

.align	4
.Loop_neon_outer:
	ldmia		sp,{r0-r9}		@ load key material
	cmp		@t[3],#64*2		@ if len<=64*2
	bls		.Lbreak_neon		@ switch to integer-only
	vmov		$a1,$a0
	str		@t[3],[sp,#4*(32+2)]	@ save len
	vmov		$a2,$a0
	str		r12,  [sp,#4*(32+1)]	@ save inp
	vmov		$b1,$b0
	str		r14,  [sp,#4*(32+0)]	@ save out
	vmov		$b2,$b0
.Loop_neon_enter:
	ldr		@t[3], [sp,#4*(15)]
	vadd.i32	$d1,$d0,$t0		@ counter+1
	ldr		@x[12],[sp,#4*(12)]	@ modulo-scheduled load
	vmov		$c1,$c0
	ldr		@t[2], [sp,#4*(13)]
	vmov		$c2,$c0
	ldr		@x[14],[sp,#4*(14)]
	vadd.i32	$d2,$d1,$t0		@ counter+2
	str		@t[3], [sp,#4*(16+15)]
	mov		@t[3],#10
	add		@x[12],@x[12],#3	@ counter+3
	b		.Loop_neon

.align	4
.Loop_neon:
	subs		@t[3],@t[3],#1
___
	my @thread0=&NEONROUND($a0,$b0,$c0,$d0,$t0,0);
	my @thread1=&NEONROUND($a1,$b1,$c1,$d1,$t1,0);
	my @thread2=&NEONROUND($a2,$b2,$c2,$d2,$t2,0);
	my @thread3=&ROUND(0,4,8,12);

	foreach (@thread0) {
		eval;			eval(shift(@thread3));
		eval(shift(@thread1));	eval(shift(@thread3));
		eval(shift(@thread2));	eval(shift(@thread3));
	}

	@thread0=&NEONROUND($a0,$b0,$c0,$d0,$t0,1);
	@thread1=&NEONROUND($a1,$b1,$c1,$d1,$t1,1);
	@thread2=&NEONROUND($a2,$b2,$c2,$d2,$t2,1);
	@thread3=&ROUND(0,5,10,15);

	foreach (@thread0) {
		eval;			eval(shift(@thread3));
		eval(shift(@thread1));	eval(shift(@thread3));
		eval(shift(@thread2));	eval(shift(@thread3));
	}
$code.=<<___;
	bne		.Loop_neon

	add		@t[3],sp,#32
	vld1.32		{$t0-$t1},[sp]		@ load key material
	vld1.32		{$t2-$t3},[@t[3]]

	ldr		@t[3],[sp,#4*(32+2)]	@ load len

	str		@t[0], [sp,#4*(16+8)]	@ modulo-scheduled store
	str		@t[1], [sp,#4*(16+9)]
	str		@x[12],[sp,#4*(16+12)]
	str		@t[2], [sp,#4*(16+13)]
	str		@x[14],[sp,#4*(16+14)]

	@ at this point we have first half of 512-bit result in
	@ @x[0-7] and second half at sp+4*(16+8)

	ldr		r12,[sp,#4*(32+1)]	@ load inp
	ldr		r14,[sp,#4*(32+0)]	@ load out

	vadd.i32	$a0,$a0,$t0		@ accumulate key material
	vadd.i32	$a1,$a1,$t0
	vadd.i32	$a2,$a2,$t0
	vldr		$t0#lo,[sp,#4*(16+0)]	@ one

	vadd.i32	$b0,$b0,$t1
	vadd.i32	$b1,$b1,$t1
	vadd.i32	$b2,$b2,$t1
	vldr		$t1#lo,[sp,#4*(16+2)]	@ two

	vadd.i32	$c0,$c0,$t2
	vadd.i32	$c1,$c1,$t2
	vadd.i32	$c2,$c2,$t2
	vadd.i32	$d1#lo,$d1#lo,$t0#lo	@ counter+1
	vadd.i32	$d2#lo,$d2#lo,$t1#lo	@ counter+2

	vadd.i32	$d0,$d0,$t3
	vadd.i32	$d1,$d1,$t3
	vadd.i32	$d2,$d2,$t3

	cmp		@t[3],#64*4
	blo		.Ltail_neon

	vld1.8		{$t0-$t1},[r12]!	@ load input
	 mov		@t[3],sp
	vld1.8		{$t2-$t3},[r12]!
	veor		$a0,$a0,$t0		@ xor with input
	veor		$b0,$b0,$t1
	vld1.8		{$t0-$t1},[r12]!
	veor		$c0,$c0,$t2
	veor		$d0,$d0,$t3
	vld1.8		{$t2-$t3},[r12]!

	veor		$a1,$a1,$t0
	 vst1.8		{$a0-$b0},[r14]!	@ store output
	veor		$b1,$b1,$t1
	vld1.8		{$t0-$t1},[r12]!
	veor		$c1,$c1,$t2
	 vst1.8		{$c0-$d0},[r14]!
	veor		$d1,$d1,$t3
	vld1.8		{$t2-$t3},[r12]!

	veor		$a2,$a2,$t0
	 vld1.32	{$a0-$b0},[@t[3]]!	@ load for next iteration
	 veor		$t0#hi,$t0#hi,$t0#hi
	 vldr		$t0#lo,[sp,#4*(16+4)]	@ four
	veor		$b2,$b2,$t1
	 vld1.32	{$c0-$d0},[@t[3]]
	veor		$c2,$c2,$t2
	 vst1.8		{$a1-$b1},[r14]!
	veor		$d2,$d2,$t3
	 vst1.8		{$c1-$d1},[r14]!

	vadd.i32	$d0#lo,$d0#lo,$t0#lo	@ next counter value
	vldr		$t0#lo,[sp,#4*(16+0)]	@ one

	ldmia		sp,{@t[0]-@t[3]}	@ load key material
	add		@x[0],@x[0],@t[0]	@ accumulate key material
	ldr		@t[0],[r12],#16		@ load input
	 vst1.8		{$a2-$b2},[r14]!
	add		@x[1],@x[1],@t[1]
	ldr		@t[1],[r12,#-12]
	 vst1.8		{$c2-$d2},[r14]!
	add		@x[2],@x[2],@t[2]
	ldr		@t[2],[r12,#-8]
	add		@x[3],@x[3],@t[3]
	ldr		@t[3],[r12,#-4]
# ifdef	__ARMEB__
	rev		@x[0],@x[0]
	rev		@x[1],@x[1]
	rev		@x[2],@x[2]
	rev		@x[3],@x[3]
# endif
	eor		@x[0],@x[0],@t[0]	@ xor with input
	 add		@t[0],sp,#4*(4)
	eor		@x[1],@x[1],@t[1]
	str		@x[0],[r14],#16		@ store output
	eor		@x[2],@x[2],@t[2]
	str		@x[1],[r14,#-12]
	eor		@x[3],@x[3],@t[3]
	 ldmia		@t[0],{@t[0]-@t[3]}	@ load key material
	str		@x[2],[r14,#-8]
	str		@x[3],[r14,#-4]

	add		@x[4],@x[4],@t[0]	@ accumulate key material
	ldr		@t[0],[r12],#16		@ load input
	add		@x[5],@x[5],@t[1]
	ldr		@t[1],[r12,#-12]
	add		@x[6],@x[6],@t[2]
	ldr		@t[2],[r12,#-8]
	add		@x[7],@x[7],@t[3]
	ldr		@t[3],[r12,#-4]
# ifdef	__ARMEB__
	rev		@x[4],@x[4]
	rev		@x[5],@x[5]
	rev		@x[6],@x[6]
	rev		@x[7],@x[7]
# endif
	eor		@x[4],@x[4],@t[0]
	 add		@t[0],sp,#4*(8)
	eor		@x[5],@x[5],@t[1]
	str		@x[4],[r14],#16		@ store output
	eor		@x[6],@x[6],@t[2]
	str		@x[5],[r14,#-12]
	eor		@x[7],@x[7],@t[3]
	 ldmia		@t[0],{@t[0]-@t[3]}	@ load key material
	str		@x[6],[r14,#-8]
	 add		@x[0],sp,#4*(16+8)
	str		@x[7],[r14,#-4]

	ldmia		@x[0],{@x[0]-@x[7]}	@ load second half

	add		@x[0],@x[0],@t[0]	@ accumulate key material
	ldr		@t[0],[r12],#16		@ load input
	add		@x[1],@x[1],@t[1]
	ldr		@t[1],[r12,#-12]
# ifdef	__thumb2__
	it	hi
# endif
	 strhi		@t[2],[sp,#4*(16+10)]	@ copy "@x[10]" while at it
	add		@x[2],@x[2],@t[2]
	ldr		@t[2],[r12,#-8]
# ifdef	__thumb2__
	it	hi
# endif
	 strhi		@t[3],[sp,#4*(16+11)]	@ copy "@x[11]" while at it
	add		@x[3],@x[3],@t[3]
	ldr		@t[3],[r12,#-4]
# ifdef	__ARMEB__
	rev		@x[0],@x[0]
	rev		@x[1],@x[1]
	rev		@x[2],@x[2]
	rev		@x[3],@x[3]
# endif
	eor		@x[0],@x[0],@t[0]
	 add		@t[0],sp,#4*(12)
	eor		@x[1],@x[1],@t[1]
	str		@x[0],[r14],#16		@ store output
	eor		@x[2],@x[2],@t[2]
	str		@x[1],[r14,#-12]
	eor		@x[3],@x[3],@t[3]
	 ldmia		@t[0],{@t[0]-@t[3]}	@ load key material
	str		@x[2],[r14,#-8]
	str		@x[3],[r14,#-4]

	add		@x[4],@x[4],@t[0]	@ accumulate key material
	 add		@t[0],@t[0],#4		@ next counter value
	add		@x[5],@x[5],@t[1]
	 str		@t[0],[sp,#4*(12)]	@ save next counter value
	ldr		@t[0],[r12],#16		@ load input
	add		@x[6],@x[6],@t[2]
	 add		@x[4],@x[4],#3		@ counter+3
	ldr		@t[1],[r12,#-12]
	add		@x[7],@x[7],@t[3]
	ldr		@t[2],[r12,#-8]
	ldr		@t[3],[r12,#-4]
# ifdef	__ARMEB__
	rev		@x[4],@x[4]
	rev		@x[5],@x[5]
	rev		@x[6],@x[6]
	rev		@x[7],@x[7]
# endif
	eor		@x[4],@x[4],@t[0]
# ifdef	__thumb2__
	it	hi
# endif
	 ldrhi		@t[0],[sp,#4*(32+2)]	@ re-load len
	eor		@x[5],@x[5],@t[1]
	eor		@x[6],@x[6],@t[2]
	str		@x[4],[r14],#16		@ store output
	eor		@x[7],@x[7],@t[3]
	str		@x[5],[r14,#-12]
	 sub		@t[3],@t[0],#64*4	@ len-=64*4
	str		@x[6],[r14,#-8]
	str		@x[7],[r14,#-4]
	bhi		.Loop_neon_outer

	b		.Ldone_neon

.align	4
.Lbreak_neon:
	@ harmonize NEON and integer-only stack frames: load data
	@ from NEON frame, but save to integer-only one; distance
	@ between the two is 4*(32+4+16-32)=4*(20).

	str		@t[3], [sp,#4*(20+32+2)]	@ save len
	 add		@t[3],sp,#4*(32+4)
	str		r12,   [sp,#4*(20+32+1)]	@ save inp
	str		r14,   [sp,#4*(20+32+0)]	@ save out

	ldr		@x[12],[sp,#4*(16+10)]
	ldr		@x[14],[sp,#4*(16+11)]
	 vldmia		@t[3],{d8-d15}			@ fulfill ABI requirement
	str		@x[12],[sp,#4*(20+16+10)]	@ copy "@x[10]"
	str		@x[14],[sp,#4*(20+16+11)]	@ copy "@x[11]"

	ldr		@t[3], [sp,#4*(15)]
	ldr		@x[12],[sp,#4*(12)]		@ modulo-scheduled load
	ldr		@t[2], [sp,#4*(13)]
	ldr		@x[14],[sp,#4*(14)]
	str		@t[3], [sp,#4*(20+16+15)]
	add		@t[3],sp,#4*(20)
	vst1.32		{$a0-$b0},[@t[3]]!		@ copy key
	add		sp,sp,#4*(20)			@ switch frame
	vst1.32		{$c0-$d0},[@t[3]]
	mov		@t[3],#10
	b		.Loop				@ go integer-only

.align	4
.Ltail_neon:
	cmp		@t[3],#64*3
	bhs		.L192_or_more_neon
	cmp		@t[3],#64*2
	bhs		.L128_or_more_neon
	cmp		@t[3],#64*1
	bhs		.L64_or_more_neon

	add		@t[0],sp,#4*(8)
	vst1.8		{$a0-$b0},[sp]
	add		@t[2],sp,#4*(0)
	vst1.8		{$c0-$d0},[@t[0]]
	b		.Loop_tail_neon

.align	4
.L64_or_more_neon:
	vld1.8		{$t0-$t1},[r12]!
	vld1.8		{$t2-$t3},[r12]!
	veor		$a0,$a0,$t0
	veor		$b0,$b0,$t1
	veor		$c0,$c0,$t2
	veor		$d0,$d0,$t3
	vst1.8		{$a0-$b0},[r14]!
	vst1.8		{$c0-$d0},[r14]!

	beq		.Ldone_neon

	add		@t[0],sp,#4*(8)
	vst1.8		{$a1-$b1},[sp]
	add		@t[2],sp,#4*(0)
	vst1.8		{$c1-$d1},[@t[0]]
	sub		@t[3],@t[3],#64*1	@ len-=64*1
	b		.Loop_tail_neon

.align	4
.L128_or_more_neon:
	vld1.8		{$t0-$t1},[r12]!
	vld1.8		{$t2-$t3},[r12]!
	veor		$a0,$a0,$t0
	veor		$b0,$b0,$t1
	vld1.8		{$t0-$t1},[r12]!
	veor		$c0,$c0,$t2
	veor		$d0,$d0,$t3
	vld1.8		{$t2-$t3},[r12]!

	veor		$a1,$a1,$t0
	veor		$b1,$b1,$t1
	 vst1.8		{$a0-$b0},[r14]!
	veor		$c1,$c1,$t2
	 vst1.8		{$c0-$d0},[r14]!
	veor		$d1,$d1,$t3
	vst1.8		{$a1-$b1},[r14]!
	vst1.8		{$c1-$d1},[r14]!

	beq		.Ldone_neon

	add		@t[0],sp,#4*(8)
	vst1.8		{$a2-$b2},[sp]
	add		@t[2],sp,#4*(0)
	vst1.8		{$c2-$d2},[@t[0]]
	sub		@t[3],@t[3],#64*2	@ len-=64*2
	b		.Loop_tail_neon

.align	4
.L192_or_more_neon:
	vld1.8		{$t0-$t1},[r12]!
	vld1.8		{$t2-$t3},[r12]!
	veor		$a0,$a0,$t0
	veor		$b0,$b0,$t1
	vld1.8		{$t0-$t1},[r12]!
	veor		$c0,$c0,$t2
	veor		$d0,$d0,$t3
	vld1.8		{$t2-$t3},[r12]!

	veor		$a1,$a1,$t0
	veor		$b1,$b1,$t1
	vld1.8		{$t0-$t1},[r12]!
	veor		$c1,$c1,$t2
	 vst1.8		{$a0-$b0},[r14]!
	veor		$d1,$d1,$t3
	vld1.8		{$t2-$t3},[r12]!

	veor		$a2,$a2,$t0
	 vst1.8		{$c0-$d0},[r14]!
	veor		$b2,$b2,$t1
	 vst1.8		{$a1-$b1},[r14]!
	veor		$c2,$c2,$t2
	 vst1.8		{$c1-$d1},[r14]!
	veor		$d2,$d2,$t3
	vst1.8		{$a2-$b2},[r14]!
	vst1.8		{$c2-$d2},[r14]!

	beq		.Ldone_neon

	ldmia		sp,{@t[0]-@t[3]}	@ load key material
	add		@x[0],@x[0],@t[0]	@ accumulate key material
	 add		@t[0],sp,#4*(4)
	add		@x[1],@x[1],@t[1]
	add		@x[2],@x[2],@t[2]
	add		@x[3],@x[3],@t[3]
	 ldmia		@t[0],{@t[0]-@t[3]}	@ load key material

	add		@x[4],@x[4],@t[0]	@ accumulate key material
	 add		@t[0],sp,#4*(8)
	add		@x[5],@x[5],@t[1]
	add		@x[6],@x[6],@t[2]
	add		@x[7],@x[7],@t[3]
	 ldmia		@t[0],{@t[0]-@t[3]}	@ load key material
# ifdef	__ARMEB__
	rev		@x[0],@x[0]
	rev		@x[1],@x[1]
	rev		@x[2],@x[2]
	rev		@x[3],@x[3]
	rev		@x[4],@x[4]
	rev		@x[5],@x[5]
	rev		@x[6],@x[6]
	rev		@x[7],@x[7]
# endif
	stmia		sp,{@x[0]-@x[7]}
	 add		@x[0],sp,#4*(16+8)

	ldmia		@x[0],{@x[0]-@x[7]}	@ load second half

	add		@x[0],@x[0],@t[0]	@ accumulate key material
	 add		@t[0],sp,#4*(12)
	add		@x[1],@x[1],@t[1]
	add		@x[2],@x[2],@t[2]
	add		@x[3],@x[3],@t[3]
	 ldmia		@t[0],{@t[0]-@t[3]}	@ load key material

	add		@x[4],@x[4],@t[0]	@ accumulate key material
	 add		@t[0],sp,#4*(8)
	add		@x[5],@x[5],@t[1]
	 add		@x[4],@x[4],#3		@ counter+3
	add		@x[6],@x[6],@t[2]
	add		@x[7],@x[7],@t[3]
	 ldr		@t[3],[sp,#4*(32+2)]	@ re-load len
# ifdef	__ARMEB__
	rev		@x[0],@x[0]
	rev		@x[1],@x[1]
	rev		@x[2],@x[2]
	rev		@x[3],@x[3]
	rev		@x[4],@x[4]
	rev		@x[5],@x[5]
	rev		@x[6],@x[6]
	rev		@x[7],@x[7]
# endif
	stmia		@t[0],{@x[0]-@x[7]}
	 add		@t[2],sp,#4*(0)
	 sub		@t[3],@t[3],#64*3	@ len-=64*3

.Loop_tail_neon:
	ldrb		@t[0],[@t[2]],#1	@ read buffer on stack
	ldrb		@t[1],[r12],#1		@ read input
	subs		@t[3],@t[3],#1
	eor		@t[0],@t[0],@t[1]
	strb		@t[0],[r14],#1		@ store output
	bne		.Loop_tail_neon

.Ldone_neon:
	add		sp,sp,#4*(32+4)
	vldmia		sp,{d8-d15}
	add		sp,sp,#4*(16+3)
	ldmia		sp!,{r4-r11,pc}
.size	ChaCha20_neon,.-ChaCha20_neon
.comm	OPENSSL_armcap_P,4,4
#endif
___
}}}

foreach (split("\n",$code)) {
	s/\`([^\`]*)\`/eval $1/geo;

	s/\bq([0-9]+)#(lo|hi)/sprintf "d%d",2*$1+($2 eq "hi")/geo;

	print $_,"\n";
}
close STDOUT or die "error closing STDOUT: $!";


#! /usr/bin/env perl
# Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the OpenSSL license (the "License").  You may not use
# this file except in compliance with the License.  You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html

#
# ====================================================================
# Written by Andy Polyakov <appro@openssl.org> for the OpenSSL
# project. The module is, however, dual licensed under OpenSSL and
# CRYPTOGAMS licenses depending on where you obtain it. For further
# details see http://www.openssl.org/~appro/cryptogams/.
# ====================================================================
#
# June 2015
#
# ChaCha20 for ARMv8.
#
# Performance in cycles per byte out of large buffer.
#
#			IALU/gcc-4.9    3xNEON+1xIALU	6xNEON+2xIALU
#
# Apple A7		5.50/+49%       3.33            1.70
# Cortex-A53		8.40/+80%       4.72		4.72(*)
# Cortex-A57		8.06/+43%       4.90            4.43(**)
# Denver		4.50/+82%       2.63		2.67(*)
# X-Gene		9.50/+46%       8.82		8.89(*)
# Mongoose		8.00/+44%	3.64		3.25
# Kryo			8.17/+50%	4.83		4.65
#
# (*)	it's expected that doubling interleave factor doesn't help
#	all processors, only those with higher NEON latency and
#	higher instruction issue rate;
# (**)	expected improvement was actually higher;

$flavour=shift;
$output=shift;

$0 =~ m/(.*[\/\\])[^\/\\]+$/; $dir=$1;
( $xlate="${dir}arm-xlate.pl" and -f $xlate ) or
( $xlate="${dir}../../perlasm/arm-xlate.pl" and -f $xlate) or
die "can't locate arm-xlate.pl";

open OUT,"| \"$^X\" $xlate $flavour $output";
*STDOUT=*OUT;

sub AUTOLOAD()		# thunk [simplified] x86-style perlasm
{ my $opcode = $AUTOLOAD; $opcode =~ s/.*:://; $opcode =~ s/_/\./;
  my $arg = pop;
    $arg = "#$arg" if ($arg*1 eq $arg);
    $code .= "\t$opcode\t".join(',',@_,$arg)."\n";
}

my ($out,$inp,$len,$key,$ctr) = map("x$_",(0..4));

my @x=map("x$_",(5..17,19..21));
my @d=map("x$_",(22..28,30));

sub ROUND {
my ($a0,$b0,$c0,$d0)=@_;
my ($a1,$b1,$c1,$d1)=map(($_&~3)+(($_+1)&3),($a0,$b0,$c0,$d0));
my ($a2,$b2,$c2,$d2)=map(($_&~3)+(($_+1)&3),($a1,$b1,$c1,$d1));
my ($a3,$b3,$c3,$d3)=map(($_&~3)+(($_+1)&3),($a2,$b2,$c2,$d2));

    (
	"&add_32	(@x[$a0],@x[$a0],@x[$b0])",
	 "&add_32	(@x[$a1],@x[$a1],@x[$b1])",
	  "&add_32	(@x[$a2],@x[$a2],@x[$b2])",
	   "&add_32	(@x[$a3],@x[$a3],@x[$b3])",
	"&eor_32	(@x[$d0],@x[$d0],@x[$a0])",
	 "&eor_32	(@x[$d1],@x[$d1],@x[$a1])",
	  "&eor_32	(@x[$d2],@x[$d2],@x[$a2])",
	   "&eor_32	(@x[$d3],@x[$d3],@x[$a3])",
	"&ror_32	(@x[$d0],@x[$d0],16)",
	 "&ror_32	(@x[$d1],@x[$d1],16)",
	  "&ror_32	(@x[$d2],@x[$d2],16)",
	   "&ror_32	(@x[$d3],@x[$d3],16)",

	"&add_32	(@x[$c0],@x[$c0],@x[$d0])",
	 "&add_32	(@x[$c1],@x[$c1],@x[$d1])",
	  "&add_32	(@x[$c2],@x[$c2],@x[$d2])",
	   "&add_32	(@x[$c3],@x[$c3],@x[$d3])",
	"&eor_32	(@x[$b0],@x[$b0],@x[$c0])",
	 "&eor_32	(@x[$b1],@x[$b1],@x[$c1])",
	  "&eor_32	(@x[$b2],@x[$b2],@x[$c2])",
	   "&eor_32	(@x[$b3],@x[$b3],@x[$c3])",
	"&ror_32	(@x[$b0],@x[$b0],20)",
	 "&ror_32	(@x[$b1],@x[$b1],20)",
	  "&ror_32	(@x[$b2],@x[$b2],20)",
	   "&ror_32	(@x[$b3],@x[$b3],20)",

	"&add_32	(@x[$a0],@x[$a0],@x[$b0])",
	 "&add_32	(@x[$a1],@x[$a1],@x[$b1])",
	  "&add_32	(@x[$a2],@x[$a2],@x[$b2])",
	   "&add_32	(@x[$a3],@x[$a3],@x[$b3])",
	"&eor_32	(@x[$d0],@x[$d0],@x[$a0])",
	 "&eor_32	(@x[$d1],@x[$d1],@x[$a1])",
	  "&eor_32	(@x[$d2],@x[$d2],@x[$a2])",
	   "&eor_32	(@x[$d3],@x[$d3],@x[$a3])",
	"&ror_32	(@x[$d0],@x[$d0],24)",
	 "&ror_32	(@x[$d1],@x[$d1],24)",
	  "&ror_32	(@x[$d2],@x[$d2],24)",
	   "&ror_32	(@x[$d3],@x[$d3],24)",

	"&add_32	(@x[$c0],@x[$c0],@x[$d0])",
	 "&add_32	(@x[$c1],@x[$c1],@x[$d1])",
	  "&add_32	(@x[$c2],@x[$c2],@x[$d2])",
	   "&add_32	(@x[$c3],@x[$c3],@x[$d3])",
	"&eor_32	(@x[$b0],@x[$b0],@x[$c0])",
	 "&eor_32	(@x[$b1],@x[$b1],@x[$c1])",
	  "&eor_32	(@x[$b2],@x[$b2],@x[$c2])",
	   "&eor_32	(@x[$b3],@x[$b3],@x[$c3])",
	"&ror_32	(@x[$b0],@x[$b0],25)",
	 "&ror_32	(@x[$b1],@x[$b1],25)",
	  "&ror_32	(@x[$b2],@x[$b2],25)",
	   "&ror_32	(@x[$b3],@x[$b3],25)"
    );
}

$code.=<<___;
#include "arm_arch.h"

.text

.extern	OPENSSL_armcap_P
.hidden	OPENSSL_armcap_P

.align	5
.Lsigma:
.quad	0x3320646e61707865,0x6b20657479622d32		// endian-neutral
.Lone:
.long	1,0,0,0
.LOPENSSL_armcap_P:
#ifdef	__ILP32__
.long	OPENSSL_armcap_P-.
#else
.quad	OPENSSL_armcap_P-.
#endif
.asciz	"ChaCha20 for ARMv8, CRYPTOGAMS by <appro\@openssl.org>"

.globl	ChaCha20_ctr32
.type	ChaCha20_ctr32,%function
.align	5
ChaCha20_ctr32:
	cbz	$len,.Labort
	adr	@x[0],.LOPENSSL_armcap_P
	cmp	$len,#192
	b.lo	.Lshort
#ifdef	__ILP32__
	ldrsw	@x[1],[@x[0]]
#else
	ldr	@x[1],[@x[0]]
#endif
	ldr	w17,[@x[1],@x[0]]
	tst	w17,#ARMV7_NEON
	b.ne	ChaCha20_neon

.Lshort:
	.inst	0xd503233f			// paciasp
	stp	x29,x30,[sp,#-96]!
	add	x29,sp,#0

	adr	@x[0],.Lsigma
	stp	x19,x20,[sp,#16]
	stp	x21,x22,[sp,#32]
	stp	x23,x24,[sp,#48]
	stp	x25,x26,[sp,#64]
	stp	x27,x28,[sp,#80]
	sub	sp,sp,#64

	ldp	@d[0],@d[1],[@x[0]]		// load sigma
	ldp	@d[2],@d[3],[$key]		// load key
	ldp	@d[4],@d[5],[$key,#16]
	ldp	@d[6],@d[7],[$ctr]		// load counter
#ifdef	__ARMEB__
	ror	@d[2],@d[2],#32
	ror	@d[3],@d[3],#32
	ror	@d[4],@d[4],#32
	ror	@d[5],@d[5],#32
	ror	@d[6],@d[6],#32
	ror	@d[7],@d[7],#32
#endif

.Loop_outer:
	mov.32	@x[0],@d[0]			// unpack key block
	lsr	@x[1],@d[0],#32
	mov.32	@x[2],@d[1]
	lsr	@x[3],@d[1],#32
	mov.32	@x[4],@d[2]
	lsr	@x[5],@d[2],#32
	mov.32	@x[6],@d[3]
	lsr	@x[7],@d[3],#32
	mov.32	@x[8],@d[4]
	lsr	@x[9],@d[4],#32
	mov.32	@x[10],@d[5]
	lsr	@x[11],@d[5],#32
	mov.32	@x[12],@d[6]
	lsr	@x[13],@d[6],#32
	mov.32	@x[14],@d[7]
	lsr	@x[15],@d[7],#32

	mov	$ctr,#10
	subs	$len,$len,#64
.Loop:
	sub	$ctr,$ctr,#1
___
	foreach (&ROUND(0, 4, 8,12)) { eval; }
	foreach (&ROUND(0, 5,10,15)) { eval; }
$code.=<<___;
	cbnz	$ctr,.Loop

	add.32	@x[0],@x[0],@d[0]		// accumulate key block
	add	@x[1],@x[1],@d[0],lsr#32
	add.32	@x[2],@x[2],@d[1]
	add	@x[3],@x[3],@d[1],lsr#32
	add.32	@x[4],@x[4],@d[2]
	add	@x[5],@x[5],@d[2],lsr#32
	add.32	@x[6],@x[6],@d[3]
	add	@x[7],@x[7],@d[3],lsr#32
	add.32	@x[8],@x[8],@d[4]
	add	@x[9],@x[9],@d[4],lsr#32
	add.32	@x[10],@x[10],@d[5]
	add	@x[11],@x[11],@d[5],lsr#32
	add.32	@x[12],@x[12],@d[6]
	add	@x[13],@x[13],@d[6],lsr#32
	add.32	@x[14],@x[14],@d[7]
	add	@x[15],@x[15],@d[7],lsr#32

	b.lo	.Ltail

	add	@x[0],@x[0],@x[1],lsl#32	// pack
	add	@x[2],@x[2],@x[3],lsl#32
	ldp	@x[1],@x[3],[$inp,#0]		// load input
	add	@x[4],@x[4],@x[5],lsl#32
	add	@x[6],@x[6],@x[7],lsl#32
	ldp	@x[5],@x[7],[$inp,#16]
	add	@x[8],@x[8],@x[9],lsl#32
	add	@x[10],@x[10],@x[11],lsl#32
	ldp	@x[9],@x[11],[$inp,#32]
	add	@x[12],@x[12],@x[13],lsl#32
	add	@x[14],@x[14],@x[15],lsl#32
	ldp	@x[13],@x[15],[$inp,#48]
	add	$inp,$inp,#64
#ifdef	__ARMEB__
	rev	@x[0],@x[0]
	rev	@x[2],@x[2]
	rev	@x[4],@x[4]
	rev	@x[6],@x[6]
	rev	@x[8],@x[8]
	rev	@x[10],@x[10]
	rev	@x[12],@x[12]
	rev	@x[14],@x[14]
#endif
	eor	@x[0],@x[0],@x[1]
	eor	@x[2],@x[2],@x[3]
	eor	@x[4],@x[4],@x[5]
	eor	@x[6],@x[6],@x[7]
	eor	@x[8],@x[8],@x[9]
	eor	@x[10],@x[10],@x[11]
	eor	@x[12],@x[12],@x[13]
	eor	@x[14],@x[14],@x[15]

	stp	@x[0],@x[2],[$out,#0]		// store output
	 add	@d[6],@d[6],#1			// increment counter
	stp	@x[4],@x[6],[$out,#16]
	stp	@x[8],@x[10],[$out,#32]
	stp	@x[12],@x[14],[$out,#48]
	add	$out,$out,#64

	b.hi	.Loop_outer

	ldp	x19,x20,[x29,#16]
	add	sp,sp,#64
	ldp	x21,x22,[x29,#32]
	ldp	x23,x24,[x29,#48]
	ldp	x25,x26,[x29,#64]
	ldp	x27,x28,[x29,#80]
	ldp	x29,x30,[sp],#96
	.inst	0xd50323bf			// autiasp
.Labort:
	ret

.align	4
.Ltail:
	add	$len,$len,#64
.Less_than_64:
	sub	$out,$out,#1
	add	$inp,$inp,$len
	add	$out,$out,$len
	add	$ctr,sp,$len
	neg	$len,$len

	add	@x[0],@x[0],@x[1],lsl#32	// pack
	add	@x[2],@x[2],@x[3],lsl#32
	add	@x[4],@x[4],@x[5],lsl#32
	add	@x[6],@x[6],@x[7],lsl#32
	add	@x[8],@x[8],@x[9],lsl#32
	add	@x[10],@x[10],@x[11],lsl#32
	add	@x[12],@x[12],@x[13],lsl#32
	add	@x[14],@x[14],@x[15],lsl#32
#ifdef	__ARMEB__
	rev	@x[0],@x[0]
	rev	@x[2],@x[2]
	rev	@x[4],@x[4]
	rev	@x[6],@x[6]
	rev	@x[8],@x[8]
	rev	@x[10],@x[10]
	rev	@x[12],@x[12]
	rev	@x[14],@x[14]
#endif
	stp	@x[0],@x[2],[sp,#0]
	stp	@x[4],@x[6],[sp,#16]
	stp	@x[8],@x[10],[sp,#32]
	stp	@x[12],@x[14],[sp,#48]

.Loop_tail:
	ldrb	w10,[$inp,$len]
	ldrb	w11,[$ctr,$len]
	add	$len,$len,#1
	eor	w10,w10,w11
	strb	w10,[$out,$len]
	cbnz	$len,.Loop_tail

	stp	xzr,xzr,[sp,#0]
	stp	xzr,xzr,[sp,#16]
	stp	xzr,xzr,[sp,#32]
	stp	xzr,xzr,[sp,#48]

	ldp	x19,x20,[x29,#16]
	add	sp,sp,#64
	ldp	x21,x22,[x29,#32]
	ldp	x23,x24,[x29,#48]
	ldp	x25,x26,[x29,#64]
	ldp	x27,x28,[x29,#80]
	ldp	x29,x30,[sp],#96
	.inst	0xd50323bf			// autiasp
	ret
.size	ChaCha20_ctr32,.-ChaCha20_ctr32
___

{{{
my ($A0,$B0,$C0,$D0,$A1,$B1,$C1,$D1,$A2,$B2,$C2,$D2,$T0,$T1,$T2,$T3) =
    map("v$_.4s",(0..7,16..23));
my (@K)=map("v$_.4s",(24..30));
my $ONE="v31.4s";

sub NEONROUND {
my $odd = pop;
my ($a,$b,$c,$d,$t)=@_;

	(
	"&add		('$a','$a','$b')",
	"&eor		('$d','$d','$a')",
	"&rev32_16	('$d','$d')",		# vrot ($d,16)

	"&add		('$c','$c','$d')",
	"&eor		('$t','$b','$c')",
	"&ushr		('$b','$t',20)",
	"&sli		('$b','$t',12)",

	"&add		('$a','$a','$b')",
	"&eor		('$t','$d','$a')",
	"&ushr		('$d','$t',24)",
	"&sli		('$d','$t',8)",

	"&add		('$c','$c','$d')",
	"&eor		('$t','$b','$c')",
	"&ushr		('$b','$t',25)",
	"&sli		('$b','$t',7)",

	"&ext		('$c','$c','$c',8)",
	"&ext		('$d','$d','$d',$odd?4:12)",
	"&ext		('$b','$b','$b',$odd?12:4)"
	);
}

$code.=<<___;

.type	ChaCha20_neon,%function
.align	5
ChaCha20_neon:
	.inst	0xd503233f			// paciasp
	stp	x29,x30,[sp,#-96]!
	add	x29,sp,#0

	adr	@x[0],.Lsigma
	stp	x19,x20,[sp,#16]
	stp	x21,x22,[sp,#32]
	stp	x23,x24,[sp,#48]
	stp	x25,x26,[sp,#64]
	stp	x27,x28,[sp,#80]
	cmp	$len,#512
	b.hs	.L512_or_more_neon

	sub	sp,sp,#64

	ldp	@d[0],@d[1],[@x[0]]		// load sigma
	ld1	{@K[0]},[@x[0]],#16
	ldp	@d[2],@d[3],[$key]		// load key
	ldp	@d[4],@d[5],[$key,#16]
	ld1	{@K[1],@K[2]},[$key]
	ldp	@d[6],@d[7],[$ctr]		// load counter
	ld1	{@K[3]},[$ctr]
	ld1	{$ONE},[@x[0]]
#ifdef	__ARMEB__
	rev64	@K[0],@K[0]
	ror	@d[2],@d[2],#32
	ror	@d[3],@d[3],#32
	ror	@d[4],@d[4],#32
	ror	@d[5],@d[5],#32
	ror	@d[6],@d[6],#32
	ror	@d[7],@d[7],#32
#endif
	add	@K[3],@K[3],$ONE		// += 1
	add	@K[4],@K[3],$ONE
	add	@K[5],@K[4],$ONE
	shl	$ONE,$ONE,#2			// 1 -> 4

.Loop_outer_neon:
	mov.32	@x[0],@d[0]			// unpack key block
	lsr	@x[1],@d[0],#32
	 mov	$A0,@K[0]
	mov.32	@x[2],@d[1]
	lsr	@x[3],@d[1],#32
	 mov	$A1,@K[0]
	mov.32	@x[4],@d[2]
	lsr	@x[5],@d[2],#32
	 mov	$A2,@K[0]
	mov.32	@x[6],@d[3]
	 mov	$B0,@K[1]
	lsr	@x[7],@d[3],#32
	 mov	$B1,@K[1]
	mov.32	@x[8],@d[4]
	 mov	$B2,@K[1]
	lsr	@x[9],@d[4],#32
	 mov	$D0,@K[3]
	mov.32	@x[10],@d[5]
	 mov	$D1,@K[4]
	lsr	@x[11],@d[5],#32
	 mov	$D2,@K[5]
	mov.32	@x[12],@d[6]
	 mov	$C0,@K[2]
	lsr	@x[13],@d[6],#32
	 mov	$C1,@K[2]
	mov.32	@x[14],@d[7]
	 mov	$C2,@K[2]
	lsr	@x[15],@d[7],#32

	mov	$ctr,#10
	subs	$len,$len,#256
.Loop_neon:
	sub	$ctr,$ctr,#1
___
	my @thread0=&NEONROUND($A0,$B0,$C0,$D0,$T0,0);
	my @thread1=&NEONROUND($A1,$B1,$C1,$D1,$T1,0);
	my @thread2=&NEONROUND($A2,$B2,$C2,$D2,$T2,0);
	my @thread3=&ROUND(0,4,8,12);

	foreach (@thread0) {
		eval;			eval(shift(@thread3));
		eval(shift(@thread1));	eval(shift(@thread3));
		eval(shift(@thread2));	eval(shift(@thread3));
	}

	@thread0=&NEONROUND($A0,$B0,$C0,$D0,$T0,1);
	@thread1=&NEONROUND($A1,$B1,$C1,$D1,$T1,1);
	@thread2=&NEONROUND($A2,$B2,$C2,$D2,$T2,1);
	@thread3=&ROUND(0,5,10,15);

	foreach (@thread0) {
		eval;			eval(shift(@thread3));
		eval(shift(@thread1));	eval(shift(@thread3));
		eval(shift(@thread2));	eval(shift(@thread3));
	}
$code.=<<___;
	cbnz	$ctr,.Loop_neon

	add.32	@x[0],@x[0],@d[0]		// accumulate key block
	 add	$A0,$A0,@K[0]
	add	@x[1],@x[1],@d[0],lsr#32
	 add	$A1,$A1,@K[0]
	add.32	@x[2],@x[2],@d[1]
	 add	$A2,$A2,@K[0]
	add	@x[3],@x[3],@d[1],lsr#32
	 add	$C0,$C0,@K[2]
	add.32	@x[4],@x[4],@d[2]
	 add	$C1,$C1,@K[2]
	add	@x[5],@x[5],@d[2],lsr#32
	 add	$C2,$C2,@K[2]
	add.32	@x[6],@x[6],@d[3]
	 add	$D0,$D0,@K[3]
	add	@x[7],@x[7],@d[3],lsr#32
	add.32	@x[8],@x[8],@d[4]
	 add	$D1,$D1,@K[4]
	add	@x[9],@x[9],@d[4],lsr#32
	add.32	@x[10],@x[10],@d[5]
	 add	$D2,$D2,@K[5]
	add	@x[11],@x[11],@d[5],lsr#32
	add.32	@x[12],@x[12],@d[6]
	 add	$B0,$B0,@K[1]
	add	@x[13],@x[13],@d[6],lsr#32
	add.32	@x[14],@x[14],@d[7]
	 add	$B1,$B1,@K[1]
	add	@x[15],@x[15],@d[7],lsr#32
	 add	$B2,$B2,@K[1]

	b.lo	.Ltail_neon

	add	@x[0],@x[0],@x[1],lsl#32	// pack
	add	@x[2],@x[2],@x[3],lsl#32
	ldp	@x[1],@x[3],[$inp,#0]		// load input
	add	@x[4],@x[4],@x[5],lsl#32
	add	@x[6],@x[6],@x[7],lsl#32
	ldp	@x[5],@x[7],[$inp,#16]
	add	@x[8],@x[8],@x[9],lsl#32
	add	@x[10],@x[10],@x[11],lsl#32
	ldp	@x[9],@x[11],[$inp,#32]
	add	@x[12],@x[12],@x[13],lsl#32
	add	@x[14],@x[14],@x[15],lsl#32
	ldp	@x[13],@x[15],[$inp,#48]
	add	$inp,$inp,#64
#ifdef	__ARMEB__
	rev	@x[0],@x[0]
	rev	@x[2],@x[2]
	rev	@x[4],@x[4]
	rev	@x[6],@x[6]
	rev	@x[8],@x[8]
	rev	@x[10],@x[10]
	rev	@x[12],@x[12]
	rev	@x[14],@x[14]
#endif
	ld1.8	{$T0-$T3},[$inp],#64
	eor	@x[0],@x[0],@x[1]
	eor	@x[2],@x[2],@x[3]
	eor	@x[4],@x[4],@x[5]
	eor	@x[6],@x[6],@x[7]
	eor	@x[8],@x[8],@x[9]
	 eor	$A0,$A0,$T0
	eor	@x[10],@x[10],@x[11]
	 eor	$B0,$B0,$T1
	eor	@x[12],@x[12],@x[13]
	 eor	$C0,$C0,$T2
	eor	@x[14],@x[14],@x[15]
	 eor	$D0,$D0,$T3
	 ld1.8	{$T0-$T3},[$inp],#64

	stp	@x[0],@x[2],[$out,#0]		// store output
	 add	@d[6],@d[6],#4			// increment counter
	stp	@x[4],@x[6],[$out,#16]
	 add	@K[3],@K[3],$ONE		// += 4
	stp	@x[8],@x[10],[$out,#32]
	 add	@K[4],@K[4],$ONE
	stp	@x[12],@x[14],[$out,#48]
	 add	@K[5],@K[5],$ONE
	add	$out,$out,#64

	st1.8	{$A0-$D0},[$out],#64
	ld1.8	{$A0-$D0},[$inp],#64

	eor	$A1,$A1,$T0
	eor	$B1,$B1,$T1
	eor	$C1,$C1,$T2
	eor	$D1,$D1,$T3
	st1.8	{$A1-$D1},[$out],#64

	eor	$A2,$A2,$A0
	eor	$B2,$B2,$B0
	eor	$C2,$C2,$C0
	eor	$D2,$D2,$D0
	st1.8	{$A2-$D2},[$out],#64

	b.hi	.Loop_outer_neon

	ldp	x19,x20,[x29,#16]
	add	sp,sp,#64
	ldp	x21,x22,[x29,#32]
	ldp	x23,x24,[x29,#48]
	ldp	x25,x26,[x29,#64]
	ldp	x27,x28,[x29,#80]
	ldp	x29,x30,[sp],#96
	.inst	0xd50323bf			// autiasp
	ret

.Ltail_neon:
	add	$len,$len,#256
	cmp	$len,#64
	b.lo	.Less_than_64

	add	@x[0],@x[0],@x[1],lsl#32	// pack
	add	@x[2],@x[2],@x[3],lsl#32
	ldp	@x[1],@x[3],[$inp,#0]		// load input
	add	@x[4],@x[4],@x[5],lsl#32
	add	@x[6],@x[6],@x[7],lsl#32
	ldp	@x[5],@x[7],[$inp,#16]
	add	@x[8],@x[8],@x[9],lsl#32
	add	@x[10],@x[10],@x[11],lsl#32
	ldp	@x[9],@x[11],[$inp,#32]
	add	@x[12],@x[12],@x[13],lsl#32
	add	@x[14],@x[14],@x[15],lsl#32
	ldp	@x[13],@x[15],[$inp,#48]
	add	$inp,$inp,#64
#ifdef	__ARMEB__
	rev	@x[0],@x[0]
	rev	@x[2],@x[2]
	rev	@x[4],@x[4]
	rev	@x[6],@x[6]
	rev	@x[8],@x[8]
	rev	@x[10],@x[10]
	rev	@x[12],@x[12]
	rev	@x[14],@x[14]
#endif
	eor	@x[0],@x[0],@x[1]
	eor	@x[2],@x[2],@x[3]
	eor	@x[4],@x[4],@x[5]
	eor	@x[6],@x[6],@x[7]
	eor	@x[8],@x[8],@x[9]
	eor	@x[10],@x[10],@x[11]
	eor	@x[12],@x[12],@x[13]
	eor	@x[14],@x[14],@x[15]

	stp	@x[0],@x[2],[$out,#0]		// store output
	 add	@d[6],@d[6],#4			// increment counter
	stp	@x[4],@x[6],[$out,#16]
	stp	@x[8],@x[10],[$out,#32]
	stp	@x[12],@x[14],[$out,#48]
	add	$out,$out,#64
	b.eq	.Ldone_neon
	sub	$len,$len,#64
	cmp	$len,#64
	b.lo	.Less_than_128

	ld1.8	{$T0-$T3},[$inp],#64
	eor	$A0,$A0,$T0
	eor	$B0,$B0,$T1
	eor	$C0,$C0,$T2
	eor	$D0,$D0,$T3
	st1.8	{$A0-$D0},[$out],#64
	b.eq	.Ldone_neon
	sub	$len,$len,#64
	cmp	$len,#64
	b.lo	.Less_than_192

	ld1.8	{$T0-$T3},[$inp],#64
	eor	$A1,$A1,$T0
	eor	$B1,$B1,$T1
	eor	$C1,$C1,$T2
	eor	$D1,$D1,$T3
	st1.8	{$A1-$D1},[$out],#64
	b.eq	.Ldone_neon
	sub	$len,$len,#64

	st1.8	{$A2-$D2},[sp]
	b	.Last_neon

.Less_than_128:
	st1.8	{$A0-$D0},[sp]
	b	.Last_neon
.Less_than_192:
	st1.8	{$A1-$D1},[sp]
	b	.Last_neon

.align	4
.Last_neon:
	sub	$out,$out,#1
	add	$inp,$inp,$len
	add	$out,$out,$len
	add	$ctr,sp,$len
	neg	$len,$len

.Loop_tail_neon:
	ldrb	w10,[$inp,$len]
	ldrb	w11,[$ctr,$len]
	add	$len,$len,#1
	eor	w10,w10,w11
	strb	w10,[$out,$len]
	cbnz	$len,.Loop_tail_neon

	stp	xzr,xzr,[sp,#0]
	stp	xzr,xzr,[sp,#16]
	stp	xzr,xzr,[sp,#32]
	stp	xzr,xzr,[sp,#48]

.Ldone_neon:
	ldp	x19,x20,[x29,#16]
	add	sp,sp,#64
	ldp	x21,x22,[x29,#32]
	ldp	x23,x24,[x29,#48]
	ldp	x25,x26,[x29,#64]
	ldp	x27,x28,[x29,#80]
	ldp	x29,x30,[sp],#96
	.inst	0xd50323bf			// autiasp
	ret
.size	ChaCha20_neon,.-ChaCha20_neon
___
{
my ($T0,$T1,$T2,$T3,$T4,$T5)=@K;
my ($A0,$B0,$C0,$D0,$A1,$B1,$C1,$D1,$A2,$B2,$C2,$D2,
    $A3,$B3,$C3,$D3,$A4,$B4,$C4,$D4,$A5,$B5,$C5,$D5) = map("v$_.4s",(0..23));

$code.=<<___;
.type	ChaCha20_512_neon,%function
.align	5
ChaCha20_512_neon:
	.inst	0xd503233f			// paciasp
	stp	x29,x30,[sp,#-96]!
	add	x29,sp,#0

	adr	@x[0],.Lsigma
	stp	x19,x20,[sp,#16]
	stp	x21,x22,[sp,#32]
	stp	x23,x24,[sp,#48]
	stp	x25,x26,[sp,#64]
	stp	x27,x28,[sp,#80]

.L512_or_more_neon:
	sub	sp,sp,#128+64

	ldp	@d[0],@d[1],[@x[0]]		// load sigma
	ld1	{@K[0]},[@x[0]],#16
	ldp	@d[2],@d[3],[$key]		// load key
	ldp	@d[4],@d[5],[$key,#16]
	ld1	{@K[1],@K[2]},[$key]
	ldp	@d[6],@d[7],[$ctr]		// load counter
	ld1	{@K[3]},[$ctr]
	ld1	{$ONE},[@x[0]]
#ifdef	__ARMEB__
	rev64	@K[0],@K[0]
	ror	@d[2],@d[2],#32
	ror	@d[3],@d[3],#32
	ror	@d[4],@d[4],#32
	ror	@d[5],@d[5],#32
	ror	@d[6],@d[6],#32
	ror	@d[7],@d[7],#32
#endif
	add	@K[3],@K[3],$ONE		// += 1
	stp	@K[0],@K[1],[sp,#0]		// off-load key block, invariant part
	add	@K[3],@K[3],$ONE		// not typo
	str	@K[2],[sp,#32]
	add	@K[4],@K[3],$ONE
	add	@K[5],@K[4],$ONE
	add	@K[6],@K[5],$ONE
	shl	$ONE,$ONE,#2			// 1 -> 4

	stp	d8,d9,[sp,#128+0]		// meet ABI requirements
	stp	d10,d11,[sp,#128+16]
	stp	d12,d13,[sp,#128+32]
	stp	d14,d15,[sp,#128+48]

	sub	$len,$len,#512			// not typo

.Loop_outer_512_neon:
	 mov	$A0,@K[0]
	 mov	$A1,@K[0]
	 mov	$A2,@K[0]
	 mov	$A3,@K[0]
	 mov	$A4,@K[0]
	 mov	$A5,@K[0]
	 mov	$B0,@K[1]
	mov.32	@x[0],@d[0]			// unpack key block
	 mov	$B1,@K[1]
	lsr	@x[1],@d[0],#32
	 mov	$B2,@K[1]
	mov.32	@x[2],@d[1]
	 mov	$B3,@K[1]
	lsr	@x[3],@d[1],#32
	 mov	$B4,@K[1]
	mov.32	@x[4],@d[2]
	 mov	$B5,@K[1]
	lsr	@x[5],@d[2],#32
	 mov	$D0,@K[3]
	mov.32	@x[6],@d[3]
	 mov	$D1,@K[4]
	lsr	@x[7],@d[3],#32
	 mov	$D2,@K[5]
	mov.32	@x[8],@d[4]
	 mov	$D3,@K[6]
	lsr	@x[9],@d[4],#32
	 mov	$C0,@K[2]
	mov.32	@x[10],@d[5]
	 mov	$C1,@K[2]
	lsr	@x[11],@d[5],#32
	 add	$D4,$D0,$ONE			// +4
	mov.32	@x[12],@d[6]
	 add	$D5,$D1,$ONE			// +4
	lsr	@x[13],@d[6],#32
	 mov	$C2,@K[2]
	mov.32	@x[14],@d[7]
	 mov	$C3,@K[2]
	lsr	@x[15],@d[7],#32
	 mov	$C4,@K[2]
	 stp	@K[3],@K[4],[sp,#48]		// off-load key block, variable part
	 mov	$C5,@K[2]
	 str	@K[5],[sp,#80]

	mov	$ctr,#5
	subs	$len,$len,#512
.Loop_upper_neon:
	sub	$ctr,$ctr,#1
___
	my @thread0=&NEONROUND($A0,$B0,$C0,$D0,$T0,0);
	my @thread1=&NEONROUND($A1,$B1,$C1,$D1,$T1,0);
	my @thread2=&NEONROUND($A2,$B2,$C2,$D2,$T2,0);
	my @thread3=&NEONROUND($A3,$B3,$C3,$D3,$T3,0);
	my @thread4=&NEONROUND($A4,$B4,$C4,$D4,$T4,0);
	my @thread5=&NEONROUND($A5,$B5,$C5,$D5,$T5,0);
	my @thread67=(&ROUND(0,4,8,12),&ROUND(0,5,10,15));
	my $diff = ($#thread0+1)*6 - $#thread67 - 1;
	my $i = 0;

	foreach (@thread0) {
		eval;			eval(shift(@thread67));
		eval(shift(@thread1));	eval(shift(@thread67));
		eval(shift(@thread2));	eval(shift(@thread67));
		eval(shift(@thread3));	eval(shift(@thread67));
		eval(shift(@thread4));	eval(shift(@thread67));
		eval(shift(@thread5));	eval(shift(@thread67));
	}

	@thread0=&NEONROUND($A0,$B0,$C0,$D0,$T0,1);
	@thread1=&NEONROUND($A1,$B1,$C1,$D1,$T1,1);
	@thread2=&NEONROUND($A2,$B2,$C2,$D2,$T2,1);
	@thread3=&NEONROUND($A3,$B3,$C3,$D3,$T3,1);
	@thread4=&NEONROUND($A4,$B4,$C4,$D4,$T4,1);
	@thread5=&NEONROUND($A5,$B5,$C5,$D5,$T5,1);
	@thread67=(&ROUND(0,4,8,12),&ROUND(0,5,10,15));

	foreach (@thread0) {
		eval;			eval(shift(@thread67));
		eval(shift(@thread1));	eval(shift(@thread67));
		eval(shift(@thread2));	eval(shift(@thread67));
		eval(shift(@thread3));	eval(shift(@thread67));
		eval(shift(@thread4));	eval(shift(@thread67));
		eval(shift(@thread5));	eval(shift(@thread67));
	}
$code.=<<___;
	cbnz	$ctr,.Loop_upper_neon

	add.32	@x[0],@x[0],@d[0]		// accumulate key block
	add	@x[1],@x[1],@d[0],lsr#32
	add.32	@x[2],@x[2],@d[1]
	add	@x[3],@x[3],@d[1],lsr#32
	add.32	@x[4],@x[4],@d[2]
	add	@x[5],@x[5],@d[2],lsr#32
	add.32	@x[6],@x[6],@d[3]
	add	@x[7],@x[7],@d[3],lsr#32
	add.32	@x[8],@x[8],@d[4]
	add	@x[9],@x[9],@d[4],lsr#32
	add.32	@x[10],@x[10],@d[5]
	add	@x[11],@x[11],@d[5],lsr#32
	add.32	@x[12],@x[12],@d[6]
	add	@x[13],@x[13],@d[6],lsr#32
	add.32	@x[14],@x[14],@d[7]
	add	@x[15],@x[15],@d[7],lsr#32

	add	@x[0],@x[0],@x[1],lsl#32	// pack
	add	@x[2],@x[2],@x[3],lsl#32
	ldp	@x[1],@x[3],[$inp,#0]		// load input
	add	@x[4],@x[4],@x[5],lsl#32
	add	@x[6],@x[6],@x[7],lsl#32
	ldp	@x[5],@x[7],[$inp,#16]
	add	@x[8],@x[8],@x[9],lsl#32
	add	@x[10],@x[10],@x[11],lsl#32
	ldp	@x[9],@x[11],[$inp,#32]
	add	@x[12],@x[12],@x[13],lsl#32
	add	@x[14],@x[14],@x[15],lsl#32
	ldp	@x[13],@x[15],[$inp,#48]
	add	$inp,$inp,#64
#ifdef	__ARMEB__
	rev	@x[0],@x[0]
	rev	@x[2],@x[2]
	rev	@x[4],@x[4]
	rev	@x[6],@x[6]
	rev	@x[8],@x[8]
	rev	@x[10],@x[10]
	rev	@x[12],@x[12]
	rev	@x[14],@x[14]
#endif
	eor	@x[0],@x[0],@x[1]
	eor	@x[2],@x[2],@x[3]
	eor	@x[4],@x[4],@x[5]
	eor	@x[6],@x[6],@x[7]
	eor	@x[8],@x[8],@x[9]
	eor	@x[10],@x[10],@x[11]
	eor	@x[12],@x[12],@x[13]
	eor	@x[14],@x[14],@x[15]

	 stp	@x[0],@x[2],[$out,#0]		// store output
	 add	@d[6],@d[6],#1			// increment counter
	mov.32	@x[0],@d[0]			// unpack key block
	lsr	@x[1],@d[0],#32
	 stp	@x[4],@x[6],[$out,#16]
	mov.32	@x[2],@d[1]
	lsr	@x[3],@d[1],#32
	 stp	@x[8],@x[10],[$out,#32]
	mov.32	@x[4],@d[2]
	lsr	@x[5],@d[2],#32
	 stp	@x[12],@x[14],[$out,#48]
	 add	$out,$out,#64
	mov.32	@x[6],@d[3]
	lsr	@x[7],@d[3],#32
	mov.32	@x[8],@d[4]
	lsr	@x[9],@d[4],#32
	mov.32	@x[10],@d[5]
	lsr	@x[11],@d[5],#32
	mov.32	@x[12],@d[6]
	lsr	@x[13],@d[6],#32
	mov.32	@x[14],@d[7]
	lsr	@x[15],@d[7],#32

	mov	$ctr,#5
.Loop_lower_neon:
	sub	$ctr,$ctr,#1
___
	@thread0=&NEONROUND($A0,$B0,$C0,$D0,$T0,0);
	@thread1=&NEONROUND($A1,$B1,$C1,$D1,$T1,0);
	@thread2=&NEONROUND($A2,$B2,$C2,$D2,$T2,0);
	@thread3=&NEONROUND($A3,$B3,$C3,$D3,$T3,0);
	@thread4=&NEONROUND($A4,$B4,$C4,$D4,$T4,0);
	@thread5=&NEONROUND($A5,$B5,$C5,$D5,$T5,0);
	@thread67=(&ROUND(0,4,8,12),&ROUND(0,5,10,15));

	foreach (@thread0) {
		eval;			eval(shift(@thread67));
		eval(shift(@thread1));	eval(shift(@thread67));
		eval(shift(@thread2));	eval(shift(@thread67));
		eval(shift(@thread3));	eval(shift(@thread67));
		eval(shift(@thread4));	eval(shift(@thread67));
		eval(shift(@thread5));	eval(shift(@thread67));
	}

	@thread0=&NEONROUND($A0,$B0,$C0,$D0,$T0,1);
	@thread1=&NEONROUND($A1,$B1,$C1,$D1,$T1,1);
	@thread2=&NEONROUND($A2,$B2,$C2,$D2,$T2,1);
	@thread3=&NEONROUND($A3,$B3,$C3,$D3,$T3,1);
	@thread4=&NEONROUND($A4,$B4,$C4,$D4,$T4,1);
	@thread5=&NEONROUND($A5,$B5,$C5,$D5,$T5,1);
	@thread67=(&ROUND(0,4,8,12),&ROUND(0,5,10,15));

	foreach (@thread0) {
		eval;			eval(shift(@thread67));
		eval(shift(@thread1));	eval(shift(@thread67));
		eval(shift(@thread2));	eval(shift(@thread67));
		eval(shift(@thread3));	eval(shift(@thread67));
		eval(shift(@thread4));	eval(shift(@thread67));
		eval(shift(@thread5));	eval(shift(@thread67));
	}
$code.=<<___;
	cbnz	$ctr,.Loop_lower_neon

	add.32	@x[0],@x[0],@d[0]		// accumulate key block
	 ldp	@K[0],@K[1],[sp,#0]
	add	@x[1],@x[1],@d[0],lsr#32
	 ldp	@K[2],@K[3],[sp,#32]
	add.32	@x[2],@x[2],@d[1]
	 ldp	@K[4],@K[5],[sp,#64]
	add	@x[3],@x[3],@d[1],lsr#32
	 add	$A0,$A0,@K[0]
	add.32	@x[4],@x[4],@d[2]
	 add	$A1,$A1,@K[0]
	add	@x[5],@x[5],@d[2],lsr#32
	 add	$A2,$A2,@K[0]
	add.32	@x[6],@x[6],@d[3]
	 add	$A3,$A3,@K[0]
	add	@x[7],@x[7],@d[3],lsr#32
	 add	$A4,$A4,@K[0]
	add.32	@x[8],@x[8],@d[4]
	 add	$A5,$A5,@K[0]
	add	@x[9],@x[9],@d[4],lsr#32
	 add	$C0,$C0,@K[2]
	add.32	@x[10],@x[10],@d[5]
	 add	$C1,$C1,@K[2]
	add	@x[11],@x[11],@d[5],lsr#32
	 add	$C2,$C2,@K[2]
	add.32	@x[12],@x[12],@d[6]
	 add	$C3,$C3,@K[2]
	add	@x[13],@x[13],@d[6],lsr#32
	 add	$C4,$C4,@K[2]
	add.32	@x[14],@x[14],@d[7]
	 add	$C5,$C5,@K[2]
	add	@x[15],@x[15],@d[7],lsr#32
	 add	$D4,$D4,$ONE			// +4
	add	@x[0],@x[0],@x[1],lsl#32	// pack
	 add	$D5,$D5,$ONE			// +4
	add	@x[2],@x[2],@x[3],lsl#32
	 add	$D0,$D0,@K[3]
	ldp	@x[1],@x[3],[$inp,#0]		// load input
	 add	$D1,$D1,@K[4]
	add	@x[4],@x[4],@x[5],lsl#32
	 add	$D2,$D2,@K[5]
	add	@x[6],@x[6],@x[7],lsl#32
	 add	$D3,$D3,@K[6]
	ldp	@x[5],@x[7],[$inp,#16]
	 add	$D4,$D4,@K[3]
	add	@x[8],@x[8],@x[9],lsl#32
	 add	$D5,$D5,@K[4]
	add	@x[10],@x[10],@x[11],lsl#32
	 add	$B0,$B0,@K[1]
	ldp	@x[9],@x[11],[$inp,#32]
	 add	$B1,$B1,@K[1]
	add	@x[12],@x[12],@x[13],lsl#32
	 add	$B2,$B2,@K[1]
	add	@x[14],@x[14],@x[15],lsl#32
	 add	$B3,$B3,@K[1]
	ldp	@x[13],@x[15],[$inp,#48]
	 add	$B4,$B4,@K[1]
	add	$inp,$inp,#64
	 add	$B5,$B5,@K[1]

#ifdef	__ARMEB__
	rev	@x[0],@x[0]
	rev	@x[2],@x[2]
	rev	@x[4],@x[4]
	rev	@x[6],@x[6]
	rev	@x[8],@x[8]
	rev	@x[10],@x[10]
	rev	@x[12],@x[12]
	rev	@x[14],@x[14]
#endif
	ld1.8	{$T0-$T3},[$inp],#64
	eor	@x[0],@x[0],@x[1]
	eor	@x[2],@x[2],@x[3]
	eor	@x[4],@x[4],@x[5]
	eor	@x[6],@x[6],@x[7]
	eor	@x[8],@x[8],@x[9]
	 eor	$A0,$A0,$T0
	eor	@x[10],@x[10],@x[11]
	 eor	$B0,$B0,$T1
	eor	@x[12],@x[12],@x[13]
	 eor	$C0,$C0,$T2
	eor	@x[14],@x[14],@x[15]
	 eor	$D0,$D0,$T3
	 ld1.8	{$T0-$T3},[$inp],#64

	stp	@x[0],@x[2],[$out,#0]		// store output
	 add	@d[6],@d[6],#7			// increment counter
	stp	@x[4],@x[6],[$out,#16]
	stp	@x[8],@x[10],[$out,#32]
	stp	@x[12],@x[14],[$out,#48]
	add	$out,$out,#64
	st1.8	{$A0-$D0},[$out],#64

	ld1.8	{$A0-$D0},[$inp],#64
	eor	$A1,$A1,$T0
	eor	$B1,$B1,$T1
	eor	$C1,$C1,$T2
	eor	$D1,$D1,$T3
	st1.8	{$A1-$D1},[$out],#64

	ld1.8	{$A1-$D1},[$inp],#64
	eor	$A2,$A2,$A0
	 ldp	@K[0],@K[1],[sp,#0]
	eor	$B2,$B2,$B0
	 ldp	@K[2],@K[3],[sp,#32]
	eor	$C2,$C2,$C0
	eor	$D2,$D2,$D0
	st1.8	{$A2-$D2},[$out],#64

	ld1.8	{$A2-$D2},[$inp],#64
	eor	$A3,$A3,$A1
	eor	$B3,$B3,$B1
	eor	$C3,$C3,$C1
	eor	$D3,$D3,$D1
	st1.8	{$A3-$D3},[$out],#64

	ld1.8	{$A3-$D3},[$inp],#64
	eor	$A4,$A4,$A2
	eor	$B4,$B4,$B2
	eor	$C4,$C4,$C2
	eor	$D4,$D4,$D2
	st1.8	{$A4-$D4},[$out],#64

	shl	$A0,$ONE,#1			// 4 -> 8
	eor	$A5,$A5,$A3
	eor	$B5,$B5,$B3
	eor	$C5,$C5,$C3
	eor	$D5,$D5,$D3
	st1.8	{$A5-$D5},[$out],#64

	add	@K[3],@K[3],$A0			// += 8
	add	@K[4],@K[4],$A0
	add	@K[5],@K[5],$A0
	add	@K[6],@K[6],$A0

	b.hs	.Loop_outer_512_neon

	adds	$len,$len,#512
	ushr	$A0,$ONE,#2			// 4 -> 1

	ldp	d8,d9,[sp,#128+0]		// meet ABI requirements
	ldp	d10,d11,[sp,#128+16]
	ldp	d12,d13,[sp,#128+32]
	ldp	d14,d15,[sp,#128+48]

	stp	@K[0],$ONE,[sp,#0]		// wipe off-load area
	stp	@K[0],$ONE,[sp,#32]
	stp	@K[0],$ONE,[sp,#64]

	b.eq	.Ldone_512_neon

	cmp	$len,#192
	sub	@K[3],@K[3],$A0			// -= 1
	sub	@K[4],@K[4],$A0
	sub	@K[5],@K[5],$A0
	add	sp,sp,#128
	b.hs	.Loop_outer_neon

	eor	@K[1],@K[1],@K[1]
	eor	@K[2],@K[2],@K[2]
	eor	@K[3],@K[3],@K[3]
	eor	@K[4],@K[4],@K[4]
	eor	@K[5],@K[5],@K[5]
	eor	@K[6],@K[6],@K[6]
	b	.Loop_outer

.Ldone_512_neon:
	ldp	x19,x20,[x29,#16]
	add	sp,sp,#128+64
	ldp	x21,x22,[x29,#32]
	ldp	x23,x24,[x29,#48]
	ldp	x25,x26,[x29,#64]
	ldp	x27,x28,[x29,#80]
	ldp	x29,x30,[sp],#96
	.inst	0xd50323bf			// autiasp
	ret
.size	ChaCha20_512_neon,.-ChaCha20_512_neon
___
}
}}}

foreach (split("\n",$code)) {
	s/\`([^\`]*)\`/eval $1/geo;

	(s/\b([a-z]+)\.32\b/$1/ and (s/x([0-9]+)/w$1/g or 1))	or
	(m/\b(eor|ext|mov)\b/ and (s/\.4s/\.16b/g or 1))	or
	(s/\b((?:ld|st)1)\.8\b/$1/ and (s/\.4s/\.16b/g or 1))	or
	(m/\b(ld|st)[rp]\b/ and (s/v([0-9]+)\.4s/q$1/g or 1))	or
	(s/\brev32\.16\b/rev32/ and (s/\.4s/\.8h/g or 1));

	#s/\bq([0-9]+)#(lo|hi)/sprintf "d%d",2*$1+($2 eq "hi")/geo;

	print $_,"\n";
}
close STDOUT or die "error closing STDOUT: $!";	# flush


#! /usr/bin/env perl
# Copyright 2016-2020 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the OpenSSL license (the "License").  You may not use
# this file except in compliance with the License.  You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html

#
# ====================================================================
# Written by Andy Polyakov <appro@openssl.org> for the OpenSSL
# project. The module is, however, dual licensed under OpenSSL and
# CRYPTOGAMS licenses depending on where you obtain it. For further
# details see http://www.openssl.org/~appro/cryptogams/.
# ====================================================================
#
# ChaCha20 for C64x+.
#
# October 2015
#
# Performance is 3.54 cycles per processed byte, which is ~4.3 times
# faster than code generated by TI compiler. Compiler also disables
# interrupts for some reason, thus making interrupt response time
# dependent on input length. This module on the other hand is free
# from such limitation.

$output=pop;
open STDOUT,">$output";

($OUT,$INP,$LEN,$KEYB,$COUNTERA)=("A4","B4","A6","B6","A8");
($KEYA,$COUNTERB,$STEP)=("A7","B7","A3");

@X=  ("A16","B16","A17","B17","A18","B18","A19","B19",
      "A20","B20","A21","B21","A22","B22","A23","B23");
@Y=  ("A24","B24","A25","B25","A26","B26","A27","B27",
      "A28","B28","A29","B29","A30","B30","A31","B31");
@DAT=("A6", "A7", "B6", "B7", "A8", "A9", "B8", "B9",
      "A10","A11","B10","B11","A12","A13","B12","B13");

# yes, overlaps with @DAT, used only in 2x interleave code path...
@K2x=("A6", "B6", "A7", "B7", "A8", "B8", "A9", "B9",
      "A10","B10","A11","B11","A2", "B2", "A13","B13");

$code.=<<___;
	.text

	.if	.ASSEMBLER_VERSION<7000000
	.asg	0,__TI_EABI__
	.endif
	.if	__TI_EABI__
	.asg	ChaCha20_ctr32,_ChaCha20_ctr32
	.endif

	.asg	B3,RA
	.asg	A15,FP
	.asg	B15,SP

	.global	_ChaCha20_ctr32
	.align	32
_ChaCha20_ctr32:
	.asmfunc	stack_usage(40+64)
	MV	$LEN,A0			; reassign
  [!A0]	BNOP	RA			; no data
|| [A0]	STW	FP,*SP--(40+64)		; save frame pointer and alloca(40+64)
|| [A0]	MV	SP,FP
   [A0]	STDW	B13:B12,*SP[4+8]	; ABI says so
|| [A0]	MV	$KEYB,$KEYA
|| [A0]	MV	$COUNTERA,$COUNTERB
   [A0]	STDW	B11:B10,*SP[3+8]
|| [A0]	STDW	A13:A12,*FP[-3]
   [A0]	STDW	A11:A10,*FP[-4]
|| [A0]	MVK	128,$STEP		; 2 * input block size

   [A0]	LDW	*${KEYA}[0],@Y[4]	; load key
|| [A0]	LDW	*${KEYB}[1],@Y[5]
|| [A0]	MVK	0x00007865,@Y[0]	; synthesize sigma
|| [A0]	MVK	0x0000646e,@Y[1]
   [A0]	LDW	*${KEYA}[2],@Y[6]
|| [A0]	LDW	*${KEYB}[3],@Y[7]
|| [A0]	MVKH	0x61700000,@Y[0]
|| [A0]	MVKH	0x33200000,@Y[1]
	LDW	*${KEYA}[4],@Y[8]
||	LDW	*${KEYB}[5],@Y[9]
||	MVK	0x00002d32,@Y[2]
||	MVK	0x00006574,@Y[3]
	LDW	*${KEYA}[6],@Y[10]
||	LDW	*${KEYB}[7],@Y[11]
||	MVKH	0x79620000,@Y[2]
||	MVKH	0x6b200000,@Y[3]
	LDW	*${COUNTERA}[0],@Y[12]	; load counter||nonce
||	LDW	*${COUNTERB}[1],@Y[13]
||	CMPLTU	A0,$STEP,A1		; is length < 2*blocks?
	LDW	*${COUNTERA}[2],@Y[14]
||	LDW	*${COUNTERB}[3],@Y[15]
|| [A1]	BNOP	top1x?
   [A1]	MVK	64,$STEP		; input block size
||	MVK	10,B0			; inner loop counter

	DMV	@Y[2],@Y[0],@X[2]:@X[0]	; copy block
||	DMV	@Y[3],@Y[1],@X[3]:@X[1]
||[!A1]	STDW	@Y[2]:@Y[0],*FP[-12]	; offload key material to stack
||[!A1]	STDW	@Y[3]:@Y[1],*SP[2]
	DMV	@Y[6],@Y[4],@X[6]:@X[4]
||	DMV	@Y[7],@Y[5],@X[7]:@X[5]
||[!A1]	STDW	@Y[6]:@Y[4],*FP[-10]
||[!A1]	STDW	@Y[7]:@Y[5],*SP[4]
	DMV	@Y[10],@Y[8],@X[10]:@X[8]
||	DMV	@Y[11],@Y[9],@X[11]:@X[9]
||[!A1]	STDW	@Y[10]:@Y[8],*FP[-8]
||[!A1]	STDW	@Y[11]:@Y[9],*SP[6]
	DMV	@Y[14],@Y[12],@X[14]:@X[12]
||	DMV	@Y[15],@Y[13],@X[15]:@X[13]
||[!A1]	MV	@Y[12],@K2x[12]		; counter
||[!A1]	MV	@Y[13],@K2x[13]
||[!A1]	STW	@Y[14],*FP[-6*2]
||[!A1]	STW	@Y[15],*SP[8*2]
___
{	################################################################
	# 2x interleave gives 50% performance improvement
	#
my ($a0,$a1,$a2,$a3) = (0..3);
my ($b0,$b1,$b2,$b3) = (4..7);
my ($c0,$c1,$c2,$c3) = (8..11);
my ($d0,$d1,$d2,$d3) = (12..15);

$code.=<<___;
outer2x?:
	ADD	@X[$b1],@X[$a1],@X[$a1]
||	ADD	@X[$b2],@X[$a2],@X[$a2]
||	ADD	@X[$b0],@X[$a0],@X[$a0]
||	ADD	@X[$b3],@X[$a3],@X[$a3]
||	 DMV	@Y[2],@Y[0],@K2x[2]:@K2x[0]
||	 DMV	@Y[3],@Y[1],@K2x[3]:@K2x[1]
	XOR	@X[$a1],@X[$d1],@X[$d1]
||	XOR	@X[$a2],@X[$d2],@X[$d2]
||	XOR	@X[$a0],@X[$d0],@X[$d0]
||	XOR	@X[$a3],@X[$d3],@X[$d3]
||	 DMV	@Y[6],@Y[4],@K2x[6]:@K2x[4]
||	 DMV	@Y[7],@Y[5],@K2x[7]:@K2x[5]
	SWAP2	@X[$d1],@X[$d1]		; rotate by 16
||	SWAP2	@X[$d2],@X[$d2]
||	SWAP2	@X[$d0],@X[$d0]
||	SWAP2	@X[$d3],@X[$d3]

	ADD	@X[$d1],@X[$c1],@X[$c1]
||	ADD	@X[$d2],@X[$c2],@X[$c2]
||	ADD	@X[$d0],@X[$c0],@X[$c0]
||	ADD	@X[$d3],@X[$c3],@X[$c3]
||	 DMV	@Y[10],@Y[8],@K2x[10]:@K2x[8]
||	 DMV	@Y[11],@Y[9],@K2x[11]:@K2x[9]
	XOR	@X[$c1],@X[$b1],@X[$b1]
||	XOR	@X[$c2],@X[$b2],@X[$b2]
||	XOR	@X[$c0],@X[$b0],@X[$b0]
||	XOR	@X[$c3],@X[$b3],@X[$b3]
||	 ADD	1,@Y[12],@Y[12]		; adjust counter for 2nd block
	ROTL	@X[$b1],12,@X[$b1]
||	ROTL	@X[$b2],12,@X[$b2]
||	 MV	@Y[14],@K2x[14]
||	 MV	@Y[15],@K2x[15]
top2x?:
	ROTL	@X[$b0],12,@X[$b0]
||	ROTL	@X[$b3],12,@X[$b3]
||	 ADD	@Y[$b1],@Y[$a1],@Y[$a1]
||	 ADD	@Y[$b2],@Y[$a2],@Y[$a2]
	 ADD	@Y[$b0],@Y[$a0],@Y[$a0]
||	 ADD	@Y[$b3],@Y[$a3],@Y[$a3]

||	ADD	@X[$b1],@X[$a1],@X[$a1]
||	ADD	@X[$b2],@X[$a2],@X[$a2]
||	 XOR	@Y[$a1],@Y[$d1],@Y[$d1]
||	 XOR	@Y[$a2],@Y[$d2],@Y[$d2]
	 XOR	@Y[$a0],@Y[$d0],@Y[$d0]
||	 XOR	@Y[$a3],@Y[$d3],@Y[$d3]
||	ADD	@X[$b0],@X[$a0],@X[$a0]
||	ADD	@X[$b3],@X[$a3],@X[$a3]
||	XOR	@X[$a1],@X[$d1],@X[$d1]
||	XOR	@X[$a2],@X[$d2],@X[$d2]
	XOR	@X[$a0],@X[$d0],@X[$d0]
||	XOR	@X[$a3],@X[$d3],@X[$d3]
||	ROTL	@X[$d1],8,@X[$d1]
||	ROTL	@X[$d2],8,@X[$d2]
||	 SWAP2	@Y[$d1],@Y[$d1]		; rotate by 16
||	 SWAP2	@Y[$d2],@Y[$d2]
||	 SWAP2	@Y[$d0],@Y[$d0]
||	 SWAP2	@Y[$d3],@Y[$d3]
	ROTL	@X[$d0],8,@X[$d0]
||	ROTL	@X[$d3],8,@X[$d3]
||	 ADD	@Y[$d1],@Y[$c1],@Y[$c1]
||	 ADD	@Y[$d2],@Y[$c2],@Y[$c2]
||	 ADD	@Y[$d0],@Y[$c0],@Y[$c0]
||	 ADD	@Y[$d3],@Y[$c3],@Y[$c3]
||	BNOP	middle2x1?		; protect from interrupt

	ADD	@X[$d1],@X[$c1],@X[$c1]
||	ADD	@X[$d2],@X[$c2],@X[$c2]
||	 XOR	@Y[$c1],@Y[$b1],@Y[$b1]
||	 XOR	@Y[$c2],@Y[$b2],@Y[$b2]
||	 XOR	@Y[$c0],@Y[$b0],@Y[$b0]
||	 XOR	@Y[$c3],@Y[$b3],@Y[$b3]
	ADD	@X[$d0],@X[$c0],@X[$c0]
||	ADD	@X[$d3],@X[$c3],@X[$c3]
||	XOR	@X[$c1],@X[$b1],@X[$b1]
||	XOR	@X[$c2],@X[$b2],@X[$b2]
||	ROTL	@X[$d1],0,@X[$d2]	; moved to avoid cross-path stall
||	ROTL	@X[$d2],0,@X[$d3]
	XOR	@X[$c0],@X[$b0],@X[$b0]
||	XOR	@X[$c3],@X[$b3],@X[$b3]
||	MV	@X[$d0],@X[$d1]
||	MV	@X[$d3],@X[$d0]
||	 ROTL	@Y[$b1],12,@Y[$b1]
||	 ROTL	@Y[$b2],12,@Y[$b2]
	ROTL	@X[$b1],7,@X[$b0]	; avoided cross-path stall
||	ROTL	@X[$b2],7,@X[$b1]
	ROTL	@X[$b0],7,@X[$b3]
||	ROTL	@X[$b3],7,@X[$b2]
middle2x1?:

	 ROTL	@Y[$b0],12,@Y[$b0]
||	 ROTL	@Y[$b3],12,@Y[$b3]
||	ADD	@X[$b0],@X[$a0],@X[$a0]
||	ADD	@X[$b1],@X[$a1],@X[$a1]
	ADD	@X[$b2],@X[$a2],@X[$a2]
||	ADD	@X[$b3],@X[$a3],@X[$a3]

||	 ADD	@Y[$b1],@Y[$a1],@Y[$a1]
||	 ADD	@Y[$b2],@Y[$a2],@Y[$a2]
||	XOR	@X[$a0],@X[$d0],@X[$d0]
||	XOR	@X[$a1],@X[$d1],@X[$d1]
	XOR	@X[$a2],@X[$d2],@X[$d2]
||	XOR	@X[$a3],@X[$d3],@X[$d3]
||	 ADD	@Y[$b0],@Y[$a0],@Y[$a0]
||	 ADD	@Y[$b3],@Y[$a3],@Y[$a3]
||	 XOR	@Y[$a1],@Y[$d1],@Y[$d1]
||	 XOR	@Y[$a2],@Y[$d2],@Y[$d2]
	 XOR	@Y[$a0],@Y[$d0],@Y[$d0]
||	 XOR	@Y[$a3],@Y[$d3],@Y[$d3]
||	 ROTL	@Y[$d1],8,@Y[$d1]
||	 ROTL	@Y[$d2],8,@Y[$d2]
||	SWAP2	@X[$d0],@X[$d0]		; rotate by 16
||	SWAP2	@X[$d1],@X[$d1]
||	SWAP2	@X[$d2],@X[$d2]
||	SWAP2	@X[$d3],@X[$d3]
	 ROTL	@Y[$d0],8,@Y[$d0]
||	 ROTL	@Y[$d3],8,@Y[$d3]
||	ADD	@X[$d0],@X[$c2],@X[$c2]
||	ADD	@X[$d1],@X[$c3],@X[$c3]
||	ADD	@X[$d2],@X[$c0],@X[$c0]
||	ADD	@X[$d3],@X[$c1],@X[$c1]
||	BNOP	middle2x2?		; protect from interrupt

	 ADD	@Y[$d1],@Y[$c1],@Y[$c1]
||	 ADD	@Y[$d2],@Y[$c2],@Y[$c2]
||	XOR	@X[$c2],@X[$b0],@X[$b0]
||	XOR	@X[$c3],@X[$b1],@X[$b1]
||	XOR	@X[$c0],@X[$b2],@X[$b2]
||	XOR	@X[$c1],@X[$b3],@X[$b3]
	 ADD	@Y[$d0],@Y[$c0],@Y[$c0]
||	 ADD	@Y[$d3],@Y[$c3],@Y[$c3]
||	 XOR	@Y[$c1],@Y[$b1],@Y[$b1]
||	 XOR	@Y[$c2],@Y[$b2],@Y[$b2]
||	 ROTL	@Y[$d1],0,@Y[$d2]	; moved to avoid cross-path stall
||	 ROTL	@Y[$d2],0,@Y[$d3]
	 XOR	@Y[$c0],@Y[$b0],@Y[$b0]
||	 XOR	@Y[$c3],@Y[$b3],@Y[$b3]
||	 MV	@Y[$d0],@Y[$d1]
||	 MV	@Y[$d3],@Y[$d0]
||	ROTL	@X[$b0],12,@X[$b0]
||	ROTL	@X[$b1],12,@X[$b1]
	 ROTL	@Y[$b1],7,@Y[$b0]	; avoided cross-path stall
||	 ROTL	@Y[$b2],7,@Y[$b1]
	 ROTL	@Y[$b0],7,@Y[$b3]
||	 ROTL	@Y[$b3],7,@Y[$b2]
middle2x2?:

	ROTL	@X[$b2],12,@X[$b2]
||	ROTL	@X[$b3],12,@X[$b3]
||	 ADD	@Y[$b0],@Y[$a0],@Y[$a0]
||	 ADD	@Y[$b1],@Y[$a1],@Y[$a1]
	 ADD	@Y[$b2],@Y[$a2],@Y[$a2]
||	 ADD	@Y[$b3],@Y[$a3],@Y[$a3]

||	ADD	@X[$b0],@X[$a0],@X[$a0]
||	ADD	@X[$b1],@X[$a1],@X[$a1]
||	 XOR	@Y[$a0],@Y[$d0],@Y[$d0]
||	 XOR	@Y[$a1],@Y[$d1],@Y[$d1]
	 XOR	@Y[$a2],@Y[$d2],@Y[$d2]
||	 XOR	@Y[$a3],@Y[$d3],@Y[$d3]
||	ADD	@X[$b2],@X[$a2],@X[$a2]
||	ADD	@X[$b3],@X[$a3],@X[$a3]
||	XOR	@X[$a0],@X[$d0],@X[$d0]
||	XOR	@X[$a1],@X[$d1],@X[$d1]
	XOR	@X[$a2],@X[$d2],@X[$d2]
||	XOR	@X[$a3],@X[$d3],@X[$d3]
||	ROTL	@X[$d0],8,@X[$d0]
||	ROTL	@X[$d1],8,@X[$d1]
||	 SWAP2	@Y[$d0],@Y[$d0]		; rotate by 16
||	 SWAP2	@Y[$d1],@Y[$d1]
||	 SWAP2	@Y[$d2],@Y[$d2]
||	 SWAP2	@Y[$d3],@Y[$d3]
	ROTL	@X[$d2],8,@X[$d2]
||	ROTL	@X[$d3],8,@X[$d3]
||	 ADD	@Y[$d0],@Y[$c2],@Y[$c2]
||	 ADD	@Y[$d1],@Y[$c3],@Y[$c3]
||	 ADD	@Y[$d2],@Y[$c0],@Y[$c0]
||	 ADD	@Y[$d3],@Y[$c1],@Y[$c1]
||	BNOP	bottom2x1?		; protect from interrupt

	ADD	@X[$d0],@X[$c2],@X[$c2]
||	ADD	@X[$d1],@X[$c3],@X[$c3]
||	 XOR	@Y[$c2],@Y[$b0],@Y[$b0]
||	 XOR	@Y[$c3],@Y[$b1],@Y[$b1]
||	 XOR	@Y[$c0],@Y[$b2],@Y[$b2]
||	 XOR	@Y[$c1],@Y[$b3],@Y[$b3]
	ADD	@X[$d2],@X[$c0],@X[$c0]
||	ADD	@X[$d3],@X[$c1],@X[$c1]
||	XOR	@X[$c2],@X[$b0],@X[$b0]
||	XOR	@X[$c3],@X[$b1],@X[$b1]
||	ROTL	@X[$d0],0,@X[$d3]	; moved to avoid cross-path stall
||	ROTL	@X[$d1],0,@X[$d0]
	XOR	@X[$c0],@X[$b2],@X[$b2]
||	XOR	@X[$c1],@X[$b3],@X[$b3]
||	MV	@X[$d2],@X[$d1]
||	MV	@X[$d3],@X[$d2]
||	 ROTL	@Y[$b0],12,@Y[$b0]
||	 ROTL	@Y[$b1],12,@Y[$b1]
	ROTL	@X[$b0],7,@X[$b1]	; avoided cross-path stall
||	ROTL	@X[$b1],7,@X[$b2]
	ROTL	@X[$b2],7,@X[$b3]
||	ROTL	@X[$b3],7,@X[$b0]
|| [B0]	SUB	B0,1,B0			; decrement inner loop counter
bottom2x1?:

	 ROTL	@Y[$b2],12,@Y[$b2]
||	 ROTL	@Y[$b3],12,@Y[$b3]
|| [B0]	ADD	@X[$b1],@X[$a1],@X[$a1]	; modulo-scheduled
|| [B0]	ADD	@X[$b2],@X[$a2],@X[$a2]
   [B0]	ADD	@X[$b0],@X[$a0],@X[$a0]
|| [B0]	ADD	@X[$b3],@X[$a3],@X[$a3]

||	 ADD	@Y[$b0],@Y[$a0],@Y[$a0]
||	 ADD	@Y[$b1],@Y[$a1],@Y[$a1]
|| [B0]	XOR	@X[$a1],@X[$d1],@X[$d1]
|| [B0]	XOR	@X[$a2],@X[$d2],@X[$d2]
   [B0]	XOR	@X[$a0],@X[$d0],@X[$d0]
|| [B0]	XOR	@X[$a3],@X[$d3],@X[$d3]
||	 ADD	@Y[$b2],@Y[$a2],@Y[$a2]
||	 ADD	@Y[$b3],@Y[$a3],@Y[$a3]
||	 XOR	@Y[$a0],@Y[$d0],@Y[$d0]
||	 XOR	@Y[$a1],@Y[$d1],@Y[$d1]
	 XOR	@Y[$a2],@Y[$d2],@Y[$d2]
||	 XOR	@Y[$a3],@Y[$d3],@Y[$d3]
||	 ROTL	@Y[$d0],8,@Y[$d0]
||	 ROTL	@Y[$d1],8,@Y[$d1]
|| [B0]	SWAP2	@X[$d1],@X[$d1]		; rotate by 16
|| [B0]	SWAP2	@X[$d2],@X[$d2]
|| [B0]	SWAP2	@X[$d0],@X[$d0]
|| [B0]	SWAP2	@X[$d3],@X[$d3]
	 ROTL	@Y[$d2],8,@Y[$d2]
||	 ROTL	@Y[$d3],8,@Y[$d3]
|| [B0]	ADD	@X[$d1],@X[$c1],@X[$c1]
|| [B0]	ADD	@X[$d2],@X[$c2],@X[$c2]
|| [B0]	ADD	@X[$d0],@X[$c0],@X[$c0]
|| [B0]	ADD	@X[$d3],@X[$c3],@X[$c3]
|| [B0]	BNOP	top2x?			; even protects from interrupt

	 ADD	@Y[$d0],@Y[$c2],@Y[$c2]
||	 ADD	@Y[$d1],@Y[$c3],@Y[$c3]
|| [B0]	XOR	@X[$c1],@X[$b1],@X[$b1]
|| [B0]	XOR	@X[$c2],@X[$b2],@X[$b2]
|| [B0]	XOR	@X[$c0],@X[$b0],@X[$b0]
|| [B0]	XOR	@X[$c3],@X[$b3],@X[$b3]
	 ADD	@Y[$d2],@Y[$c0],@Y[$c0]
||	 ADD	@Y[$d3],@Y[$c1],@Y[$c1]
||	 XOR	@Y[$c2],@Y[$b0],@Y[$b0]
||	 XOR	@Y[$c3],@Y[$b1],@Y[$b1]
||	 ROTL	@Y[$d0],0,@Y[$d3]	; moved to avoid cross-path stall
||	 ROTL	@Y[$d1],0,@Y[$d0]
	 XOR	@Y[$c0],@Y[$b2],@Y[$b2]
||	 XOR	@Y[$c1],@Y[$b3],@Y[$b3]
||	 MV	@Y[$d2],@Y[$d1]
||	 MV	@Y[$d3],@Y[$d2]
|| [B0]	ROTL	@X[$b1],12,@X[$b1]
|| [B0]	ROTL	@X[$b2],12,@X[$b2]
	 ROTL	@Y[$b0],7,@Y[$b1]	; avoided cross-path stall
||	 ROTL	@Y[$b1],7,@Y[$b2]
	 ROTL	@Y[$b2],7,@Y[$b3]
||	 ROTL	@Y[$b3],7,@Y[$b0]
bottom2x2?:
___
}

$code.=<<___;
	ADD	@K2x[0],@X[0],@X[0]	; accumulate key material
||	ADD	@K2x[1],@X[1],@X[1]
||	ADD	@K2x[2],@X[2],@X[2]
||	ADD	@K2x[3],@X[3],@X[3]
	 ADD	@K2x[0],@Y[0],@Y[0]
||	 ADD	@K2x[1],@Y[1],@Y[1]
||	 ADD	@K2x[2],@Y[2],@Y[2]
||	 ADD	@K2x[3],@Y[3],@Y[3]
||	LDNDW	*${INP}++[8],@DAT[1]:@DAT[0]
	ADD	@K2x[4],@X[4],@X[4]
||	ADD	@K2x[5],@X[5],@X[5]
||	ADD	@K2x[6],@X[6],@X[6]
||	ADD	@K2x[7],@X[7],@X[7]
||	LDNDW	*${INP}[-7],@DAT[3]:@DAT[2]
	 ADD	@K2x[4],@Y[4],@Y[4]
||	 ADD	@K2x[5],@Y[5],@Y[5]
||	 ADD	@K2x[6],@Y[6],@Y[6]
||	 ADD	@K2x[7],@Y[7],@Y[7]
||	LDNDW	*${INP}[-6],@DAT[5]:@DAT[4]
	ADD	@K2x[8],@X[8],@X[8]
||	ADD	@K2x[9],@X[9],@X[9]
||	ADD	@K2x[10],@X[10],@X[10]
||	ADD	@K2x[11],@X[11],@X[11]
||	LDNDW	*${INP}[-5],@DAT[7]:@DAT[6]
	 ADD	@K2x[8],@Y[8],@Y[8]
||	 ADD	@K2x[9],@Y[9],@Y[9]
||	 ADD	@K2x[10],@Y[10],@Y[10]
||	 ADD	@K2x[11],@Y[11],@Y[11]
||	LDNDW	*${INP}[-4],@DAT[9]:@DAT[8]
	ADD	@K2x[12],@X[12],@X[12]
||	ADD	@K2x[13],@X[13],@X[13]
||	ADD	@K2x[14],@X[14],@X[14]
||	ADD	@K2x[15],@X[15],@X[15]
||	LDNDW	*${INP}[-3],@DAT[11]:@DAT[10]
	 ADD	@K2x[12],@Y[12],@Y[12]
||	 ADD	@K2x[13],@Y[13],@Y[13]
||	 ADD	@K2x[14],@Y[14],@Y[14]
||	 ADD	@K2x[15],@Y[15],@Y[15]
||	LDNDW	*${INP}[-2],@DAT[13]:@DAT[12]
	 ADD	1,@Y[12],@Y[12]		; adjust counter for 2nd block
||	ADD	2,@K2x[12],@K2x[12]	; increment counter
||	LDNDW	*${INP}[-1],@DAT[15]:@DAT[14]

	.if	.BIG_ENDIAN
	SWAP2	@X[0],@X[0]
||	SWAP2	@X[1],@X[1]
||	SWAP2	@X[2],@X[2]
||	SWAP2	@X[3],@X[3]
	SWAP2	@X[4],@X[4]
||	SWAP2	@X[5],@X[5]
||	SWAP2	@X[6],@X[6]
||	SWAP2	@X[7],@X[7]
	SWAP2	@X[8],@X[8]
||	SWAP2	@X[9],@X[9]
||	SWAP4	@X[0],@X[1]
||	SWAP4	@X[1],@X[0]
	SWAP2	@X[10],@X[10]
||	SWAP2	@X[11],@X[11]
||	SWAP4	@X[2],@X[3]
||	SWAP4	@X[3],@X[2]
	SWAP2	@X[12],@X[12]
||	SWAP2	@X[13],@X[13]
||	SWAP4	@X[4],@X[5]
||	SWAP4	@X[5],@X[4]
	SWAP2	@X[14],@X[14]
||	SWAP2	@X[15],@X[15]
||	SWAP4	@X[6],@X[7]
||	SWAP4	@X[7],@X[6]
	SWAP4	@X[8],@X[9]
||	SWAP4	@X[9],@X[8]
||	 SWAP2	@Y[0],@Y[0]
||	 SWAP2	@Y[1],@Y[1]
	SWAP4	@X[10],@X[11]
||	SWAP4	@X[11],@X[10]
||	 SWAP2	@Y[2],@Y[2]
||	 SWAP2	@Y[3],@Y[3]
	SWAP4	@X[12],@X[13]
||	SWAP4	@X[13],@X[12]
||	 SWAP2	@Y[4],@Y[4]
||	 SWAP2	@Y[5],@Y[5]
	SWAP4	@X[14],@X[15]
||	SWAP4	@X[15],@X[14]
||	 SWAP2	@Y[6],@Y[6]
||	 SWAP2	@Y[7],@Y[7]
	 SWAP2	@Y[8],@Y[8]
||	 SWAP2	@Y[9],@Y[9]
||	 SWAP4	@Y[0],@Y[1]
||	 SWAP4	@Y[1],@Y[0]
	 SWAP2	@Y[10],@Y[10]
||	 SWAP2	@Y[11],@Y[11]
||	 SWAP4	@Y[2],@Y[3]
||	 SWAP4	@Y[3],@Y[2]
	 SWAP2	@Y[12],@Y[12]
||	 SWAP2	@Y[13],@Y[13]
||	 SWAP4	@Y[4],@Y[5]
||	 SWAP4	@Y[5],@Y[4]
	 SWAP2	@Y[14],@Y[14]
||	 SWAP2	@Y[15],@Y[15]
||	 SWAP4	@Y[6],@Y[7]
||	 SWAP4	@Y[7],@Y[6]
	 SWAP4	@Y[8],@Y[9]
||	 SWAP4	@Y[9],@Y[8]
	 SWAP4	@Y[10],@Y[11]
||	 SWAP4	@Y[11],@Y[10]
	 SWAP4	@Y[12],@Y[13]
||	 SWAP4	@Y[13],@Y[12]
	 SWAP4	@Y[14],@Y[15]
||	 SWAP4	@Y[15],@Y[14]
	.endif

	XOR	@DAT[0],@X[0],@X[0]	; xor 1st block
||	XOR	@DAT[3],@X[3],@X[3]
||	XOR	@DAT[2],@X[2],@X[1]
||	XOR	@DAT[1],@X[1],@X[2]
||	LDNDW	*${INP}++[8],@DAT[1]:@DAT[0]
	XOR	@DAT[4],@X[4],@X[4]
||	XOR	@DAT[7],@X[7],@X[7]
||	LDNDW	*${INP}[-7],@DAT[3]:@DAT[2]
	XOR	@DAT[6],@X[6],@X[5]
||	XOR	@DAT[5],@X[5],@X[6]
||	LDNDW	*${INP}[-6],@DAT[5]:@DAT[4]
	XOR	@DAT[8],@X[8],@X[8]
||	XOR	@DAT[11],@X[11],@X[11]
||	LDNDW	*${INP}[-5],@DAT[7]:@DAT[6]
	XOR	@DAT[10],@X[10],@X[9]
||	XOR	@DAT[9],@X[9],@X[10]
||	LDNDW	*${INP}[-4],@DAT[9]:@DAT[8]
	XOR	@DAT[12],@X[12],@X[12]
||	XOR	@DAT[15],@X[15],@X[15]
||	LDNDW	*${INP}[-3],@DAT[11]:@DAT[10]
	XOR	@DAT[14],@X[14],@X[13]
||	XOR	@DAT[13],@X[13],@X[14]
||	LDNDW	*${INP}[-2],@DAT[13]:@DAT[12]
   [A0]	SUB	A0,$STEP,A0		; SUB	A0,128,A0
||	LDNDW	*${INP}[-1],@DAT[15]:@DAT[14]

	XOR	@Y[0],@DAT[0],@DAT[0]	; xor 2nd block
||	XOR	@Y[1],@DAT[1],@DAT[1]
||	STNDW	@X[2]:@X[0],*${OUT}++[8]
	XOR	@Y[2],@DAT[2],@DAT[2]
||	XOR	@Y[3],@DAT[3],@DAT[3]
||	STNDW	@X[3]:@X[1],*${OUT}[-7]
	XOR	@Y[4],@DAT[4],@DAT[4]
|| [A0]	LDDW	*FP[-12],@X[2]:@X[0]	; re-load key material from stack
|| [A0]	LDDW	*SP[2],  @X[3]:@X[1]
	XOR	@Y[5],@DAT[5],@DAT[5]
||	STNDW	@X[6]:@X[4],*${OUT}[-6]
	XOR	@Y[6],@DAT[6],@DAT[6]
||	XOR	@Y[7],@DAT[7],@DAT[7]
||	STNDW	@X[7]:@X[5],*${OUT}[-5]
	XOR	@Y[8],@DAT[8],@DAT[8]
|| [A0]	LDDW	*FP[-10],@X[6]:@X[4]
|| [A0]	LDDW	*SP[4],  @X[7]:@X[5]
	XOR	@Y[9],@DAT[9],@DAT[9]
||	STNDW	@X[10]:@X[8],*${OUT}[-4]
	XOR	@Y[10],@DAT[10],@DAT[10]
||	XOR	@Y[11],@DAT[11],@DAT[11]
||	STNDW	@X[11]:@X[9],*${OUT}[-3]
	XOR	@Y[12],@DAT[12],@DAT[12]
|| [A0]	LDDW	*FP[-8], @X[10]:@X[8]
|| [A0]	LDDW	*SP[6],  @X[11]:@X[9]
	XOR	@Y[13],@DAT[13],@DAT[13]
||	STNDW	@X[14]:@X[12],*${OUT}[-2]
	XOR	@Y[14],@DAT[14],@DAT[14]
||	XOR	@Y[15],@DAT[15],@DAT[15]
||	STNDW	@X[15]:@X[13],*${OUT}[-1]

   [A0]	MV	@K2x[12],@X[12]
|| [A0]	MV	@K2x[13],@X[13]
|| [A0]	LDW	*FP[-6*2], @X[14]
|| [A0]	LDW	*SP[8*2],  @X[15]

   [A0]	DMV	@X[2],@X[0],@Y[2]:@Y[0]	; duplicate key material
||	STNDW	@DAT[1]:@DAT[0],*${OUT}++[8]
   [A0]	DMV	@X[3],@X[1],@Y[3]:@Y[1]
||	STNDW	@DAT[3]:@DAT[2],*${OUT}[-7]
   [A0]	DMV	@X[6],@X[4],@Y[6]:@Y[4]
||	STNDW	@DAT[5]:@DAT[4],*${OUT}[-6]
||	CMPLTU	A0,$STEP,A1		; is remaining length < 2*blocks?
||[!A0]	BNOP	epilogue?
   [A0]	DMV	@X[7],@X[5],@Y[7]:@Y[5]
||	STNDW	@DAT[7]:@DAT[6],*${OUT}[-5]
||[!A1]	BNOP	outer2x?
   [A0]	DMV	@X[10],@X[8],@Y[10]:@Y[8]
||	STNDW	@DAT[9]:@DAT[8],*${OUT}[-4]
   [A0]	DMV	@X[11],@X[9],@Y[11]:@Y[9]
||	STNDW	@DAT[11]:@DAT[10],*${OUT}[-3]
   [A0]	DMV	@X[14],@X[12],@Y[14]:@Y[12]
||	STNDW	@DAT[13]:@DAT[12],*${OUT}[-2]
   [A0]	DMV	@X[15],@X[13],@Y[15]:@Y[13]
||	STNDW	@DAT[15]:@DAT[14],*${OUT}[-1]
;;===== branch to epilogue? is taken here
   [A1]	MVK	64,$STEP
|| [A0]	MVK	10,B0			; inner loop counter
;;===== branch to outer2x? is taken here
___
{
my ($a0,$a1,$a2,$a3) = (0..3);
my ($b0,$b1,$b2,$b3) = (4..7);
my ($c0,$c1,$c2,$c3) = (8..11);
my ($d0,$d1,$d2,$d3) = (12..15);

$code.=<<___;
top1x?:
	ADD	@X[$b1],@X[$a1],@X[$a1]
||	ADD	@X[$b2],@X[$a2],@X[$a2]
	ADD	@X[$b0],@X[$a0],@X[$a0]
||	ADD	@X[$b3],@X[$a3],@X[$a3]
||	XOR	@X[$a1],@X[$d1],@X[$d1]
||	XOR	@X[$a2],@X[$d2],@X[$d2]
	XOR	@X[$a0],@X[$d0],@X[$d0]
||	XOR	@X[$a3],@X[$d3],@X[$d3]
||	SWAP2	@X[$d1],@X[$d1]		; rotate by 16
||	SWAP2	@X[$d2],@X[$d2]
	SWAP2	@X[$d0],@X[$d0]
||	SWAP2	@X[$d3],@X[$d3]

||	ADD	@X[$d1],@X[$c1],@X[$c1]
||	ADD	@X[$d2],@X[$c2],@X[$c2]
	ADD	@X[$d0],@X[$c0],@X[$c0]
||	ADD	@X[$d3],@X[$c3],@X[$c3]
||	XOR	@X[$c1],@X[$b1],@X[$b1]
||	XOR	@X[$c2],@X[$b2],@X[$b2]
	XOR	@X[$c0],@X[$b0],@X[$b0]
||	XOR	@X[$c3],@X[$b3],@X[$b3]
||	ROTL	@X[$b1],12,@X[$b1]
||	ROTL	@X[$b2],12,@X[$b2]
	ROTL	@X[$b0],12,@X[$b0]
||	ROTL	@X[$b3],12,@X[$b3]

	ADD	@X[$b1],@X[$a1],@X[$a1]
||	ADD	@X[$b2],@X[$a2],@X[$a2]
	ADD	@X[$b0],@X[$a0],@X[$a0]
||	ADD	@X[$b3],@X[$a3],@X[$a3]
||	XOR	@X[$a1],@X[$d1],@X[$d1]
||	XOR	@X[$a2],@X[$d2],@X[$d2]
	XOR	@X[$a0],@X[$d0],@X[$d0]
||	XOR	@X[$a3],@X[$d3],@X[$d3]
||	ROTL	@X[$d1],8,@X[$d1]
||	ROTL	@X[$d2],8,@X[$d2]
	ROTL	@X[$d0],8,@X[$d0]
||	ROTL	@X[$d3],8,@X[$d3]
||	BNOP	middle1x?		; protect from interrupt

	ADD	@X[$d1],@X[$c1],@X[$c1]
||	ADD	@X[$d2],@X[$c2],@X[$c2]
	ADD	@X[$d0],@X[$c0],@X[$c0]
||	ADD	@X[$d3],@X[$c3],@X[$c3]
||	XOR	@X[$c1],@X[$b1],@X[$b1]
||	XOR	@X[$c2],@X[$b2],@X[$b2]
||	ROTL	@X[$d1],0,@X[$d2]	; moved to avoid cross-path stall
||	ROTL	@X[$d2],0,@X[$d3]
	XOR	@X[$c0],@X[$b0],@X[$b0]
||	XOR	@X[$c3],@X[$b3],@X[$b3]
||	ROTL	@X[$d0],0,@X[$d1]
||	ROTL	@X[$d3],0,@X[$d0]
	ROTL	@X[$b1],7,@X[$b0]	; avoided cross-path stall
||	ROTL	@X[$b2],7,@X[$b1]
	ROTL	@X[$b0],7,@X[$b3]
||	ROTL	@X[$b3],7,@X[$b2]
middle1x?:

	ADD	@X[$b0],@X[$a0],@X[$a0]
||	ADD	@X[$b1],@X[$a1],@X[$a1]
	ADD	@X[$b2],@X[$a2],@X[$a2]
||	ADD	@X[$b3],@X[$a3],@X[$a3]
||	XOR	@X[$a0],@X[$d0],@X[$d0]
||	XOR	@X[$a1],@X[$d1],@X[$d1]
	XOR	@X[$a2],@X[$d2],@X[$d2]
||	XOR	@X[$a3],@X[$d3],@X[$d3]
||	SWAP2	@X[$d0],@X[$d0]		; rotate by 16
||	SWAP2	@X[$d1],@X[$d1]
	SWAP2	@X[$d2],@X[$d2]
||	SWAP2	@X[$d3],@X[$d3]

||	ADD	@X[$d0],@X[$c2],@X[$c2]
||	ADD	@X[$d1],@X[$c3],@X[$c3]
	ADD	@X[$d2],@X[$c0],@X[$c0]
||	ADD	@X[$d3],@X[$c1],@X[$c1]
||	XOR	@X[$c2],@X[$b0],@X[$b0]
||	XOR	@X[$c3],@X[$b1],@X[$b1]
	XOR	@X[$c0],@X[$b2],@X[$b2]
||	XOR	@X[$c1],@X[$b3],@X[$b3]
||	ROTL	@X[$b0],12,@X[$b0]
||	ROTL	@X[$b1],12,@X[$b1]
	ROTL	@X[$b2],12,@X[$b2]
||	ROTL	@X[$b3],12,@X[$b3]

	ADD	@X[$b0],@X[$a0],@X[$a0]
||	ADD	@X[$b1],@X[$a1],@X[$a1]
|| [B0]	SUB	B0,1,B0			; decrement inner loop counter
	ADD	@X[$b2],@X[$a2],@X[$a2]
||	ADD	@X[$b3],@X[$a3],@X[$a3]
||	XOR	@X[$a0],@X[$d0],@X[$d0]
||	XOR	@X[$a1],@X[$d1],@X[$d1]
	XOR	@X[$a2],@X[$d2],@X[$d2]
||	XOR	@X[$a3],@X[$d3],@X[$d3]
||	ROTL	@X[$d0],8,@X[$d0]
||	ROTL	@X[$d1],8,@X[$d1]
	ROTL	@X[$d2],8,@X[$d2]
||	ROTL	@X[$d3],8,@X[$d3]
|| [B0]	BNOP	top1x?			; even protects from interrupt

	ADD	@X[$d0],@X[$c2],@X[$c2]
||	ADD	@X[$d1],@X[$c3],@X[$c3]
	ADD	@X[$d2],@X[$c0],@X[$c0]
||	ADD	@X[$d3],@X[$c1],@X[$c1]
||	XOR	@X[$c2],@X[$b0],@X[$b0]
||	XOR	@X[$c3],@X[$b1],@X[$b1]
||	ROTL	@X[$d0],0,@X[$d3]	; moved to avoid cross-path stall
||	ROTL	@X[$d1],0,@X[$d0]
	XOR	@X[$c0],@X[$b2],@X[$b2]
||	XOR	@X[$c1],@X[$b3],@X[$b3]
||	ROTL	@X[$d2],0,@X[$d1]
||	ROTL	@X[$d3],0,@X[$d2]
	ROTL	@X[$b0],7,@X[$b1]	; avoided cross-path stall
||	ROTL	@X[$b1],7,@X[$b2]
	ROTL	@X[$b2],7,@X[$b3]
||	ROTL	@X[$b3],7,@X[$b0]
||[!B0]	CMPLTU	A0,$STEP,A1		; less than 64 bytes left?
bottom1x?:
___
}

$code.=<<___;
	ADD	@Y[0],@X[0],@X[0]	; accumulate key material
||	ADD	@Y[1],@X[1],@X[1]
||	ADD	@Y[2],@X[2],@X[2]
||	ADD	@Y[3],@X[3],@X[3]
||[!A1]	LDNDW	*${INP}++[8],@DAT[1]:@DAT[0]
|| [A1]	BNOP	tail?
	ADD	@Y[4],@X[4],@X[4]
||	ADD	@Y[5],@X[5],@X[5]
||	ADD	@Y[6],@X[6],@X[6]
||	ADD	@Y[7],@X[7],@X[7]
||[!A1]	LDNDW	*${INP}[-7],@DAT[3]:@DAT[2]
	ADD	@Y[8],@X[8],@X[8]
||	ADD	@Y[9],@X[9],@X[9]
||	ADD	@Y[10],@X[10],@X[10]
||	ADD	@Y[11],@X[11],@X[11]
||[!A1]	LDNDW	*${INP}[-6],@DAT[5]:@DAT[4]
	ADD	@Y[12],@X[12],@X[12]
||	ADD	@Y[13],@X[13],@X[13]
||	ADD	@Y[14],@X[14],@X[14]
||	ADD	@Y[15],@X[15],@X[15]
||[!A1]	LDNDW	*${INP}[-5],@DAT[7]:@DAT[6]
  [!A1]	LDNDW	*${INP}[-4],@DAT[9]:@DAT[8]
  [!A1]	LDNDW	*${INP}[-3],@DAT[11]:@DAT[10]
	LDNDW	*${INP}[-2],@DAT[13]:@DAT[12]
	LDNDW	*${INP}[-1],@DAT[15]:@DAT[14]

	.if	.BIG_ENDIAN
	SWAP2	@X[0],@X[0]
||	SWAP2	@X[1],@X[1]
||	SWAP2	@X[2],@X[2]
||	SWAP2	@X[3],@X[3]
	SWAP2	@X[4],@X[4]
||	SWAP2	@X[5],@X[5]
||	SWAP2	@X[6],@X[6]
||	SWAP2	@X[7],@X[7]
	SWAP2	@X[8],@X[8]
||	SWAP2	@X[9],@X[9]
||	SWAP4	@X[0],@X[1]
||	SWAP4	@X[1],@X[0]
	SWAP2	@X[10],@X[10]
||	SWAP2	@X[11],@X[11]
||	SWAP4	@X[2],@X[3]
||	SWAP4	@X[3],@X[2]
	SWAP2	@X[12],@X[12]
||	SWAP2	@X[13],@X[13]
||	SWAP4	@X[4],@X[5]
||	SWAP4	@X[5],@X[4]
	SWAP2	@X[14],@X[14]
||	SWAP2	@X[15],@X[15]
||	SWAP4	@X[6],@X[7]
||	SWAP4	@X[7],@X[6]
	SWAP4	@X[8],@X[9]
||	SWAP4	@X[9],@X[8]
	SWAP4	@X[10],@X[11]
||	SWAP4	@X[11],@X[10]
	SWAP4	@X[12],@X[13]
||	SWAP4	@X[13],@X[12]
	SWAP4	@X[14],@X[15]
||	SWAP4	@X[15],@X[14]
	.else
	NOP	1
	.endif

	XOR	@X[0],@DAT[0],@DAT[0]	; xor with input
||	XOR	@X[1],@DAT[1],@DAT[1]
||	XOR	@X[2],@DAT[2],@DAT[2]
||	XOR	@X[3],@DAT[3],@DAT[3]
|| [A0]	SUB	A0,$STEP,A0		; SUB	A0,64,A0
	XOR	@X[4],@DAT[4],@DAT[4]
||	XOR	@X[5],@DAT[5],@DAT[5]
||	XOR	@X[6],@DAT[6],@DAT[6]
||	XOR	@X[7],@DAT[7],@DAT[7]
||	STNDW	@DAT[1]:@DAT[0],*${OUT}++[8]
	XOR	@X[8],@DAT[8],@DAT[8]
||	XOR	@X[9],@DAT[9],@DAT[9]
||	XOR	@X[10],@DAT[10],@DAT[10]
||	XOR	@X[11],@DAT[11],@DAT[11]
||	STNDW	@DAT[3]:@DAT[2],*${OUT}[-7]
	XOR	@X[12],@DAT[12],@DAT[12]
||	XOR	@X[13],@DAT[13],@DAT[13]
||	XOR	@X[14],@DAT[14],@DAT[14]
||	XOR	@X[15],@DAT[15],@DAT[15]
||	STNDW	@DAT[5]:@DAT[4],*${OUT}[-6]
|| [A0]	BNOP	top1x?
   [A0]	DMV	@Y[2],@Y[0],@X[2]:@X[0]	; duplicate key material
|| [A0]	DMV	@Y[3],@Y[1],@X[3]:@X[1]
||	STNDW	@DAT[7]:@DAT[6],*${OUT}[-5]
   [A0]	DMV	@Y[6],@Y[4],@X[6]:@X[4]
|| [A0]	DMV	@Y[7],@Y[5],@X[7]:@X[5]
||	STNDW	@DAT[9]:@DAT[8],*${OUT}[-4]
   [A0]	DMV	@Y[10],@Y[8],@X[10]:@X[8]
|| [A0]	DMV	@Y[11],@Y[9],@X[11]:@X[9]
|| [A0]	ADD	1,@Y[12],@Y[12]		; increment counter
||	STNDW	@DAT[11]:@DAT[10],*${OUT}[-3]
   [A0]	DMV	@Y[14],@Y[12],@X[14]:@X[12]
|| [A0]	DMV	@Y[15],@Y[13],@X[15]:@X[13]
||	STNDW	@DAT[13]:@DAT[12],*${OUT}[-2]
   [A0]	MVK	10,B0			; inner loop counter
||	STNDW	@DAT[15]:@DAT[14],*${OUT}[-1]
;;===== branch to top1x? is taken here

epilogue?:
	LDDW	*FP[-4],A11:A10		; ABI says so
	LDDW	*FP[-3],A13:A12
||	LDDW	*SP[3+8],B11:B10
	LDDW	*SP[4+8],B13:B12
||	BNOP	RA
	LDW	*++SP(40+64),FP		; restore frame pointer
	NOP	4

tail?:
	LDBU	*${INP}++[1],B24	; load byte by byte
||	SUB	A0,1,A0
||	SUB	A0,1,B1
  [!B1]	BNOP	epilogue?		; interrupts are disabled for whole time
|| [A0] LDBU	*${INP}++[1],B24
|| [A0]	SUB	A0,1,A0
||	SUB	B1,1,B1
  [!B1]	BNOP	epilogue?
|| [A0] LDBU	*${INP}++[1],B24
|| [A0]	SUB	A0,1,A0
||	SUB	B1,1,B1
  [!B1]	BNOP	epilogue?
||	ROTL	@X[0],0,A24
|| [A0] LDBU	*${INP}++[1],B24
|| [A0]	SUB	A0,1,A0
||	SUB	B1,1,B1
  [!B1]	BNOP	epilogue?
||	ROTL	@X[0],24,A24
|| [A0] LDBU	*${INP}++[1],A24
|| [A0]	SUB	A0,1,A0
||	SUB	B1,1,B1
  [!B1]	BNOP	epilogue?
||	ROTL	@X[0],16,A24
|| [A0] LDBU	*${INP}++[1],A24
|| [A0]	SUB	A0,1,A0
||	SUB	B1,1,B1
||	XOR	A24,B24,B25
	STB	B25,*${OUT}++[1]	; store byte by byte
||[!B1]	BNOP	epilogue?
||	ROTL	@X[0],8,A24
|| [A0] LDBU	*${INP}++[1],A24
|| [A0]	SUB	A0,1,A0
||	SUB	B1,1,B1
||	XOR	A24,B24,B25
	STB	B25,*${OUT}++[1]
___
sub TAIL_STEP {
my $Xi= shift;
my $T = ($Xi=~/^B/?"B24":"A24");	# match @X[i] to avoid cross path
my $D = $T; $D=~tr/AB/BA/;
my $O = $D; $O=~s/24/25/;

$code.=<<___;
||[!B1]	BNOP	epilogue?
||	ROTL	$Xi,0,$T
|| [A0] LDBU	*${INP}++[1],$D
|| [A0]	SUB	A0,1,A0
||	SUB	B1,1,B1
||	XOR	A24,B24,$O
	STB	$O,*${OUT}++[1]
||[!B1]	BNOP	epilogue?
||	ROTL	$Xi,24,$T
|| [A0] LDBU	*${INP}++[1],$T
|| [A0]	SUB	A0,1,A0
||	SUB	B1,1,B1
||	XOR	A24,B24,$O
	STB	$O,*${OUT}++[1]
||[!B1]	BNOP	epilogue?
||	ROTL	$Xi,16,$T
|| [A0] LDBU	*${INP}++[1],$T
|| [A0]	SUB	A0,1,A0
||	SUB	B1,1,B1
||	XOR	A24,B24,$O
	STB	$O,*${OUT}++[1]
||[!B1]	BNOP	epilogue?
||	ROTL	$Xi,8,$T
|| [A0] LDBU	*${INP}++[1],$T
|| [A0]	SUB	A0,1,A0
||	SUB	B1,1,B1
||	XOR	A24,B24,$O
	STB	$O,*${OUT}++[1]
___
}
	foreach (1..14) { TAIL_STEP(@X[$_]); }
$code.=<<___;
||[!B1]	BNOP	epilogue?
||	ROTL	@X[15],0,B24
||	XOR	A24,B24,A25
	STB	A25,*${OUT}++[1]
||	ROTL	@X[15],24,B24
||	XOR	A24,B24,A25
	STB	A25,*${OUT}++[1]
||	ROTL	@X[15],16,B24
||	XOR	A24,B24,A25
	STB	A25,*${OUT}++[1]
||	XOR	A24,B24,A25
	STB	A25,*${OUT}++[1]
||	XOR	A24,B24,B25
	STB	B25,*${OUT}++[1]
	.endasmfunc

	.sect	.const
	.cstring "ChaCha20 for C64x+, CRYPTOGAMS by <appro\@openssl.org>"
	.align	4
___

print $code;
close STDOUT or die "error closing STDOUT: $!";