#!/usr/bin/env perl
# SPDX-License-Identifier: GPL-1.0+ OR BSD-3-Clause
#
# ====================================================================
# Written by Andy Polyakov, @dot-asm, originally for the OpenSSL
# project.
# ====================================================================

# Poly1305 hash for MIPS.
#
# May 2016
#
# Numbers are cycles per processed byte with poly1305_blocks alone.
#
#		IALU/gcc
# R1x000	~5.5/+130%	(big-endian)
# Octeon II	2.50/+70%	(little-endian)
#
# March 2019
#
# Add 32-bit code path.
#
# October 2019
#
# Modulo-scheduling reduction allows to omit dependency chain at the
# end of inner loop and improve performance. Also optimize MIPS32R2
# code path for MIPS 1004K core. Per René von Dorst's suggestions.
#
#		IALU/gcc
# R1x000	~9.8/?		(big-endian)
# Octeon II	3.65/+140%	(little-endian)
# MT7621/1004K	4.75/?		(little-endian)
#
######################################################################
# There is a number of MIPS ABI in use, O32 and N32/64 are most
# widely used. Then there is a new contender: NUBI. It appears that if
# one picks the latter, it's possible to arrange code in ABI neutral
# manner. Therefore let's stick to NUBI register layout:
#
($zero,$at,$t0,$t1,$t2)=map("\$$_",(0..2,24,25));
($a0,$a1,$a2,$a3,$a4,$a5,$a6,$a7)=map("\$$_",(4..11));
($s0,$s1,$s2,$s3,$s4,$s5,$s6,$s7,$s8,$s9,$s10,$s11)=map("\$$_",(12..23));
($gp,$tp,$sp,$fp,$ra)=map("\$$_",(3,28..31));
#
# The return value is placed in $a0. Following coding rules facilitate
# interoperability:
#
# - never ever touch $tp, "thread pointer", former $gp [o32 can be
#   excluded from the rule, because it's specified volatile];
# - copy return value to $t0, former $v0 [or to $a0 if you're adapting
#   old code];
# - on O32 populate $a4-$a7 with 'lw $aN,4*N($sp)' if necessary;
#
# For reference here is register layout for N32/64 MIPS ABIs:
#
# ($zero,$at,$v0,$v1)=map("\$$_",(0..3));
# ($a0,$a1,$a2,$a3,$a4,$a5,$a6,$a7)=map("\$$_",(4..11));
# ($t0,$t1,$t2,$t3,$t8,$t9)=map("\$$_",(12..15,24,25));
# ($s0,$s1,$s2,$s3,$s4,$s5,$s6,$s7)=map("\$$_",(16..23));
# ($gp,$sp,$fp,$ra)=map("\$$_",(28..31));
#
# <appro@openssl.org>
#
######################################################################

$flavour = shift || "64"; # supported flavours are o32,n32,64,nubi32,nubi64

$v0 = ($flavour =~ /nubi/i) ? $a0 : $t0;

if ($flavour =~ /64|n32/i) {{{
######################################################################
# 64-bit code path
#

my ($ctx,$inp,$len,$padbit) = ($a0,$a1,$a2,$a3);
my ($in0,$in1,$tmp0,$tmp1,$tmp2,$tmp3,$tmp4) = ($a4,$a5,$a6,$a7,$at,$t0,$t1);

$code.=<<___;
#if (defined(_MIPS_ARCH_MIPS64R3) || defined(_MIPS_ARCH_MIPS64R5) || \\
     defined(_MIPS_ARCH_MIPS64R6)) \\
     && !defined(_MIPS_ARCH_MIPS64R2)
# define _MIPS_ARCH_MIPS64R2
#endif

#if defined(_MIPS_ARCH_MIPS64R6)
# define dmultu(rs,rt)
# define mflo(rd,rs,rt)	dmulu	rd,rs,rt
# define mfhi(rd,rs,rt)	dmuhu	rd,rs,rt
#else
# define dmultu(rs,rt)		dmultu	rs,rt
# define mflo(rd,rs,rt)	mflo	rd
# define mfhi(rd,rs,rt)	mfhi	rd
#endif

#ifdef	__KERNEL__
# define poly1305_init   poly1305_init_mips
# define poly1305_blocks poly1305_blocks_mips
# define poly1305_emit   poly1305_emit_mips
#endif

#if defined(__MIPSEB__) && !defined(MIPSEB)
# define MIPSEB
#endif

#ifdef MIPSEB
# define MSB 0
# define LSB 7
#else
# define MSB 7
# define LSB 0
#endif

.text
.set	noat
.set	noreorder

.align	5
.globl	poly1305_init
.ent	poly1305_init
poly1305_init:
	.frame	$sp,0,$ra
	.set	reorder

	sd	$zero,0($ctx)
	sd	$zero,8($ctx)
	sd	$zero,16($ctx)

	beqz	$inp,.Lno_key

#if defined(_MIPS_ARCH_MIPS64R6)
	andi	$tmp0,$inp,7		# $inp % 8
	dsubu	$inp,$inp,$tmp0		# align $inp
	sll	$tmp0,$tmp0,3		# byte to bit offset
	ld	$in0,0($inp)
	ld	$in1,8($inp)
	beqz	$tmp0,.Laligned_key
	ld	$tmp2,16($inp)

	subu	$tmp1,$zero,$tmp0
# ifdef	MIPSEB
	dsllv	$in0,$in0,$tmp0
	dsrlv	$tmp3,$in1,$tmp1
	dsllv	$in1,$in1,$tmp0
	dsrlv	$tmp2,$tmp2,$tmp1
# else
	dsrlv	$in0,$in0,$tmp0
	dsllv	$tmp3,$in1,$tmp1
	dsrlv	$in1,$in1,$tmp0
	dsllv	$tmp2,$tmp2,$tmp1
# endif
	or	$in0,$in0,$tmp3
	or	$in1,$in1,$tmp2
.Laligned_key:
#else
	ldl	$in0,0+MSB($inp)
	ldl	$in1,8+MSB($inp)
	ldr	$in0,0+LSB($inp)
	ldr	$in1,8+LSB($inp)
#endif
#ifdef	MIPSEB
# if defined(_MIPS_ARCH_MIPS64R2)
	dsbh	$in0,$in0		# byte swap
	 dsbh	$in1,$in1
	dshd	$in0,$in0
	 dshd	$in1,$in1
# else
	ori	$tmp0,$zero,0xFF
	dsll	$tmp2,$tmp0,32
	or	$tmp0,$tmp2		# 0x000000FF000000FF

	and	$tmp1,$in0,$tmp0	# byte swap
	 and	$tmp3,$in1,$tmp0
	dsrl	$tmp2,$in0,24
	 dsrl	$tmp4,$in1,24
	dsll	$tmp1,24
	 dsll	$tmp3,24
	and	$tmp2,$tmp0
	 and	$tmp4,$tmp0
	dsll	$tmp0,8			# 0x0000FF000000FF00
	or	$tmp1,$tmp2
	 or	$tmp3,$tmp4
	and	$tmp2,$in0,$tmp0
	 and	$tmp4,$in1,$tmp0
	dsrl	$in0,8
	 dsrl	$in1,8
	dsll	$tmp2,8
	 dsll	$tmp4,8
	and	$in0,$tmp0
	 and	$in1,$tmp0
	or	$tmp1,$tmp2
	 or	$tmp3,$tmp4
	or	$in0,$tmp1
	 or	$in1,$tmp3
	dsrl	$tmp1,$in0,32
	 dsrl	$tmp3,$in1,32
	dsll	$in0,32
	 dsll	$in1,32
	or	$in0,$tmp1
	 or	$in1,$tmp3
# endif
#endif
	li	$tmp0,1
	dsll	$tmp0,32		# 0x0000000100000000
	daddiu	$tmp0,-63		# 0x00000000ffffffc1
	dsll	$tmp0,28		# 0x0ffffffc10000000
	daddiu	$tmp0,-1		# 0x0ffffffc0fffffff

	and	$in0,$tmp0
	daddiu	$tmp0,-3		# 0x0ffffffc0ffffffc
	and	$in1,$tmp0

	sd	$in0,24($ctx)
	dsrl	$tmp0,$in1,2
	sd	$in1,32($ctx)
	daddu	$tmp0,$in1		# s1 = r1 + (r1 >> 2)
	sd	$tmp0,40($ctx)

.Lno_key:
	li	$v0,0			# return 0
	jr	$ra
.end	poly1305_init
___
{
my $SAVED_REGS_MASK = ($flavour =~ /nubi/i) ? "0x0003f000" : "0x00030000";

my ($h0,$h1,$h2,$r0,$r1,$rs1,$d0,$d1,$d2) =
   ($s0,$s1,$s2,$s3,$s4,$s5,$in0,$in1,$t2);
my ($shr,$shl) = ($s6,$s7);		# used on R6

$code.=<<___;
.align	5
.globl	poly1305_blocks
.ent	poly1305_blocks
poly1305_blocks:
	.set	noreorder
	dsrl	$len,4			# number of complete blocks
	bnez	$len,poly1305_blocks_internal
	nop
	jr	$ra
	nop
.end	poly1305_blocks

.align	5
.ent	poly1305_blocks_internal
poly1305_blocks_internal:
	.set	noreorder
#if defined(_MIPS_ARCH_MIPS64R6)
	.frame	$sp,8*8,$ra
	.mask	$SAVED_REGS_MASK|0x000c0000,-8
	dsubu	$sp,8*8
	sd	$s7,56($sp)
	sd	$s6,48($sp)
#else
	.frame	$sp,6*8,$ra
	.mask	$SAVED_REGS_MASK,-8
	dsubu	$sp,6*8
#endif
	sd	$s5,40($sp)
	sd	$s4,32($sp)
___
$code.=<<___ if ($flavour =~ /nubi/i);	# optimize non-nubi prologue
	sd	$s3,24($sp)
	sd	$s2,16($sp)
	sd	$s1,8($sp)
	sd	$s0,0($sp)
___
$code.=<<___;
	.set	reorder

#if defined(_MIPS_ARCH_MIPS64R6)
	andi	$shr,$inp,7
	dsubu	$inp,$inp,$shr		# align $inp
	sll	$shr,$shr,3		# byte to bit offset
	subu	$shl,$zero,$shr
#endif

	ld	$h0,0($ctx)		# load hash value
	ld	$h1,8($ctx)
	ld	$h2,16($ctx)

	ld	$r0,24($ctx)		# load key
	ld	$r1,32($ctx)
	ld	$rs1,40($ctx)

	dsll	$len,4
	daddu	$len,$inp		# end of buffer
	b	.Loop

.align	4
.Loop:
#if defined(_MIPS_ARCH_MIPS64R6)
	ld	$in0,0($inp)		# load input
	ld	$in1,8($inp)
	beqz	$shr,.Laligned_inp

	ld	$tmp2,16($inp)
# ifdef	MIPSEB
	dsllv	$in0,$in0,$shr
	dsrlv	$tmp3,$in1,$shl
	dsllv	$in1,$in1,$shr
	dsrlv	$tmp2,$tmp2,$shl
# else
	dsrlv	$in0,$in0,$shr
	dsllv	$tmp3,$in1,$shl
	dsrlv	$in1,$in1,$shr
	dsllv	$tmp2,$tmp2,$shl
# endif
	or	$in0,$in0,$tmp3
	or	$in1,$in1,$tmp2
.Laligned_inp:
#else
	ldl	$in0,0+MSB($inp)	# load input
	ldl	$in1,8+MSB($inp)
	ldr	$in0,0+LSB($inp)
	ldr	$in1,8+LSB($inp)
#endif
	daddiu	$inp,16
#ifdef	MIPSEB
# if defined(_MIPS_ARCH_MIPS64R2)
	dsbh	$in0,$in0		# byte swap
	 dsbh	$in1,$in1
	dshd	$in0,$in0
	 dshd	$in1,$in1
# else
	ori	$tmp0,$zero,0xFF
	dsll	$tmp2,$tmp0,32
	or	$tmp0,$tmp2		# 0x000000FF000000FF

	and	$tmp1,$in0,$tmp0	# byte swap
	 and	$tmp3,$in1,$tmp0
	dsrl	$tmp2,$in0,24
	 dsrl	$tmp4,$in1,24
	dsll	$tmp1,24
	 dsll	$tmp3,24
	and	$tmp2,$tmp0
	 and	$tmp4,$tmp0
	dsll	$tmp0,8			# 0x0000FF000000FF00
	or	$tmp1,$tmp2
	 or	$tmp3,$tmp4
	and	$tmp2,$in0,$tmp0
	 and	$tmp4,$in1,$tmp0
	dsrl	$in0,8
	 dsrl	$in1,8
	dsll	$tmp2,8
	 dsll	$tmp4,8
	and	$in0,$tmp0
	 and	$in1,$tmp0
	or	$tmp1,$tmp2
	 or	$tmp3,$tmp4
	or	$in0,$tmp1
	 or	$in1,$tmp3
	dsrl	$tmp1,$in0,32
	 dsrl	$tmp3,$in1,32
	dsll	$in0,32
	 dsll	$in1,32
	or	$in0,$tmp1
	 or	$in1,$tmp3
# endif
#endif
	dsrl	$tmp1,$h2,2		# modulo-scheduled reduction
	andi	$h2,$h2,3
	dsll	$tmp0,$tmp1,2

	daddu	$d0,$h0,$in0		# accumulate input
	 daddu	$tmp1,$tmp0
	sltu	$tmp0,$d0,$h0
	daddu	$d0,$d0,$tmp1		# ... and residue
	sltu	$tmp1,$d0,$tmp1
	daddu	$d1,$h1,$in1
	daddu	$tmp0,$tmp1
	sltu	$tmp1,$d1,$h1
	daddu	$d1,$tmp0

	dmultu	($r0,$d0)		# h0*r0
	 daddu	$d2,$h2,$padbit
	 sltu	$tmp0,$d1,$tmp0
	mflo	($h0,$r0,$d0)
	mfhi	($h1,$r0,$d0)

	dmultu	($rs1,$d1)		# h1*5*r1
	 daddu	$d2,$tmp1
	 daddu	$d2,$tmp0
	mflo	($tmp0,$rs1,$d1)
	mfhi	($tmp1,$rs1,$d1)

	dmultu	($r1,$d0)		# h0*r1
	mflo	($tmp2,$r1,$d0)
	mfhi	($h2,$r1,$d0)
	 daddu	$h0,$tmp0
	 daddu	$h1,$tmp1
	 sltu	$tmp0,$h0,$tmp0

	dmultu	($r0,$d1)		# h1*r0
	 daddu	$h1,$tmp0
	 daddu	$h1,$tmp2
	mflo	($tmp0,$r0,$d1)
	mfhi	($tmp1,$r0,$d1)

	dmultu	($rs1,$d2)		# h2*5*r1
	 sltu	$tmp2,$h1,$tmp2
	 daddu	$h2,$tmp2
	mflo	($tmp2,$rs1,$d2)

	dmultu	($r0,$d2)		# h2*r0
	 daddu	$h1,$tmp0
	 daddu	$h2,$tmp1
	mflo	($tmp3,$r0,$d2)
	 sltu	$tmp0,$h1,$tmp0
	 daddu	$h2,$tmp0

	daddu	$h1,$tmp2
	sltu	$tmp2,$h1,$tmp2
	daddu	$h2,$tmp2
	daddu	$h2,$tmp3

	bne	$inp,$len,.Loop

	sd	$h0,0($ctx)		# store hash value
	sd	$h1,8($ctx)
	sd	$h2,16($ctx)

	.set	noreorder
#if defined(_MIPS_ARCH_MIPS64R6)
	ld	$s7,56($sp)
	ld	$s6,48($sp)
#endif
	ld	$s5,40($sp)		# epilogue
	ld	$s4,32($sp)
___
$code.=<<___ if ($flavour =~ /nubi/i);	# optimize non-nubi epilogue
	ld	$s3,24($sp)
	ld	$s2,16($sp)
	ld	$s1,8($sp)
	ld	$s0,0($sp)
___
$code.=<<___;
	jr	$ra
#if defined(_MIPS_ARCH_MIPS64R6)
	daddu	$sp,8*8
#else
	daddu	$sp,6*8
#endif
.end	poly1305_blocks_internal
___
}
{
my ($ctx,$mac,$nonce) = ($a0,$a1,$a2);

$code.=<<___;
.align	5
.globl	poly1305_emit
.ent	poly1305_emit
poly1305_emit:
	.frame	$sp,0,$ra
	.set	reorder

	ld	$tmp2,16($ctx)
	ld	$tmp0,0($ctx)
	ld	$tmp1,8($ctx)

	li	$in0,-4			# final reduction
	dsrl	$in1,$tmp2,2
	and	$in0,$tmp2
	andi	$tmp2,$tmp2,3
	daddu	$in0,$in1

	daddu	$tmp0,$tmp0,$in0
	sltu	$in1,$tmp0,$in0
	 daddiu	$in0,$tmp0,5		# compare to modulus
	daddu	$tmp1,$tmp1,$in1
	 sltiu	$tmp3,$in0,5
	sltu	$tmp4,$tmp1,$in1
	 daddu	$in1,$tmp1,$tmp3
	daddu	$tmp2,$tmp2,$tmp4
	 sltu	$tmp3,$in1,$tmp3
	 daddu	$tmp2,$tmp2,$tmp3

	dsrl	$tmp2,2			# see if it carried/borrowed
	dsubu	$tmp2,$zero,$tmp2

	xor	$in0,$tmp0
	xor	$in1,$tmp1
	and	$in0,$tmp2
	and	$in1,$tmp2
	xor	$in0,$tmp0
	xor	$in1,$tmp1

	lwu	$tmp0,0($nonce)		# load nonce
	lwu	$tmp1,4($nonce)
	lwu	$tmp2,8($nonce)
	lwu	$tmp3,12($nonce)
	dsll	$tmp1,32
	dsll	$tmp3,32
	or	$tmp0,$tmp1
	or	$tmp2,$tmp3

	daddu	$in0,$tmp0		# accumulate nonce
	daddu	$in1,$tmp2
	sltu	$tmp0,$in0,$tmp0
	daddu	$in1,$tmp0

	dsrl	$tmp0,$in0,8		# write mac value
	dsrl	$tmp1,$in0,16
	dsrl	$tmp2,$in0,24
	sb	$in0,0($mac)
	dsrl	$tmp3,$in0,32
	sb	$tmp0,1($mac)
	dsrl	$tmp0,$in0,40
	sb	$tmp1,2($mac)
	dsrl	$tmp1,$in0,48
	sb	$tmp2,3($mac)
	dsrl	$tmp2,$in0,56
	sb	$tmp3,4($mac)
	dsrl	$tmp3,$in1,8
	sb	$tmp0,5($mac)
	dsrl	$tmp0,$in1,16
	sb	$tmp1,6($mac)
	dsrl	$tmp1,$in1,24
	sb	$tmp2,7($mac)

	sb	$in1,8($mac)
	dsrl	$tmp2,$in1,32
	sb	$tmp3,9($mac)
	dsrl	$tmp3,$in1,40
	sb	$tmp0,10($mac)
	dsrl	$tmp0,$in1,48
	sb	$tmp1,11($mac)
	dsrl	$tmp1,$in1,56
	sb	$tmp2,12($mac)
	sb	$tmp3,13($mac)
	sb	$tmp0,14($mac)
	sb	$tmp1,15($mac)

	jr	$ra
.end	poly1305_emit
.rdata
.asciiz	"Poly1305 for MIPS64, CRYPTOGAMS by \@dot-asm"
.align	2
___
}
}}} else {{{
######################################################################
# 32-bit code path
#

my ($ctx,$inp,$len,$padbit) = ($a0,$a1,$a2,$a3);
my ($in0,$in1,$in2,$in3,$tmp0,$tmp1,$tmp2,$tmp3) =
   ($a4,$a5,$a6,$a7,$at,$t0,$t1,$t2);

$code.=<<___;
#if (defined(_MIPS_ARCH_MIPS32R3) || defined(_MIPS_ARCH_MIPS32R5) || \\
     defined(_MIPS_ARCH_MIPS32R6)) \\
     && !defined(_MIPS_ARCH_MIPS32R2)
# define _MIPS_ARCH_MIPS32R2
#endif

#if defined(_MIPS_ARCH_MIPS32R6)
# define multu(rs,rt)
# define mflo(rd,rs,rt)	mulu	rd,rs,rt
# define mfhi(rd,rs,rt)	muhu	rd,rs,rt
#else
# define multu(rs,rt)	multu	rs,rt
# define mflo(rd,rs,rt)	mflo	rd
# define mfhi(rd,rs,rt)	mfhi	rd
#endif

#ifdef	__KERNEL__
# define poly1305_init   poly1305_init_mips
# define poly1305_blocks poly1305_blocks_mips
# define poly1305_emit   poly1305_emit_mips
#endif

#if defined(__MIPSEB__) && !defined(MIPSEB)
# define MIPSEB
#endif

#ifdef MIPSEB
# define MSB 0
# define LSB 3
#else
# define MSB 3
# define LSB 0
#endif

.text
.set	noat
.set	noreorder

.align	5
.globl	poly1305_init
.ent	poly1305_init
poly1305_init:
	.frame	$sp,0,$ra
	.set	reorder

	sw	$zero,0($ctx)
	sw	$zero,4($ctx)
	sw	$zero,8($ctx)
	sw	$zero,12($ctx)
	sw	$zero,16($ctx)

	beqz	$inp,.Lno_key

#if defined(_MIPS_ARCH_MIPS32R6)
	andi	$tmp0,$inp,3		# $inp % 4
	subu	$inp,$inp,$tmp0		# align $inp
	sll	$tmp0,$tmp0,3		# byte to bit offset
	lw	$in0,0($inp)
	lw	$in1,4($inp)
	lw	$in2,8($inp)
	lw	$in3,12($inp)
	beqz	$tmp0,.Laligned_key

	lw	$tmp2,16($inp)
	subu	$tmp1,$zero,$tmp0
# ifdef	MIPSEB
	sllv	$in0,$in0,$tmp0
	srlv	$tmp3,$in1,$tmp1
	sllv	$in1,$in1,$tmp0
	or	$in0,$in0,$tmp3
	srlv	$tmp3,$in2,$tmp1
	sllv	$in2,$in2,$tmp0
	or	$in1,$in1,$tmp3
	srlv	$tmp3,$in3,$tmp1
	sllv	$in3,$in3,$tmp0
	or	$in2,$in2,$tmp3
	srlv	$tmp2,$tmp2,$tmp1
	or	$in3,$in3,$tmp2
# else
	srlv	$in0,$in0,$tmp0
	sllv	$tmp3,$in1,$tmp1
	srlv	$in1,$in1,$tmp0
	or	$in0,$in0,$tmp3
	sllv	$tmp3,$in2,$tmp1
	srlv	$in2,$in2,$tmp0
	or	$in1,$in1,$tmp3
	sllv	$tmp3,$in3,$tmp1
	srlv	$in3,$in3,$tmp0
	or	$in2,$in2,$tmp3
	sllv	$tmp2,$tmp2,$tmp1
	or	$in3,$in3,$tmp2
# endif
.Laligned_key:
#else
	lwl	$in0,0+MSB($inp)
	lwl	$in1,4+MSB($inp)
	lwl	$in2,8+MSB($inp)
	lwl	$in3,12+MSB($inp)
	lwr	$in0,0+LSB($inp)
	lwr	$in1,4+LSB($inp)
	lwr	$in2,8+LSB($inp)
	lwr	$in3,12+LSB($inp)
#endif
#ifdef	MIPSEB
# if defined(_MIPS_ARCH_MIPS32R2)
	wsbh	$in0,$in0		# byte swap
	wsbh	$in1,$in1
	wsbh	$in2,$in2
	wsbh	$in3,$in3
	rotr	$in0,$in0,16
	rotr	$in1,$in1,16
	rotr	$in2,$in2,16
	rotr	$in3,$in3,16
# else
	srl	$tmp0,$in0,24		# byte swap
	srl	$tmp1,$in0,8
	andi	$tmp2,$in0,0xFF00
	sll	$in0,$in0,24
	andi	$tmp1,0xFF00
	sll	$tmp2,$tmp2,8
	or	$in0,$tmp0
	 srl	$tmp0,$in1,24
	or	$tmp1,$tmp2
	 srl	$tmp2,$in1,8
	or	$in0,$tmp1
	 andi	$tmp1,$in1,0xFF00
	 sll	$in1,$in1,24
	 andi	$tmp2,0xFF00
	 sll	$tmp1,$tmp1,8
	 or	$in1,$tmp0
	srl	$tmp0,$in2,24
	 or	$tmp2,$tmp1
	srl	$tmp1,$in2,8
	 or	$in1,$tmp2
	andi	$tmp2,$in2,0xFF00
	sll	$in2,$in2,24
	andi	$tmp1,0xFF00
	sll	$tmp2,$tmp2,8
	or	$in2,$tmp0
	 srl	$tmp0,$in3,24
	or	$tmp1,$tmp2
	 srl	$tmp2,$in3,8
	or	$in2,$tmp1
	 andi	$tmp1,$in3,0xFF00
	 sll	$in3,$in3,24
	 andi	$tmp2,0xFF00
	 sll	$tmp1,$tmp1,8
	 or	$in3,$tmp0
	 or	$tmp2,$tmp1
	 or	$in3,$tmp2
# endif
#endif
	lui	$tmp0,0x0fff
	ori	$tmp0,0xffff		# 0x0fffffff
	and	$in0,$in0,$tmp0
	subu	$tmp0,3			# 0x0ffffffc
	and	$in1,$in1,$tmp0
	and	$in2,$in2,$tmp0
	and	$in3,$in3,$tmp0

	sw	$in0,20($ctx)
	sw	$in1,24($ctx)
	sw	$in2,28($ctx)
	sw	$in3,32($ctx)

	srl	$tmp1,$in1,2
	srl	$tmp2,$in2,2
	srl	$tmp3,$in3,2
	addu	$in1,$in1,$tmp1		# s1 = r1 + (r1 >> 2)
	addu	$in2,$in2,$tmp2
	addu	$in3,$in3,$tmp3
	sw	$in1,36($ctx)
	sw	$in2,40($ctx)
	sw	$in3,44($ctx)
.Lno_key:
	li	$v0,0
	jr	$ra
.end	poly1305_init
___
{
my $SAVED_REGS_MASK = ($flavour =~ /nubi/i) ? "0x00fff000" : "0x00ff0000";

my ($h0,$h1,$h2,$h3,$h4, $r0,$r1,$r2,$r3, $rs1,$rs2,$rs3) =
   ($s0,$s1,$s2,$s3,$s4, $s5,$s6,$s7,$s8, $s9,$s10,$s11);
my ($d0,$d1,$d2,$d3) =
   ($a4,$a5,$a6,$a7);
my $shr = $t2;		# used on R6
my $one = $t2;		# used on R2

$code.=<<___;
.globl	poly1305_blocks
.align	5
.ent	poly1305_blocks
poly1305_blocks:
	.frame	$sp,16*4,$ra
	.mask	$SAVED_REGS_MASK,-4
	.set	noreorder
	subu	$sp, $sp,4*12
	sw	$s11,4*11($sp)
	sw	$s10,4*10($sp)
	sw	$s9, 4*9($sp)
	sw	$s8, 4*8($sp)
	sw	$s7, 4*7($sp)
	sw	$s6, 4*6($sp)
	sw	$s5, 4*5($sp)
	sw	$s4, 4*4($sp)
___
$code.=<<___ if ($flavour =~ /nubi/i);	# optimize non-nubi prologue
	sw	$s3, 4*3($sp)
	sw	$s2, 4*2($sp)
	sw	$s1, 4*1($sp)
	sw	$s0, 4*0($sp)
___
$code.=<<___;
	.set	reorder

	srl	$len,4			# number of complete blocks
	li	$one,1
	beqz	$len,.Labort

#if defined(_MIPS_ARCH_MIPS32R6)
	andi	$shr,$inp,3
	subu	$inp,$inp,$shr		# align $inp
	sll	$shr,$shr,3		# byte to bit offset
#endif

	lw	$h0,0($ctx)		# load hash value
	lw	$h1,4($ctx)
	lw	$h2,8($ctx)
	lw	$h3,12($ctx)
	lw	$h4,16($ctx)

	lw	$r0,20($ctx)		# load key
	lw	$r1,24($ctx)
	lw	$r2,28($ctx)
	lw	$r3,32($ctx)
	lw	$rs1,36($ctx)
	lw	$rs2,40($ctx)
	lw	$rs3,44($ctx)

	sll	$len,4
	addu	$len,$len,$inp		# end of buffer
	b	.Loop

.align	4
.Loop:
#if defined(_MIPS_ARCH_MIPS32R6)
	lw	$d0,0($inp)		# load input
	lw	$d1,4($inp)
	lw	$d2,8($inp)
	lw	$d3,12($inp)
	beqz	$shr,.Laligned_inp

	lw	$t0,16($inp)
	subu	$t1,$zero,$shr
# ifdef	MIPSEB
	sllv	$d0,$d0,$shr
	srlv	$at,$d1,$t1
	sllv	$d1,$d1,$shr
	or	$d0,$d0,$at
	srlv	$at,$d2,$t1
	sllv	$d2,$d2,$shr
	or	$d1,$d1,$at
	srlv	$at,$d3,$t1
	sllv	$d3,$d3,$shr
	or	$d2,$d2,$at
	srlv	$t0,$t0,$t1
	or	$d3,$d3,$t0
# else
	srlv	$d0,$d0,$shr
	sllv	$at,$d1,$t1
	srlv	$d1,$d1,$shr
	or	$d0,$d0,$at
	sllv	$at,$d2,$t1
	srlv	$d2,$d2,$shr
	or	$d1,$d1,$at
	sllv	$at,$d3,$t1
	srlv	$d3,$d3,$shr
	or	$d2,$d2,$at
	sllv	$t0,$t0,$t1
	or	$d3,$d3,$t0
# endif
.Laligned_inp:
#else
	lwl	$d0,0+MSB($inp)		# load input
	lwl	$d1,4+MSB($inp)
	lwl	$d2,8+MSB($inp)
	lwl	$d3,12+MSB($inp)
	lwr	$d0,0+LSB($inp)
	lwr	$d1,4+LSB($inp)
	lwr	$d2,8+LSB($inp)
	lwr	$d3,12+LSB($inp)
#endif
#ifdef	MIPSEB
# if defined(_MIPS_ARCH_MIPS32R2)
	wsbh	$d0,$d0			# byte swap
	wsbh	$d1,$d1
	wsbh	$d2,$d2
	wsbh	$d3,$d3
	rotr	$d0,$d0,16
	rotr	$d1,$d1,16
	rotr	$d2,$d2,16
	rotr	$d3,$d3,16
# else
	srl	$at,$d0,24		# byte swap
	srl	$t0,$d0,8
	andi	$t1,$d0,0xFF00
	sll	$d0,$d0,24
	andi	$t0,0xFF00
	sll	$t1,$t1,8
	or	$d0,$at
	 srl	$at,$d1,24
	or	$t0,$t1
	 srl	$t1,$d1,8
	or	$d0,$t0
	 andi	$t0,$d1,0xFF00
	 sll	$d1,$d1,24
	 andi	$t1,0xFF00
	 sll	$t0,$t0,8
	 or	$d1,$at
	srl	$at,$d2,24
	 or	$t1,$t0
	srl	$t0,$d2,8
	 or	$d1,$t1
	andi	$t1,$d2,0xFF00
	sll	$d2,$d2,24
	andi	$t0,0xFF00
	sll	$t1,$t1,8
	or	$d2,$at
	 srl	$at,$d3,24
	or	$t0,$t1
	 srl	$t1,$d3,8
	or	$d2,$t0
	 andi	$t0,$d3,0xFF00
	 sll	$d3,$d3,24
	 andi	$t1,0xFF00
	 sll	$t0,$t0,8
	 or	$d3,$at
	 or	$t1,$t0
	 or	$d3,$t1
# endif
#endif
	srl	$t0,$h4,2		# modulo-scheduled reduction
	andi	$h4,$h4,3
	sll	$at,$t0,2

	addu	$d0,$d0,$h0		# accumulate input
	 addu	$t0,$t0,$at
	sltu	$h0,$d0,$h0
	addu	$d0,$d0,$t0		# ... and residue
	sltu	$at,$d0,$t0

	addu	$d1,$d1,$h1
	 addu	$h0,$h0,$at		# carry
	sltu	$h1,$d1,$h1
	addu	$d1,$d1,$h0
	sltu	$h0,$d1,$h0

	addu	$d2,$d2,$h2
	 addu	$h1,$h1,$h0		# carry
	sltu	$h2,$d2,$h2
	addu	$d2,$d2,$h1
	sltu	$h1,$d2,$h1

	addu	$d3,$d3,$h3
	 addu	$h2,$h2,$h1		# carry
	sltu	$h3,$d3,$h3
	addu	$d3,$d3,$h2

#if defined(_MIPS_ARCH_MIPS32R2) && !defined(_MIPS_ARCH_MIPS32R6)
	multu	$r0,$d0			# d0*r0
	 sltu	$h2,$d3,$h2
	maddu	$rs3,$d1		# d1*s3
	 addu	$h3,$h3,$h2		# carry
	maddu	$rs2,$d2		# d2*s2
	 addu	$h4,$h4,$padbit
	maddu	$rs1,$d3		# d3*s1
	 addu	$h4,$h4,$h3
	mfhi	$at
	mflo	$h0

	multu	$r1,$d0			# d0*r1
	maddu	$r0,$d1			# d1*r0
	maddu	$rs3,$d2		# d2*s3
	maddu	$rs2,$d3		# d3*s2
	maddu	$rs1,$h4		# h4*s1
	maddu	$at,$one		# hi*1
	mfhi	$at
	mflo	$h1

	multu	$r2,$d0			# d0*r2
	maddu	$r1,$d1			# d1*r1
	maddu	$r0,$d2			# d2*r0
	maddu	$rs3,$d3		# d3*s3
	maddu	$rs2,$h4		# h4*s2
	maddu	$at,$one		# hi*1
	mfhi	$at
	mflo	$h2

	mul	$t0,$r0,$h4		# h4*r0

	multu	$r3,$d0			# d0*r3
	maddu	$r2,$d1			# d1*r2
	maddu	$r1,$d2			# d2*r1
	maddu	$r0,$d3			# d3*r0
	maddu	$rs3,$h4		# h4*s3
	maddu	$at,$one		# hi*1
	mfhi	$at
	mflo	$h3

	 addiu	$inp,$inp,16

	addu	$h4,$t0,$at
#else
	multu	($r0,$d0)		# d0*r0
	mflo	($h0,$r0,$d0)
	mfhi	($h1,$r0,$d0)

	 sltu	$h2,$d3,$h2
	 addu	$h3,$h3,$h2		# carry

	multu	($rs3,$d1)		# d1*s3
	mflo	($at,$rs3,$d1)
	mfhi	($t0,$rs3,$d1)

	 addu	$h4,$h4,$padbit
	 addiu	$inp,$inp,16
	 addu	$h4,$h4,$h3

	multu	($rs2,$d2)		# d2*s2
	mflo	($a3,$rs2,$d2)
	mfhi	($t1,$rs2,$d2)
	 addu	$h0,$h0,$at
	 addu	$h1,$h1,$t0
	multu	($rs1,$d3)		# d3*s1
	 sltu	$at,$h0,$at
	 addu	$h1,$h1,$at

	mflo	($at,$rs1,$d3)
	mfhi	($t0,$rs1,$d3)
	 addu	$h0,$h0,$a3
	 addu	$h1,$h1,$t1
	multu	($r1,$d0)		# d0*r1
	 sltu	$a3,$h0,$a3
	 addu	$h1,$h1,$a3


	mflo	($a3,$r1,$d0)
	mfhi	($h2,$r1,$d0)
	 addu	$h0,$h0,$at
	 addu	$h1,$h1,$t0
	multu	($r0,$d1)		# d1*r0
	 sltu	$at,$h0,$at
	 addu	$h1,$h1,$at

	mflo	($at,$r0,$d1)
	mfhi	($t0,$r0,$d1)
	 addu	$h1,$h1,$a3
	 sltu	$a3,$h1,$a3
	multu	($rs3,$d2)		# d2*s3
	 addu	$h2,$h2,$a3

	mflo	($a3,$rs3,$d2)
	mfhi	($t1,$rs3,$d2)
	 addu	$h1,$h1,$at
	 addu	$h2,$h2,$t0
	multu	($rs2,$d3)		# d3*s2
	 sltu	$at,$h1,$at
	 addu	$h2,$h2,$at

	mflo	($at,$rs2,$d3)
	mfhi	($t0,$rs2,$d3)
	 addu	$h1,$h1,$a3
	 addu	$h2,$h2,$t1
	multu	($rs1,$h4)		# h4*s1
	 sltu	$a3,$h1,$a3
	 addu	$h2,$h2,$a3

	mflo	($a3,$rs1,$h4)
	 addu	$h1,$h1,$at
	 addu	$h2,$h2,$t0
	multu	($r2,$d0)		# d0*r2
	 sltu	$at,$h1,$at
	 addu	$h2,$h2,$at


	mflo	($at,$r2,$d0)
	mfhi	($h3,$r2,$d0)
	 addu	$h1,$h1,$a3
	 sltu	$a3,$h1,$a3
	multu	($r1,$d1)		# d1*r1
	 addu	$h2,$h2,$a3

	mflo	($a3,$r1,$d1)
	mfhi	($t1,$r1,$d1)
	 addu	$h2,$h2,$at
	 sltu	$at,$h2,$at
	multu	($r0,$d2)		# d2*r0
	 addu	$h3,$h3,$at

	mflo	($at,$r0,$d2)
	mfhi	($t0,$r0,$d2)
	 addu	$h2,$h2,$a3
	 addu	$h3,$h3,$t1
	multu	($rs3,$d3)		# d3*s3
	 sltu	$a3,$h2,$a3
	 addu	$h3,$h3,$a3

	mflo	($a3,$rs3,$d3)
	mfhi	($t1,$rs3,$d3)
	 addu	$h2,$h2,$at
	 addu	$h3,$h3,$t0
	multu	($rs2,$h4)		# h4*s2
	 sltu	$at,$h2,$at
	 addu	$h3,$h3,$at

	mflo	($at,$rs2,$h4)
	 addu	$h2,$h2,$a3
	 addu	$h3,$h3,$t1
	multu	($r3,$d0)		# d0*r3
	 sltu	$a3,$h2,$a3
	 addu	$h3,$h3,$a3


	mflo	($a3,$r3,$d0)
	mfhi	($t1,$r3,$d0)
	 addu	$h2,$h2,$at
	 sltu	$at,$h2,$at
	multu	($r2,$d1)		# d1*r2
	 addu	$h3,$h3,$at

	mflo	($at,$r2,$d1)
	mfhi	($t0,$r2,$d1)
	 addu	$h3,$h3,$a3
	 sltu	$a3,$h3,$a3
	multu	($r0,$d3)		# d3*r0
	 addu	$t1,$t1,$a3

	mflo	($a3,$r0,$d3)
	mfhi	($d3,$r0,$d3)
	 addu	$h3,$h3,$at
	 addu	$t1,$t1,$t0
	multu	($r1,$d2)		# d2*r1
	 sltu	$at,$h3,$at
	 addu	$t1,$t1,$at

	mflo	($at,$r1,$d2)
	mfhi	($t0,$r1,$d2)
	 addu	$h3,$h3,$a3
	 addu	$t1,$t1,$d3
	multu	($rs3,$h4)		# h4*s3
	 sltu	$a3,$h3,$a3
	 addu	$t1,$t1,$a3

	mflo	($a3,$rs3,$h4)
	 addu	$h3,$h3,$at
	 addu	$t1,$t1,$t0
	multu	($r0,$h4)		# h4*r0
	 sltu	$at,$h3,$at
	 addu	$t1,$t1,$at


	mflo	($h4,$r0,$h4)
	 addu	$h3,$h3,$a3
	 sltu	$a3,$h3,$a3
	 addu	$t1,$t1,$a3
	addu	$h4,$h4,$t1

	li	$padbit,1		# if we loop, padbit is 1
#endif
	bne	$inp,$len,.Loop

	sw	$h0,0($ctx)		# store hash value
	sw	$h1,4($ctx)
	sw	$h2,8($ctx)
	sw	$h3,12($ctx)
	sw	$h4,16($ctx)

	.set	noreorder
.Labort:
	lw	$s11,4*11($sp)
	lw	$s10,4*10($sp)
	lw	$s9, 4*9($sp)
	lw	$s8, 4*8($sp)
	lw	$s7, 4*7($sp)
	lw	$s6, 4*6($sp)
	lw	$s5, 4*5($sp)
	lw	$s4, 4*4($sp)
___
$code.=<<___ if ($flavour =~ /nubi/i);	# optimize non-nubi prologue
	lw	$s3, 4*3($sp)
	lw	$s2, 4*2($sp)
	lw	$s1, 4*1($sp)
	lw	$s0, 4*0($sp)
___
$code.=<<___;
	jr	$ra
	addu	$sp,$sp,4*12
.end	poly1305_blocks
___
}
{
my ($ctx,$mac,$nonce,$tmp4) = ($a0,$a1,$a2,$a3);

$code.=<<___;
.align	5
.globl	poly1305_emit
.ent	poly1305_emit
poly1305_emit:
	.frame	$sp,0,$ra
	.set	reorder

	lw	$tmp4,16($ctx)
	lw	$tmp0,0($ctx)
	lw	$tmp1,4($ctx)
	lw	$tmp2,8($ctx)
	lw	$tmp3,12($ctx)

	li	$in0,-4			# final reduction
	srl	$ctx,$tmp4,2
	and	$in0,$in0,$tmp4
	andi	$tmp4,$tmp4,3
	addu	$ctx,$ctx,$in0

	addu	$tmp0,$tmp0,$ctx
	sltu	$ctx,$tmp0,$ctx
	 addiu	$in0,$tmp0,5		# compare to modulus
	addu	$tmp1,$tmp1,$ctx
	 sltiu	$in1,$in0,5
	sltu	$ctx,$tmp1,$ctx
	 addu	$in1,$in1,$tmp1
	addu	$tmp2,$tmp2,$ctx
	 sltu	$in2,$in1,$tmp1
	sltu	$ctx,$tmp2,$ctx
	 addu	$in2,$in2,$tmp2
	addu	$tmp3,$tmp3,$ctx
	 sltu	$in3,$in2,$tmp2
	sltu	$ctx,$tmp3,$ctx
	 addu	$in3,$in3,$tmp3
	addu	$tmp4,$tmp4,$ctx
	 sltu	$ctx,$in3,$tmp3
	 addu	$ctx,$tmp4

	srl	$ctx,2			# see if it carried/borrowed
	subu	$ctx,$zero,$ctx

	xor	$in0,$tmp0
	xor	$in1,$tmp1
	xor	$in2,$tmp2
	xor	$in3,$tmp3
	and	$in0,$ctx
	and	$in1,$ctx
	and	$in2,$ctx
	and	$in3,$ctx
	xor	$in0,$tmp0
	xor	$in1,$tmp1
	xor	$in2,$tmp2
	xor	$in3,$tmp3

	lw	$tmp0,0($nonce)		# load nonce
	lw	$tmp1,4($nonce)
	lw	$tmp2,8($nonce)
	lw	$tmp3,12($nonce)

	addu	$in0,$tmp0		# accumulate nonce
	sltu	$ctx,$in0,$tmp0

	addu	$in1,$tmp1
	sltu	$tmp1,$in1,$tmp1
	addu	$in1,$ctx
	sltu	$ctx,$in1,$ctx
	addu	$ctx,$tmp1

	addu	$in2,$tmp2
	sltu	$tmp2,$in2,$tmp2
	addu	$in2,$ctx
	sltu	$ctx,$in2,$ctx
	addu	$ctx,$tmp2

	addu	$in3,$tmp3
	addu	$in3,$ctx

	srl	$tmp0,$in0,8		# write mac value
	srl	$tmp1,$in0,16
	srl	$tmp2,$in0,24
	sb	$in0, 0($mac)
	sb	$tmp0,1($mac)
	srl	$tmp0,$in1,8
	sb	$tmp1,2($mac)
	srl	$tmp1,$in1,16
	sb	$tmp2,3($mac)
	srl	$tmp2,$in1,24
	sb	$in1, 4($mac)
	sb	$tmp0,5($mac)
	srl	$tmp0,$in2,8
	sb	$tmp1,6($mac)
	srl	$tmp1,$in2,16
	sb	$tmp2,7($mac)
	srl	$tmp2,$in2,24
	sb	$in2, 8($mac)
	sb	$tmp0,9($mac)
	srl	$tmp0,$in3,8
	sb	$tmp1,10($mac)
	srl	$tmp1,$in3,16
	sb	$tmp2,11($mac)
	srl	$tmp2,$in3,24
	sb	$in3, 12($mac)
	sb	$tmp0,13($mac)
	sb	$tmp1,14($mac)
	sb	$tmp2,15($mac)

	jr	$ra
.end	poly1305_emit
.rdata
.asciiz	"Poly1305 for MIPS32, CRYPTOGAMS by \@dot-asm"
.align	2
___
}
}}}

$output=pop and open STDOUT,">$output";
print $code;
close STDOUT;