perlasm: fix symptom-less bugs, missing semicolons and 'my' declarations.
[openssl.git] / crypto / aes / asm / aesni-x86.pl
index fe0cbe0b5e6950d1b06ac9532bd62d3c44e1a60f..14ff2602edd14b977d92e45ee33cebba0d420432 100644 (file)
 # OpenSSL context it's used with Intel engine, but can also be used as
 # drop-in replacement for crypto/aes/asm/aes-586.pl [see below for
 # details].
+#
+# Performance.
+#
+# To start with see corresponding paragraph in aesni-x86_64.pl...
+# Instead of filling table similar to one found there I've chosen to
+# summarize *comparison* results for raw ECB, CTR and CBC benchmarks.
+# The simplified table below represents 32-bit performance relative
+# to 64-bit one in every given point. Ratios vary for different
+# encryption modes, therefore interval values.
+#
+#      16-byte     64-byte     256-byte    1-KB        8-KB
+#      53-67%      67-84%      91-94%      95-98%      97-99.5%
+#
+# Lower ratios for smaller block sizes are perfectly understandable,
+# because function call overhead is higher in 32-bit mode. Largest
+# 8-KB block performance is virtually same: 32-bit code is less than
+# 1% slower for ECB, CBC and CCM, and ~3% slower otherwise.
+
+# January 2011
+#
+# See aesni-x86_64.pl for details. Unlike x86_64 version this module
+# interleaves at most 6 aes[enc|dec] instructions, because there are
+# not enough registers for 8x interleave [which should be optimal for
+# Sandy Bridge]. Actually, performance results for 6x interleave
+# factor presented in aesni-x86_64.pl (except for CTR) are for this
+# module.
+
+# April 2011
+#
+# Add aesni_xts_[en|de]crypt. Westmere spends 1.50 cycles processing
+# one byte out of 8KB with 128-bit key, Sandy Bridge - 1.09.
 
 $PREFIX="aesni";       # if $PREFIX is set to "AES", the script
                        # generates drop-in replacement for
                        # crypto/aes/asm/aes-586.pl:-)
+$inline=1;             # inline _aesni_[en|de]crypt
 
 $0 =~ m/(.*[\/\\])[^\/\\]+$/; $dir=$1;
 push(@INC,"${dir}","${dir}../../perlasm");
@@ -22,153 +54,181 @@ require "x86asm.pl";
 
 &asm_init($ARGV[0],$0);
 
-$movekey = eval($RREFIX eq "aseni" ? "*movaps" : "*movups");
+if ($PREFIX eq "aesni")        { $movekey=\&movups; }
+else                   { $movekey=\&movups; }
 
 $len="eax";
 $rounds="ecx";
 $key="edx";
 $inp="esi";
 $out="edi";
-$rounds_="ebx";
-$key_="ebp";
-
-$inout0="xmm0";
-$inout1="xmm1";
-$inout2="xmm2";
-$rndkey0="xmm3";
-$rndkey1="xmm4";
-$ivec="xmm5";
-$in0="xmm6";
-$in1="xmm7";
-
-sub _aesni_generate1   # folded loop
-{ my $p=shift;
+$rounds_="ebx";        # backup copy for $rounds
+$key_="ebp";   # backup copy for $key
 
-    &function_begin_B("_aesni_${p}rypt1");
-       &$movekey       ($rndkey0,&QWP(0,$key));
-       &$movekey       ($rndkey1,&QWP(16,$key));
-       &lea            ($key,&DWP(16,$key));
-       &pxor           ($inout0,$rndkey0);
-       &dec            ($rounds);
-    &set_label("${p}1_loop",16);
-       eval"&aes${p}   ($inout0,$rndkey1)";
+$rndkey0="xmm0";
+$rndkey1="xmm1";
+$inout0="xmm2";
+$inout1="xmm3";
+$inout2="xmm4";
+$inout3="xmm5";        $in1="xmm5";
+$inout4="xmm6";        $in0="xmm6";
+$inout5="xmm7";        $ivec="xmm7";
+
+# AESNI extenstion
+sub aeskeygenassist
+{ my($dst,$src,$imm)=@_;
+    if ("$dst:$src" =~ /xmm([0-7]):xmm([0-7])/)
+    {  &data_byte(0x66,0x0f,0x3a,0xdf,0xc0|($1<<3)|$2,$imm);   }
+}
+sub aescommon
+{ my($opcodelet,$dst,$src)=@_;
+    if ("$dst:$src" =~ /xmm([0-7]):xmm([0-7])/)
+    {  &data_byte(0x66,0x0f,0x38,$opcodelet,0xc0|($1<<3)|$2);}
+}
+sub aesimc     { aescommon(0xdb,@_); }
+sub aesenc     { aescommon(0xdc,@_); }
+sub aesenclast { aescommon(0xdd,@_); }
+sub aesdec     { aescommon(0xde,@_); }
+sub aesdeclast { aescommon(0xdf,@_); }
+\f
+# Inline version of internal aesni_[en|de]crypt1
+{ my $sn;
+sub aesni_inline_generate1
+{ my ($p,$inout,$ivec)=@_; $inout=$inout0 if (!defined($inout));
+  $sn++;
+
+    &$movekey          ($rndkey0,&QWP(0,$key));
+    &$movekey          ($rndkey1,&QWP(16,$key));
+    &xorps             ($ivec,$rndkey0)        if (defined($ivec));
+    &lea               ($key,&DWP(32,$key));
+    &xorps             ($inout,$ivec)          if (defined($ivec));
+    &xorps             ($inout,$rndkey0)       if (!defined($ivec));
+    &set_label("${p}1_loop_$sn");
+       eval"&aes${p}   ($inout,$rndkey1)";
        &dec            ($rounds);
-       &lea            ($key,&DWP(16,$key));
        &$movekey       ($rndkey1,&QWP(0,$key));
-       &jnz            (&label("${p}1_loop"));
-    eval"&aes${p}last  ($inout0,$rndkey1)";
-    &ret();
-    &function_end_B("_aesni_${p}rypt1");
-}
+       &lea            ($key,&DWP(16,$key));
+    &jnz               (&label("${p}1_loop_$sn"));
+    eval"&aes${p}last  ($inout,$rndkey1)";
+}}
 
 sub aesni_generate1    # fully unrolled loop
-{ my $p=shift;
+{ my ($p,$inout)=@_; $inout=$inout0 if (!defined($inout));
 
     &function_begin_B("_aesni_${p}rypt1");
-       &$movekey       ($rndkey0,&QWP(0,$key));
+       &movups         ($rndkey0,&QWP(0,$key));
        &$movekey       ($rndkey1,&QWP(0x10,$key));
-       &cmp            ($rounds,12);
-       &pxor           ($inout0,$rndkey0);
+       &xorps          ($inout,$rndkey0);
        &$movekey       ($rndkey0,&QWP(0x20,$key));
        &lea            ($key,&DWP(0x30,$key));
+       &cmp            ($rounds,11);
        &jb             (&label("${p}128"));
        &lea            ($key,&DWP(0x20,$key));
        &je             (&label("${p}192"));
        &lea            ($key,&DWP(0x20,$key));
-       eval"&aes${p}   ($inout0,$rndkey1)";
+       eval"&aes${p}   ($inout,$rndkey1)";
        &$movekey       ($rndkey1,&QWP(-0x40,$key));
-       eval"&aes${p}   ($inout0,$rndkey0)";
+       eval"&aes${p}   ($inout,$rndkey0)";
        &$movekey       ($rndkey0,&QWP(-0x30,$key));
     &set_label("${p}192");
-       eval"&aes${p}   ($inout0,$rndkey1)";
+       eval"&aes${p}   ($inout,$rndkey1)";
        &$movekey       ($rndkey1,&QWP(-0x20,$key));
-       eval"&aes${p}   ($inout0,$rndkey0)";
+       eval"&aes${p}   ($inout,$rndkey0)";
        &$movekey       ($rndkey0,&QWP(-0x10,$key));
     &set_label("${p}128");
-       eval"&aes${p}   ($inout0,$rndkey1)";
+       eval"&aes${p}   ($inout,$rndkey1)";
        &$movekey       ($rndkey1,&QWP(0,$key));
-       eval"&aes${p}   ($inout0,$rndkey0)";
+       eval"&aes${p}   ($inout,$rndkey0)";
        &$movekey       ($rndkey0,&QWP(0x10,$key));
-       eval"&aes${p}   ($inout0,$rndkey1)";
+       eval"&aes${p}   ($inout,$rndkey1)";
        &$movekey       ($rndkey1,&QWP(0x20,$key));
-       eval"&aes${p}   ($inout0,$rndkey0)";
+       eval"&aes${p}   ($inout,$rndkey0)";
        &$movekey       ($rndkey0,&QWP(0x30,$key));
-       eval"&aes${p}   ($inout0,$rndkey1)";
+       eval"&aes${p}   ($inout,$rndkey1)";
        &$movekey       ($rndkey1,&QWP(0x40,$key));
-       eval"&aes${p}   ($inout0,$rndkey0)";
+       eval"&aes${p}   ($inout,$rndkey0)";
        &$movekey       ($rndkey0,&QWP(0x50,$key));
-       eval"&aes${p}   ($inout0,$rndkey1)";
+       eval"&aes${p}   ($inout,$rndkey1)";
        &$movekey       ($rndkey1,&QWP(0x60,$key));
-       eval"&aes${p}   ($inout0,$rndkey0)";
+       eval"&aes${p}   ($inout,$rndkey0)";
        &$movekey       ($rndkey0,&QWP(0x70,$key));
-       eval"&aes${p}   ($inout0,$rndkey1)";
-    eval"&aes${p}last  ($inout0,$rndkey0)";
+       eval"&aes${p}   ($inout,$rndkey1)";
+    eval"&aes${p}last  ($inout,$rndkey0)";
     &ret();
     &function_end_B("_aesni_${p}rypt1");
 }
-
-&aesni_generate1("enc");
+\f
 # void $PREFIX_encrypt (const void *inp,void *out,const AES_KEY *key);
+&aesni_generate1("enc") if (!$inline);
 &function_begin_B("${PREFIX}_encrypt");
        &mov    ("eax",&wparam(0));
        &mov    ($key,&wparam(2));
        &movups ($inout0,&QWP(0,"eax"));
        &mov    ($rounds,&DWP(240,$key));
        &mov    ("eax",&wparam(1));
-       &call   ("_aesni_encrypt1");
+       if ($inline)
+       {   &aesni_inline_generate1("enc");     }
+       else
+       {   &call       ("_aesni_encrypt1");    }
        &movups (&QWP(0,"eax"),$inout0);
        &ret    ();
 &function_end_B("${PREFIX}_encrypt");
 
-&aesni_generate1("dec");
 # void $PREFIX_decrypt (const void *inp,void *out,const AES_KEY *key);
+&aesni_generate1("dec") if(!$inline);
 &function_begin_B("${PREFIX}_decrypt");
        &mov    ("eax",&wparam(0));
        &mov    ($key,&wparam(2));
        &movups ($inout0,&QWP(0,"eax"));
        &mov    ($rounds,&DWP(240,$key));
        &mov    ("eax",&wparam(1));
-       &call   ("_aesni_decrypt1");
+       if ($inline)
+       {   &aesni_inline_generate1("dec");     }
+       else
+       {   &call       ("_aesni_decrypt1");    }
        &movups (&QWP(0,"eax"),$inout0);
        &ret    ();
 &function_end_B("${PREFIX}_decrypt");
 
-# _aesni_[en|de]crypt3 are private interfaces, 3 denotes interleave
-# factor. Why 3x? Even though aes[enc|dec] latency is 6, it turned
-# out that it can be scheduled only every *second* cycle. Thus 3x
-# interleave is the one providing optimal utilization, i.e. when
-# subroutine's throughput is virtually same as of non-interleaved
-# subroutine for number of input blocks up to 3. This is why it
-# handles even double-block inputs. Larger interleave factor would
-# perform suboptimally on shorter inputs... 
+# _aesni_[en|de]cryptN are private interfaces, N denotes interleave
+# factor. Why 3x subroutine were originally used in loops? Even though
+# aes[enc|dec] latency was originally 6, it could be scheduled only
+# every *2nd* cycle. Thus 3x interleave was the one providing optimal
+# utilization, i.e. when subroutine's throughput is virtually same as
+# of non-interleaved subroutine [for number of input blocks up to 3].
+# This is why it makes no sense to implement 2x subroutine.
+# aes[enc|dec] latency in next processor generation is 8, but the
+# instructions can be scheduled every cycle. Optimal interleave for
+# new processor is therefore 8x, but it's unfeasible to accommodate it
+# in XMM registers addreassable in 32-bit mode and therefore 6x is
+# used instead...
 
 sub aesni_generate3
 { my $p=shift;
 
     &function_begin_B("_aesni_${p}rypt3");
        &$movekey       ($rndkey0,&QWP(0,$key));
-       &$movekey       ($rndkey1,&QWP(16,$key));
        &shr            ($rounds,1);
+       &$movekey       ($rndkey1,&QWP(16,$key));
        &lea            ($key,&DWP(32,$key));
-       &pxor           ($inout0,$rndkey0);
+       &xorps          ($inout0,$rndkey0);
        &pxor           ($inout1,$rndkey0);
-       &dec            ($rounds);
        &pxor           ($inout2,$rndkey0);
-       &jmp            (&label("${p}3_loop"));
-    &set_label("${p}3_loop",16);
-       eval"&aes${p}   ($inout0,$rndkey1)";
        &$movekey       ($rndkey0,&QWP(0,$key));
+
+    &set_label("${p}3_loop");
+       eval"&aes${p}   ($inout0,$rndkey1)";
        eval"&aes${p}   ($inout1,$rndkey1)";
        &dec            ($rounds);
        eval"&aes${p}   ($inout2,$rndkey1)";
        &$movekey       ($rndkey1,&QWP(16,$key));
        eval"&aes${p}   ($inout0,$rndkey0)";
-       &lea            ($key,&DWP(32,$key));
        eval"&aes${p}   ($inout1,$rndkey0)";
+       &lea            ($key,&DWP(32,$key));
        eval"&aes${p}   ($inout2,$rndkey0)";
+       &$movekey       ($rndkey0,&QWP(0,$key));
        &jnz            (&label("${p}3_loop"));
     eval"&aes${p}      ($inout0,$rndkey1)";
-    &$movekey          ($rndkey0,&QWP(0,$key));
     eval"&aes${p}      ($inout1,$rndkey1)";
     eval"&aes${p}      ($inout2,$rndkey1)";
     eval"&aes${p}last  ($inout0,$rndkey0)";
@@ -177,124 +237,1509 @@ sub aesni_generate3
     &ret();
     &function_end_B("_aesni_${p}rypt3");
 }
+
+# 4x interleave is implemented to improve small block performance,
+# most notably [and naturally] 4 block by ~30%. One can argue that one
+# should have implemented 5x as well, but improvement  would be <20%,
+# so it's not worth it...
+sub aesni_generate4
+{ my $p=shift;
+
+    &function_begin_B("_aesni_${p}rypt4");
+       &$movekey       ($rndkey0,&QWP(0,$key));
+       &$movekey       ($rndkey1,&QWP(16,$key));
+       &shr            ($rounds,1);
+       &lea            ($key,&DWP(32,$key));
+       &xorps          ($inout0,$rndkey0);
+       &pxor           ($inout1,$rndkey0);
+       &pxor           ($inout2,$rndkey0);
+       &pxor           ($inout3,$rndkey0);
+       &$movekey       ($rndkey0,&QWP(0,$key));
+
+    &set_label("${p}4_loop");
+       eval"&aes${p}   ($inout0,$rndkey1)";
+       eval"&aes${p}   ($inout1,$rndkey1)";
+       &dec            ($rounds);
+       eval"&aes${p}   ($inout2,$rndkey1)";
+       eval"&aes${p}   ($inout3,$rndkey1)";
+       &$movekey       ($rndkey1,&QWP(16,$key));
+       eval"&aes${p}   ($inout0,$rndkey0)";
+       eval"&aes${p}   ($inout1,$rndkey0)";
+       &lea            ($key,&DWP(32,$key));
+       eval"&aes${p}   ($inout2,$rndkey0)";
+       eval"&aes${p}   ($inout3,$rndkey0)";
+       &$movekey       ($rndkey0,&QWP(0,$key));
+    &jnz               (&label("${p}4_loop"));
+
+    eval"&aes${p}      ($inout0,$rndkey1)";
+    eval"&aes${p}      ($inout1,$rndkey1)";
+    eval"&aes${p}      ($inout2,$rndkey1)";
+    eval"&aes${p}      ($inout3,$rndkey1)";
+    eval"&aes${p}last  ($inout0,$rndkey0)";
+    eval"&aes${p}last  ($inout1,$rndkey0)";
+    eval"&aes${p}last  ($inout2,$rndkey0)";
+    eval"&aes${p}last  ($inout3,$rndkey0)";
+    &ret();
+    &function_end_B("_aesni_${p}rypt4");
+}
+
+sub aesni_generate6
+{ my $p=shift;
+
+    &function_begin_B("_aesni_${p}rypt6");
+    &static_label("_aesni_${p}rypt6_enter");
+       &$movekey       ($rndkey0,&QWP(0,$key));
+       &shr            ($rounds,1);
+       &$movekey       ($rndkey1,&QWP(16,$key));
+       &lea            ($key,&DWP(32,$key));
+       &xorps          ($inout0,$rndkey0);
+       &pxor           ($inout1,$rndkey0);     # pxor does better here
+       eval"&aes${p}   ($inout0,$rndkey1)";
+       &pxor           ($inout2,$rndkey0);
+       eval"&aes${p}   ($inout1,$rndkey1)";
+       &pxor           ($inout3,$rndkey0);
+       &dec            ($rounds);
+       eval"&aes${p}   ($inout2,$rndkey1)";
+       &pxor           ($inout4,$rndkey0);
+       eval"&aes${p}   ($inout3,$rndkey1)";
+       &pxor           ($inout5,$rndkey0);
+       eval"&aes${p}   ($inout4,$rndkey1)";
+       &$movekey       ($rndkey0,&QWP(0,$key));
+       eval"&aes${p}   ($inout5,$rndkey1)";
+       &jmp            (&label("_aesni_${p}rypt6_enter"));
+
+    &set_label("${p}6_loop",16);
+       eval"&aes${p}   ($inout0,$rndkey1)";
+       eval"&aes${p}   ($inout1,$rndkey1)";
+       &dec            ($rounds);
+       eval"&aes${p}   ($inout2,$rndkey1)";
+       eval"&aes${p}   ($inout3,$rndkey1)";
+       eval"&aes${p}   ($inout4,$rndkey1)";
+       eval"&aes${p}   ($inout5,$rndkey1)";
+    &set_label("_aesni_${p}rypt6_enter",16);
+       &$movekey       ($rndkey1,&QWP(16,$key));
+       eval"&aes${p}   ($inout0,$rndkey0)";
+       eval"&aes${p}   ($inout1,$rndkey0)";
+       &lea            ($key,&DWP(32,$key));
+       eval"&aes${p}   ($inout2,$rndkey0)";
+       eval"&aes${p}   ($inout3,$rndkey0)";
+       eval"&aes${p}   ($inout4,$rndkey0)";
+       eval"&aes${p}   ($inout5,$rndkey0)";
+       &$movekey       ($rndkey0,&QWP(0,$key));
+    &jnz               (&label("${p}6_loop"));
+
+    eval"&aes${p}      ($inout0,$rndkey1)";
+    eval"&aes${p}      ($inout1,$rndkey1)";
+    eval"&aes${p}      ($inout2,$rndkey1)";
+    eval"&aes${p}      ($inout3,$rndkey1)";
+    eval"&aes${p}      ($inout4,$rndkey1)";
+    eval"&aes${p}      ($inout5,$rndkey1)";
+    eval"&aes${p}last  ($inout0,$rndkey0)";
+    eval"&aes${p}last  ($inout1,$rndkey0)";
+    eval"&aes${p}last  ($inout2,$rndkey0)";
+    eval"&aes${p}last  ($inout3,$rndkey0)";
+    eval"&aes${p}last  ($inout4,$rndkey0)";
+    eval"&aes${p}last  ($inout5,$rndkey0)";
+    &ret();
+    &function_end_B("_aesni_${p}rypt6");
+}
 &aesni_generate3("enc") if ($PREFIX eq "aesni");
 &aesni_generate3("dec");
-
+&aesni_generate4("enc") if ($PREFIX eq "aesni");
+&aesni_generate4("dec");
+&aesni_generate6("enc") if ($PREFIX eq "aesni");
+&aesni_generate6("dec");
+\f
 if ($PREFIX eq "aesni") {
+######################################################################
 # void aesni_ecb_encrypt (const void *in, void *out,
 #                         size_t length, const AES_KEY *key,
 #                         int enc);
-
 &function_begin("aesni_ecb_encrypt");
        &mov    ($inp,&wparam(0));
        &mov    ($out,&wparam(1));
        &mov    ($len,&wparam(2));
        &mov    ($key,&wparam(3));
-       &mov    ($rounds,&wparam(4));
-       &cmp    ($len,16);
-       &jb     (&label("ecb_ret"));
+       &mov    ($rounds_,&wparam(4));
        &and    ($len,-16);
-       &test   ($rounds,$rounds)
+       &jz     (&label("ecb_ret"));
        &mov    ($rounds,&DWP(240,$key));
+       &test   ($rounds_,$rounds_);
+       &jz     (&label("ecb_decrypt"));
+
        &mov    ($key_,$key);           # backup $key
        &mov    ($rounds_,$rounds);     # backup $rounds
-       &jz     (&label("ecb_decrypt"));
+       &cmp    ($len,0x60);
+       &jb     (&label("ecb_enc_tail"));
 
-       &sub    ($len,0x30);
-       &jc     (&label("ecb_enc_tail"));
-       jmp     (&label("ecb_enc_loop3"));
+       &movdqu ($inout0,&QWP(0,$inp));
+       &movdqu ($inout1,&QWP(0x10,$inp));
+       &movdqu ($inout2,&QWP(0x20,$inp));
+       &movdqu ($inout3,&QWP(0x30,$inp));
+       &movdqu ($inout4,&QWP(0x40,$inp));
+       &movdqu ($inout5,&QWP(0x50,$inp));
+       &lea    ($inp,&DWP(0x60,$inp));
+       &sub    ($len,0x60);
+       &jmp    (&label("ecb_enc_loop6_enter"));
 
-&set_label("ecb_enc_loop3",16);
-       &movups ($inout0,&QWP(0,$inp));
-       &movups ($inout1,&QWP(0x10,$inp));
-       &movups ($inout2,&QWP(0x20,$inp));
-       &lea    ($inp,&DWP(0x30,$inp));
-       &call   ("_aesni_encrypt3");
+&set_label("ecb_enc_loop6",16);
        &movups (&QWP(0,$out),$inout0);
-       &sub    ($len,0x30);
+       &movdqu ($inout0,&QWP(0,$inp));
        &movups (&QWP(0x10,$out),$inout1);
-       &mov    ($key,$key_);           # restore $key
+       &movdqu ($inout1,&QWP(0x10,$inp));
        &movups (&QWP(0x20,$out),$inout2);
+       &movdqu ($inout2,&QWP(0x20,$inp));
+       &movups (&QWP(0x30,$out),$inout3);
+       &movdqu ($inout3,&QWP(0x30,$inp));
+       &movups (&QWP(0x40,$out),$inout4);
+       &movdqu ($inout4,&QWP(0x40,$inp));
+       &movups (&QWP(0x50,$out),$inout5);
+       &lea    ($out,&DWP(0x60,$out));
+       &movdqu ($inout5,&QWP(0x50,$inp));
+       &lea    ($inp,&DWP(0x60,$inp));
+&set_label("ecb_enc_loop6_enter");
+
+       &call   ("_aesni_encrypt6");
+
+       &mov    ($key,$key_);           # restore $key
        &mov    ($rounds,$rounds_);     # restore $rounds
-       &lea    ($out,&DWP(0x30,$out));
-       &jnc    (&label("ecb_enc_loop3"));
+       &sub    ($len,0x60);
+       &jnc    (&label("ecb_enc_loop6"));
 
-&set_label("ecb_enc_tail");
-       &add    ($len,0x30);
+       &movups (&QWP(0,$out),$inout0);
+       &movups (&QWP(0x10,$out),$inout1);
+       &movups (&QWP(0x20,$out),$inout2);
+       &movups (&QWP(0x30,$out),$inout3);
+       &movups (&QWP(0x40,$out),$inout4);
+       &movups (&QWP(0x50,$out),$inout5);
+       &lea    ($out,&DWP(0x60,$out));
+       &add    ($len,0x60);
        &jz     (&label("ecb_ret"));
 
-       &cmp    ($len,0x10);
+&set_label("ecb_enc_tail");
        &movups ($inout0,&QWP(0,$inp));
-       je      (&label("ecb_enc_one"));
+       &cmp    ($len,0x20);
+       &jb     (&label("ecb_enc_one"));
        &movups ($inout1,&QWP(0x10,$inp));
-       &call   ("_aesni_encrypt3");
+       &je     (&label("ecb_enc_two"));
+       &movups ($inout2,&QWP(0x20,$inp));
+       &cmp    ($len,0x40);
+       &jb     (&label("ecb_enc_three"));
+       &movups ($inout3,&QWP(0x30,$inp));
+       &je     (&label("ecb_enc_four"));
+       &movups ($inout4,&QWP(0x40,$inp));
+       &xorps  ($inout5,$inout5);
+       &call   ("_aesni_encrypt6");
        &movups (&QWP(0,$out),$inout0);
        &movups (&QWP(0x10,$out),$inout1);
+       &movups (&QWP(0x20,$out),$inout2);
+       &movups (&QWP(0x30,$out),$inout3);
+       &movups (&QWP(0x40,$out),$inout4);
        jmp     (&label("ecb_ret"));
 
 &set_label("ecb_enc_one",16);
-       &call   ("_aesni_encrypt1");
+       if ($inline)
+       {   &aesni_inline_generate1("enc");     }
+       else
+       {   &call       ("_aesni_encrypt1");    }
+       &movups (&QWP(0,$out),$inout0);
+       &jmp    (&label("ecb_ret"));
+
+&set_label("ecb_enc_two",16);
+       &xorps  ($inout2,$inout2);
+       &call   ("_aesni_encrypt3");
+       &movups (&QWP(0,$out),$inout0);
+       &movups (&QWP(0x10,$out),$inout1);
+       &jmp    (&label("ecb_ret"));
+
+&set_label("ecb_enc_three",16);
+       &call   ("_aesni_encrypt3");
        &movups (&QWP(0,$out),$inout0);
+       &movups (&QWP(0x10,$out),$inout1);
+       &movups (&QWP(0x20,$out),$inout2);
        &jmp    (&label("ecb_ret"));
 
+&set_label("ecb_enc_four",16);
+       &call   ("_aesni_encrypt4");
+       &movups (&QWP(0,$out),$inout0);
+       &movups (&QWP(0x10,$out),$inout1);
+       &movups (&QWP(0x20,$out),$inout2);
+       &movups (&QWP(0x30,$out),$inout3);
+       &jmp    (&label("ecb_ret"));
+######################################################################
 &set_label("ecb_decrypt",16);
-       &sub    ($len,0x30);
-       &jc     (&label("ecb_dec_tail"));
-       jmp     (&label("ecb_dec_loop3"));
+       &mov    ($key_,$key);           # backup $key
+       &mov    ($rounds_,$rounds);     # backup $rounds
+       &cmp    ($len,0x60);
+       &jb     (&label("ecb_dec_tail"));
 
-&set_label("ecb_dec_loop3",16);
-       &movups ($inout0,&QWP(0,$inp));
-       &movups ($inout1,&QWP(0x10,$inp));
-       &movups ($inout2,&QWP(0x20,$inp));
-       &call   ("_aesni_decrypt3");
+       &movdqu ($inout0,&QWP(0,$inp));
+       &movdqu ($inout1,&QWP(0x10,$inp));
+       &movdqu ($inout2,&QWP(0x20,$inp));
+       &movdqu ($inout3,&QWP(0x30,$inp));
+       &movdqu ($inout4,&QWP(0x40,$inp));
+       &movdqu ($inout5,&QWP(0x50,$inp));
+       &lea    ($inp,&DWP(0x60,$inp));
+       &sub    ($len,0x60);
+       &jmp    (&label("ecb_dec_loop6_enter"));
+
+&set_label("ecb_dec_loop6",16);
        &movups (&QWP(0,$out),$inout0);
-       &sub    ($len,0x30);
-       &lea    ($inp,&DWP(0x30,$inp));
+       &movdqu ($inout0,&QWP(0,$inp));
        &movups (&QWP(0x10,$out),$inout1);
-       &mov    ($key,$key_);           # restore $key
+       &movdqu ($inout1,&QWP(0x10,$inp));
        &movups (&QWP(0x20,$out),$inout2);
+       &movdqu ($inout2,&QWP(0x20,$inp));
+       &movups (&QWP(0x30,$out),$inout3);
+       &movdqu ($inout3,&QWP(0x30,$inp));
+       &movups (&QWP(0x40,$out),$inout4);
+       &movdqu ($inout4,&QWP(0x40,$inp));
+       &movups (&QWP(0x50,$out),$inout5);
+       &lea    ($out,&DWP(0x60,$out));
+       &movdqu ($inout5,&QWP(0x50,$inp));
+       &lea    ($inp,&DWP(0x60,$inp));
+&set_label("ecb_dec_loop6_enter");
+
+       &call   ("_aesni_decrypt6");
+
+       &mov    ($key,$key_);           # restore $key
        &mov    ($rounds,$rounds_);     # restore $rounds
-       &lea    ($out,&DWP(0x30,$out));
-       &jnc    (&label("ecb_dec_loop3"));
+       &sub    ($len,0x60);
+       &jnc    (&label("ecb_dec_loop6"));
 
-&set_label("ecb_dec_tail");
-       &add    ($len,0x30);
+       &movups (&QWP(0,$out),$inout0);
+       &movups (&QWP(0x10,$out),$inout1);
+       &movups (&QWP(0x20,$out),$inout2);
+       &movups (&QWP(0x30,$out),$inout3);
+       &movups (&QWP(0x40,$out),$inout4);
+       &movups (&QWP(0x50,$out),$inout5);
+       &lea    ($out,&DWP(0x60,$out));
+       &add    ($len,0x60);
        &jz     (&label("ecb_ret"));
 
-       &cmp    ($len,0x10);
+&set_label("ecb_dec_tail");
        &movups ($inout0,&QWP(0,$inp));
-       je      (&label("ecb_dec_one"));
+       &cmp    ($len,0x20);
+       &jb     (&label("ecb_dec_one"));
        &movups ($inout1,&QWP(0x10,$inp));
-       &call   ("_aesni_decrypt3");
+       &je     (&label("ecb_dec_two"));
+       &movups ($inout2,&QWP(0x20,$inp));
+       &cmp    ($len,0x40);
+       &jb     (&label("ecb_dec_three"));
+       &movups ($inout3,&QWP(0x30,$inp));
+       &je     (&label("ecb_dec_four"));
+       &movups ($inout4,&QWP(0x40,$inp));
+       &xorps  ($inout5,$inout5);
+       &call   ("_aesni_decrypt6");
        &movups (&QWP(0,$out),$inout0);
        &movups (&QWP(0x10,$out),$inout1);
-       jmp     (&label("ecb_ret"));
+       &movups (&QWP(0x20,$out),$inout2);
+       &movups (&QWP(0x30,$out),$inout3);
+       &movups (&QWP(0x40,$out),$inout4);
+       &jmp    (&label("ecb_ret"));
 
 &set_label("ecb_dec_one",16);
-       &call   ("_aesni_decrypt1");
+       if ($inline)
+       {   &aesni_inline_generate1("dec");     }
+       else
+       {   &call       ("_aesni_decrypt1");    }
+       &movups (&QWP(0,$out),$inout0);
+       &jmp    (&label("ecb_ret"));
+
+&set_label("ecb_dec_two",16);
+       &xorps  ($inout2,$inout2);
+       &call   ("_aesni_decrypt3");
+       &movups (&QWP(0,$out),$inout0);
+       &movups (&QWP(0x10,$out),$inout1);
+       &jmp    (&label("ecb_ret"));
+
+&set_label("ecb_dec_three",16);
+       &call   ("_aesni_decrypt3");
        &movups (&QWP(0,$out),$inout0);
+       &movups (&QWP(0x10,$out),$inout1);
+       &movups (&QWP(0x20,$out),$inout2);
+       &jmp    (&label("ecb_ret"));
+
+&set_label("ecb_dec_four",16);
+       &call   ("_aesni_decrypt4");
+       &movups (&QWP(0,$out),$inout0);
+       &movups (&QWP(0x10,$out),$inout1);
+       &movups (&QWP(0x20,$out),$inout2);
+       &movups (&QWP(0x30,$out),$inout3);
 
 &set_label("ecb_ret");
 &function_end("aesni_ecb_encrypt");
+\f
+######################################################################
+# void aesni_ccm64_[en|de]crypt_blocks (const void *in, void *out,
+#                         size_t blocks, const AES_KEY *key,
+#                         const char *ivec,char *cmac);
+#
+# Handles only complete blocks, operates on 64-bit counter and
+# does not update *ivec! Nor does it finalize CMAC value
+# (see engine/eng_aesni.c for details)
+#
+{ my $cmac=$inout1;
+&function_begin("aesni_ccm64_encrypt_blocks");
+       &mov    ($inp,&wparam(0));
+       &mov    ($out,&wparam(1));
+       &mov    ($len,&wparam(2));
+       &mov    ($key,&wparam(3));
+       &mov    ($rounds_,&wparam(4));
+       &mov    ($rounds,&wparam(5));
+       &mov    ($key_,"esp");
+       &sub    ("esp",60);
+       &and    ("esp",-16);                    # align stack
+       &mov    (&DWP(48,"esp"),$key_);
+
+       &movdqu ($ivec,&QWP(0,$rounds_));       # load ivec
+       &movdqu ($cmac,&QWP(0,$rounds));        # load cmac
+       &mov    ($rounds,&DWP(240,$key));
+
+       # compose byte-swap control mask for pshufb on stack
+       &mov    (&DWP(0,"esp"),0x0c0d0e0f);
+       &mov    (&DWP(4,"esp"),0x08090a0b);
+       &mov    (&DWP(8,"esp"),0x04050607);
+       &mov    (&DWP(12,"esp"),0x00010203);
+
+       # compose counter increment vector on stack
+       &mov    ($rounds_,1);
+       &xor    ($key_,$key_);
+       &mov    (&DWP(16,"esp"),$rounds_);
+       &mov    (&DWP(20,"esp"),$key_);
+       &mov    (&DWP(24,"esp"),$key_);
+       &mov    (&DWP(28,"esp"),$key_);
+
+       &shr    ($rounds,1);
+       &lea    ($key_,&DWP(0,$key));
+       &movdqa ($inout3,&QWP(0,"esp"));
+       &movdqa ($inout0,$ivec);
+       &mov    ($rounds_,$rounds);
+       &pshufb ($ivec,$inout3);
+
+&set_label("ccm64_enc_outer");
+       &$movekey       ($rndkey0,&QWP(0,$key_));
+       &mov            ($rounds,$rounds_);
+       &movups         ($in0,&QWP(0,$inp));
+
+       &xorps          ($inout0,$rndkey0);
+       &$movekey       ($rndkey1,&QWP(16,$key_));
+       &xorps          ($rndkey0,$in0);
+       &lea            ($key,&DWP(32,$key_));
+       &xorps          ($cmac,$rndkey0);               # cmac^=inp
+       &$movekey       ($rndkey0,&QWP(0,$key));
+
+&set_label("ccm64_enc2_loop");
+       &aesenc         ($inout0,$rndkey1);
+       &dec            ($rounds);
+       &aesenc         ($cmac,$rndkey1);
+       &$movekey       ($rndkey1,&QWP(16,$key));
+       &aesenc         ($inout0,$rndkey0);
+       &lea            ($key,&DWP(32,$key));
+       &aesenc         ($cmac,$rndkey0);
+       &$movekey       ($rndkey0,&QWP(0,$key));
+       &jnz            (&label("ccm64_enc2_loop"));
+       &aesenc         ($inout0,$rndkey1);
+       &aesenc         ($cmac,$rndkey1);
+       &paddq          ($ivec,&QWP(16,"esp"));
+       &aesenclast     ($inout0,$rndkey0);
+       &aesenclast     ($cmac,$rndkey0);
+
+       &dec    ($len);
+       &lea    ($inp,&DWP(16,$inp));
+       &xorps  ($in0,$inout0);                 # inp^=E(ivec)
+       &movdqa ($inout0,$ivec);
+       &movups (&QWP(0,$out),$in0);            # save output
+       &lea    ($out,&DWP(16,$out));
+       &pshufb ($inout0,$inout3);
+       &jnz    (&label("ccm64_enc_outer"));
+
+       &mov    ("esp",&DWP(48,"esp"));
+       &mov    ($out,&wparam(5));
+       &movups (&QWP(0,$out),$cmac);
+&function_end("aesni_ccm64_encrypt_blocks");
+
+&function_begin("aesni_ccm64_decrypt_blocks");
+       &mov    ($inp,&wparam(0));
+       &mov    ($out,&wparam(1));
+       &mov    ($len,&wparam(2));
+       &mov    ($key,&wparam(3));
+       &mov    ($rounds_,&wparam(4));
+       &mov    ($rounds,&wparam(5));
+       &mov    ($key_,"esp");
+       &sub    ("esp",60);
+       &and    ("esp",-16);                    # align stack
+       &mov    (&DWP(48,"esp"),$key_);
+
+       &movdqu ($ivec,&QWP(0,$rounds_));       # load ivec
+       &movdqu ($cmac,&QWP(0,$rounds));        # load cmac
+       &mov    ($rounds,&DWP(240,$key));
+
+       # compose byte-swap control mask for pshufb on stack
+       &mov    (&DWP(0,"esp"),0x0c0d0e0f);
+       &mov    (&DWP(4,"esp"),0x08090a0b);
+       &mov    (&DWP(8,"esp"),0x04050607);
+       &mov    (&DWP(12,"esp"),0x00010203);
+
+       # compose counter increment vector on stack
+       &mov    ($rounds_,1);
+       &xor    ($key_,$key_);
+       &mov    (&DWP(16,"esp"),$rounds_);
+       &mov    (&DWP(20,"esp"),$key_);
+       &mov    (&DWP(24,"esp"),$key_);
+       &mov    (&DWP(28,"esp"),$key_);
+
+       &movdqa ($inout3,&QWP(0,"esp"));        # bswap mask
+       &movdqa ($inout0,$ivec);
+
+       &mov    ($key_,$key);
+       &mov    ($rounds_,$rounds);
+
+       &pshufb ($ivec,$inout3);
+       if ($inline)
+       {   &aesni_inline_generate1("enc");     }
+       else
+       {   &call       ("_aesni_encrypt1");    }
+       &movups ($in0,&QWP(0,$inp));            # load inp
+       &paddq  ($ivec,&QWP(16,"esp"));
+       &lea    ($inp,&QWP(16,$inp));
+       &jmp    (&label("ccm64_dec_outer"));
+
+&set_label("ccm64_dec_outer",16);
+       &xorps  ($in0,$inout0);                 # inp ^= E(ivec)
+       &movdqa ($inout0,$ivec);
+       &mov    ($rounds,$rounds_);
+       &movups (&QWP(0,$out),$in0);            # save output
+       &lea    ($out,&DWP(16,$out));
+       &pshufb ($inout0,$inout3);
+
+       &sub    ($len,1);
+       &jz     (&label("ccm64_dec_break"));
+
+       &$movekey       ($rndkey0,&QWP(0,$key_));
+       &shr            ($rounds,1);
+       &$movekey       ($rndkey1,&QWP(16,$key_));
+       &xorps          ($in0,$rndkey0);
+       &lea            ($key,&DWP(32,$key_));
+       &xorps          ($inout0,$rndkey0);
+       &xorps          ($cmac,$in0);           # cmac^=out
+       &$movekey       ($rndkey0,&QWP(0,$key));
+
+&set_label("ccm64_dec2_loop");
+       &aesenc         ($inout0,$rndkey1);
+       &dec            ($rounds);
+       &aesenc         ($cmac,$rndkey1);
+       &$movekey       ($rndkey1,&QWP(16,$key));
+       &aesenc         ($inout0,$rndkey0);
+       &lea            ($key,&DWP(32,$key));
+       &aesenc         ($cmac,$rndkey0);
+       &$movekey       ($rndkey0,&QWP(0,$key));
+       &jnz            (&label("ccm64_dec2_loop"));
+       &movups         ($in0,&QWP(0,$inp));    # load inp
+       &paddq          ($ivec,&QWP(16,"esp"));
+       &aesenc         ($inout0,$rndkey1);
+       &aesenc         ($cmac,$rndkey1);
+       &lea            ($inp,&QWP(16,$inp));
+       &aesenclast     ($inout0,$rndkey0);
+       &aesenclast     ($cmac,$rndkey0);
+       &jmp    (&label("ccm64_dec_outer"));
+
+&set_label("ccm64_dec_break",16);
+       &mov    ($key,$key_);
+       if ($inline)
+       {   &aesni_inline_generate1("enc",$cmac,$in0);  }
+       else
+       {   &call       ("_aesni_encrypt1",$cmac);      }
+
+       &mov    ("esp",&DWP(48,"esp"));
+       &mov    ($out,&wparam(5));
+       &movups (&QWP(0,$out),$cmac);
+&function_end("aesni_ccm64_decrypt_blocks");
 }
+\f
+######################################################################
+# void aesni_ctr32_encrypt_blocks (const void *in, void *out,
+#                         size_t blocks, const AES_KEY *key,
+#                         const char *ivec);
+#
+# Handles only complete blocks, operates on 32-bit counter and
+# does not update *ivec! (see engine/eng_aesni.c for details)
+#
+# stack layout:
+#      0       pshufb mask
+#      16      vector addend: 0,6,6,6
+#      32      counter-less ivec
+#      48      1st triplet of counter vector
+#      64      2nd triplet of counter vector
+#      80      saved %esp
+
+&function_begin("aesni_ctr32_encrypt_blocks");
+       &mov    ($inp,&wparam(0));
+       &mov    ($out,&wparam(1));
+       &mov    ($len,&wparam(2));
+       &mov    ($key,&wparam(3));
+       &mov    ($rounds_,&wparam(4));
+       &mov    ($key_,"esp");
+       &sub    ("esp",88);
+       &and    ("esp",-16);                    # align stack
+       &mov    (&DWP(80,"esp"),$key_);
+
+       &cmp    ($len,1);
+       &je     (&label("ctr32_one_shortcut"));
+
+       &movdqu ($inout5,&QWP(0,$rounds_));     # load ivec
+
+       # compose byte-swap control mask for pshufb on stack
+       &mov    (&DWP(0,"esp"),0x0c0d0e0f);
+       &mov    (&DWP(4,"esp"),0x08090a0b);
+       &mov    (&DWP(8,"esp"),0x04050607);
+       &mov    (&DWP(12,"esp"),0x00010203);
+
+       # compose counter increment vector on stack
+       &mov    ($rounds,6);
+       &xor    ($key_,$key_);
+       &mov    (&DWP(16,"esp"),$rounds);
+       &mov    (&DWP(20,"esp"),$rounds);
+       &mov    (&DWP(24,"esp"),$rounds);
+       &mov    (&DWP(28,"esp"),$key_);
+
+       &pextrd ($rounds_,$inout5,3);           # pull 32-bit counter
+       &pinsrd ($inout5,$key_,3);              # wipe 32-bit counter
+
+       &mov    ($rounds,&DWP(240,$key));       # key->rounds
+
+       # compose 2 vectors of 3x32-bit counters
+       &bswap  ($rounds_);
+       &pxor   ($rndkey1,$rndkey1);
+       &pxor   ($rndkey0,$rndkey0);
+       &movdqa ($inout0,&QWP(0,"esp"));        # load byte-swap mask
+       &pinsrd ($rndkey1,$rounds_,0);
+       &lea    ($key_,&DWP(3,$rounds_));
+       &pinsrd ($rndkey0,$key_,0);
+       &inc    ($rounds_);
+       &pinsrd ($rndkey1,$rounds_,1);
+       &inc    ($key_);
+       &pinsrd ($rndkey0,$key_,1);
+       &inc    ($rounds_);
+       &pinsrd ($rndkey1,$rounds_,2);
+       &inc    ($key_);
+       &pinsrd ($rndkey0,$key_,2);
+       &movdqa (&QWP(48,"esp"),$rndkey1);      # save 1st triplet
+       &pshufb ($rndkey1,$inout0);             # byte swap
+       &movdqa (&QWP(64,"esp"),$rndkey0);      # save 2nd triplet
+       &pshufb ($rndkey0,$inout0);             # byte swap
+
+       &pshufd ($inout0,$rndkey1,3<<6);        # place counter to upper dword
+       &pshufd ($inout1,$rndkey1,2<<6);
+       &cmp    ($len,6);
+       &jb     (&label("ctr32_tail"));
+       &movdqa (&QWP(32,"esp"),$inout5);       # save counter-less ivec
+       &shr    ($rounds,1);
+       &mov    ($key_,$key);                   # backup $key
+       &mov    ($rounds_,$rounds);             # backup $rounds
+       &sub    ($len,6);
+       &jmp    (&label("ctr32_loop6"));
+
+&set_label("ctr32_loop6",16);
+       &pshufd ($inout2,$rndkey1,1<<6);
+       &movdqa ($rndkey1,&QWP(32,"esp"));      # pull counter-less ivec
+       &pshufd ($inout3,$rndkey0,3<<6);
+       &por    ($inout0,$rndkey1);             # merge counter-less ivec
+       &pshufd ($inout4,$rndkey0,2<<6);
+       &por    ($inout1,$rndkey1);
+       &pshufd ($inout5,$rndkey0,1<<6);
+       &por    ($inout2,$rndkey1);
+       &por    ($inout3,$rndkey1);
+       &por    ($inout4,$rndkey1);
+       &por    ($inout5,$rndkey1);
+
+       # inlining _aesni_encrypt6's prologue gives ~4% improvement...
+       &$movekey       ($rndkey0,&QWP(0,$key_));
+       &$movekey       ($rndkey1,&QWP(16,$key_));
+       &lea            ($key,&DWP(32,$key_));
+       &dec            ($rounds);
+       &pxor           ($inout0,$rndkey0);
+       &pxor           ($inout1,$rndkey0);
+       &aesenc         ($inout0,$rndkey1);
+       &pxor           ($inout2,$rndkey0);
+       &aesenc         ($inout1,$rndkey1);
+       &pxor           ($inout3,$rndkey0);
+       &aesenc         ($inout2,$rndkey1);
+       &pxor           ($inout4,$rndkey0);
+       &aesenc         ($inout3,$rndkey1);
+       &pxor           ($inout5,$rndkey0);
+       &aesenc         ($inout4,$rndkey1);
+       &$movekey       ($rndkey0,&QWP(0,$key));
+       &aesenc         ($inout5,$rndkey1);
+
+       &call           (&label("_aesni_encrypt6_enter"));
+
+       &movups ($rndkey1,&QWP(0,$inp));
+       &movups ($rndkey0,&QWP(0x10,$inp));
+       &xorps  ($inout0,$rndkey1);
+       &movups ($rndkey1,&QWP(0x20,$inp));
+       &xorps  ($inout1,$rndkey0);
+       &movups (&QWP(0,$out),$inout0);
+       &movdqa ($rndkey0,&QWP(16,"esp"));      # load increment
+       &xorps  ($inout2,$rndkey1);
+       &movdqa ($rndkey1,&QWP(48,"esp"));      # load 1st triplet
+       &movups (&QWP(0x10,$out),$inout1);
+       &movups (&QWP(0x20,$out),$inout2);
+
+       &paddd  ($rndkey1,$rndkey0);            # 1st triplet increment
+       &paddd  ($rndkey0,&QWP(64,"esp"));      # 2nd triplet increment
+       &movdqa ($inout0,&QWP(0,"esp"));        # load byte swap mask
+
+       &movups ($inout1,&QWP(0x30,$inp));
+       &movups ($inout2,&QWP(0x40,$inp));
+       &xorps  ($inout3,$inout1);
+       &movups ($inout1,&QWP(0x50,$inp));
+       &lea    ($inp,&DWP(0x60,$inp));
+       &movdqa (&QWP(48,"esp"),$rndkey1);      # save 1st triplet
+       &pshufb ($rndkey1,$inout0);             # byte swap
+       &xorps  ($inout4,$inout2);
+       &movups (&QWP(0x30,$out),$inout3);
+       &xorps  ($inout5,$inout1);
+       &movdqa (&QWP(64,"esp"),$rndkey0);      # save 2nd triplet
+       &pshufb ($rndkey0,$inout0);             # byte swap
+       &movups (&QWP(0x40,$out),$inout4);
+       &pshufd ($inout0,$rndkey1,3<<6);
+       &movups (&QWP(0x50,$out),$inout5);
+       &lea    ($out,&DWP(0x60,$out));
+
+       &mov    ($rounds,$rounds_);
+       &pshufd ($inout1,$rndkey1,2<<6);
+       &sub    ($len,6);
+       &jnc    (&label("ctr32_loop6"));
+
+       &add    ($len,6);
+       &jz     (&label("ctr32_ret"));
+       &mov    ($key,$key_);
+       &lea    ($rounds,&DWP(1,"",$rounds,2)); # restore $rounds
+       &movdqa ($inout5,&QWP(32,"esp"));       # pull count-less ivec
+
+&set_label("ctr32_tail");
+       &por    ($inout0,$inout5);
+       &cmp    ($len,2);
+       &jb     (&label("ctr32_one"));
+
+       &pshufd ($inout2,$rndkey1,1<<6);
+       &por    ($inout1,$inout5);
+       &je     (&label("ctr32_two"));
+
+       &pshufd ($inout3,$rndkey0,3<<6);
+       &por    ($inout2,$inout5);
+       &cmp    ($len,4);
+       &jb     (&label("ctr32_three"));
+
+       &pshufd ($inout4,$rndkey0,2<<6);
+       &por    ($inout3,$inout5);
+       &je     (&label("ctr32_four"));
+
+       &por    ($inout4,$inout5);
+       &call   ("_aesni_encrypt6");
+       &movups ($rndkey1,&QWP(0,$inp));
+       &movups ($rndkey0,&QWP(0x10,$inp));
+       &xorps  ($inout0,$rndkey1);
+       &movups ($rndkey1,&QWP(0x20,$inp));
+       &xorps  ($inout1,$rndkey0);
+       &movups ($rndkey0,&QWP(0x30,$inp));
+       &xorps  ($inout2,$rndkey1);
+       &movups ($rndkey1,&QWP(0x40,$inp));
+       &xorps  ($inout3,$rndkey0);
+       &movups (&QWP(0,$out),$inout0);
+       &xorps  ($inout4,$rndkey1);
+       &movups (&QWP(0x10,$out),$inout1);
+       &movups (&QWP(0x20,$out),$inout2);
+       &movups (&QWP(0x30,$out),$inout3);
+       &movups (&QWP(0x40,$out),$inout4);
+       &jmp    (&label("ctr32_ret"));
+
+&set_label("ctr32_one_shortcut",16);
+       &movups ($inout0,&QWP(0,$rounds_));     # load ivec
+       &mov    ($rounds,&DWP(240,$key));
+       
+&set_label("ctr32_one");
+       if ($inline)
+       {   &aesni_inline_generate1("enc");     }
+       else
+       {   &call       ("_aesni_encrypt1");    }
+       &movups ($in0,&QWP(0,$inp));
+       &xorps  ($in0,$inout0);
+       &movups (&QWP(0,$out),$in0);
+       &jmp    (&label("ctr32_ret"));
+
+&set_label("ctr32_two",16);
+       &call   ("_aesni_encrypt3");
+       &movups ($inout3,&QWP(0,$inp));
+       &movups ($inout4,&QWP(0x10,$inp));
+       &xorps  ($inout0,$inout3);
+       &xorps  ($inout1,$inout4);
+       &movups (&QWP(0,$out),$inout0);
+       &movups (&QWP(0x10,$out),$inout1);
+       &jmp    (&label("ctr32_ret"));
+
+&set_label("ctr32_three",16);
+       &call   ("_aesni_encrypt3");
+       &movups ($inout3,&QWP(0,$inp));
+       &movups ($inout4,&QWP(0x10,$inp));
+       &xorps  ($inout0,$inout3);
+       &movups ($inout5,&QWP(0x20,$inp));
+       &xorps  ($inout1,$inout4);
+       &movups (&QWP(0,$out),$inout0);
+       &xorps  ($inout2,$inout5);
+       &movups (&QWP(0x10,$out),$inout1);
+       &movups (&QWP(0x20,$out),$inout2);
+       &jmp    (&label("ctr32_ret"));
+
+&set_label("ctr32_four",16);
+       &call   ("_aesni_encrypt4");
+       &movups ($inout4,&QWP(0,$inp));
+       &movups ($inout5,&QWP(0x10,$inp));
+       &movups ($rndkey1,&QWP(0x20,$inp));
+       &xorps  ($inout0,$inout4);
+       &movups ($rndkey0,&QWP(0x30,$inp));
+       &xorps  ($inout1,$inout5);
+       &movups (&QWP(0,$out),$inout0);
+       &xorps  ($inout2,$rndkey1);
+       &movups (&QWP(0x10,$out),$inout1);
+       &xorps  ($inout3,$rndkey0);
+       &movups (&QWP(0x20,$out),$inout2);
+       &movups (&QWP(0x30,$out),$inout3);
+
+&set_label("ctr32_ret");
+       &mov    ("esp",&DWP(80,"esp"));
+&function_end("aesni_ctr32_encrypt_blocks");
+\f
+######################################################################
+# void aesni_xts_[en|de]crypt(const char *inp,char *out,size_t len,
+#      const AES_KEY *key1, const AES_KEY *key2
+#      const unsigned char iv[16]);
+#
+{ my ($tweak,$twtmp,$twres,$twmask)=($rndkey1,$rndkey0,$inout0,$inout1);
+
+&function_begin("aesni_xts_encrypt");
+       &mov    ($key,&wparam(4));              # key2
+       &mov    ($inp,&wparam(5));              # clear-text tweak
+
+       &mov    ($rounds,&DWP(240,$key));       # key2->rounds
+       &movups ($inout0,&QWP(0,$inp));
+       if ($inline)
+       {   &aesni_inline_generate1("enc");     }
+       else
+       {   &call       ("_aesni_encrypt1");    }
+
+       &mov    ($inp,&wparam(0));
+       &mov    ($out,&wparam(1));
+       &mov    ($len,&wparam(2));
+       &mov    ($key,&wparam(3));              # key1
+
+       &mov    ($key_,"esp");
+       &sub    ("esp",16*7+8);
+       &mov    ($rounds,&DWP(240,$key));       # key1->rounds
+       &and    ("esp",-16);                    # align stack
+
+       &mov    (&DWP(16*6+0,"esp"),0x87);      # compose the magic constant
+       &mov    (&DWP(16*6+4,"esp"),0);
+       &mov    (&DWP(16*6+8,"esp"),1);
+       &mov    (&DWP(16*6+12,"esp"),0);
+       &mov    (&DWP(16*7+0,"esp"),$len);      # save original $len
+       &mov    (&DWP(16*7+4,"esp"),$key_);     # save original %esp
+
+       &movdqa ($tweak,$inout0);
+       &pxor   ($twtmp,$twtmp);
+       &movdqa ($twmask,&QWP(6*16,"esp"));     # 0x0...010...87
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+
+       &and    ($len,-16);
+       &mov    ($key_,$key);                   # backup $key
+       &mov    ($rounds_,$rounds);             # backup $rounds
+       &sub    ($len,16*6);
+       &jc     (&label("xts_enc_short"));
+
+       &shr    ($rounds,1);
+       &mov    ($rounds_,$rounds);
+       &jmp    (&label("xts_enc_loop6"));
+
+&set_label("xts_enc_loop6",16);
+       for ($i=0;$i<4;$i++) {
+           &pshufd     ($twres,$twtmp,0x13);
+           &pxor       ($twtmp,$twtmp);
+           &movdqa     (&QWP(16*$i,"esp"),$tweak);
+           &paddq      ($tweak,$tweak);        # &psllq($tweak,1);
+           &pand       ($twres,$twmask);       # isolate carry and residue
+           &pcmpgtd    ($twtmp,$tweak);        # broadcast upper bits
+           &pxor       ($tweak,$twres);
+       }
+       &pshufd ($inout5,$twtmp,0x13);
+       &movdqa (&QWP(16*$i++,"esp"),$tweak);
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+        &$movekey      ($rndkey0,&QWP(0,$key_));
+       &pand   ($inout5,$twmask);              # isolate carry and residue
+        &movups        ($inout0,&QWP(0,$inp)); # load input
+       &pxor   ($inout5,$tweak);
+
+       # inline _aesni_encrypt6 prologue and flip xor with tweak and key[0]
+       &movdqu ($inout1,&QWP(16*1,$inp));
+        &xorps         ($inout0,$rndkey0);     # input^=rndkey[0]
+       &movdqu ($inout2,&QWP(16*2,$inp));
+        &pxor          ($inout1,$rndkey0);
+       &movdqu ($inout3,&QWP(16*3,$inp));
+        &pxor          ($inout2,$rndkey0);
+       &movdqu ($inout4,&QWP(16*4,$inp));
+        &pxor          ($inout3,$rndkey0);
+       &movdqu ($rndkey1,&QWP(16*5,$inp));
+        &pxor          ($inout4,$rndkey0);
+       &lea    ($inp,&DWP(16*6,$inp));
+       &pxor   ($inout0,&QWP(16*0,"esp"));     # input^=tweak
+       &movdqa (&QWP(16*$i,"esp"),$inout5);    # save last tweak
+       &pxor   ($inout5,$rndkey1);
+
+        &$movekey      ($rndkey1,&QWP(16,$key_));
+        &lea           ($key,&DWP(32,$key_));
+       &pxor   ($inout1,&QWP(16*1,"esp"));
+        &aesenc        ($inout0,$rndkey1);
+       &pxor   ($inout2,&QWP(16*2,"esp"));
+        &aesenc        ($inout1,$rndkey1);
+       &pxor   ($inout3,&QWP(16*3,"esp"));
+        &dec           ($rounds);
+        &aesenc        ($inout2,$rndkey1);
+       &pxor   ($inout4,&QWP(16*4,"esp"));
+        &aesenc        ($inout3,$rndkey1);
+       &pxor           ($inout5,$rndkey0);
+        &aesenc        ($inout4,$rndkey1);
+        &$movekey      ($rndkey0,&QWP(0,$key));
+        &aesenc        ($inout5,$rndkey1);
+       &call           (&label("_aesni_encrypt6_enter"));
+
+       &movdqa ($tweak,&QWP(16*5,"esp"));      # last tweak
+       &pxor   ($twtmp,$twtmp);
+       &xorps  ($inout0,&QWP(16*0,"esp"));     # output^=tweak
+       &pcmpgtd        ($twtmp,$tweak);                # broadcast upper bits
+       &xorps  ($inout1,&QWP(16*1,"esp"));
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &xorps  ($inout2,&QWP(16*2,"esp"));
+       &movups (&QWP(16*1,$out),$inout1);
+       &xorps  ($inout3,&QWP(16*3,"esp"));
+       &movups (&QWP(16*2,$out),$inout2);
+       &xorps  ($inout4,&QWP(16*4,"esp"));
+       &movups (&QWP(16*3,$out),$inout3);
+       &xorps  ($inout5,$tweak);
+       &movups (&QWP(16*4,$out),$inout4);
+       &pshufd ($twres,$twtmp,0x13);
+       &movups (&QWP(16*5,$out),$inout5);
+       &lea    ($out,&DWP(16*6,$out));
+       &movdqa ($twmask,&QWP(16*6,"esp"));     # 0x0...010...87
+
+       &pxor   ($twtmp,$twtmp);
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($twres,$twmask);               # isolate carry and residue
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &mov    ($rounds,$rounds_);             # restore $rounds
+       &pxor   ($tweak,$twres);
+
+       &sub    ($len,16*6);
+       &jnc    (&label("xts_enc_loop6"));
+
+       &lea    ($rounds,&DWP(1,"",$rounds,2)); # restore $rounds
+       &mov    ($key,$key_);                   # restore $key
+       &mov    ($rounds_,$rounds);
+
+&set_label("xts_enc_short");
+       &add    ($len,16*6);
+       &jz     (&label("xts_enc_done6x"));
+
+       &movdqa ($inout3,$tweak);               # put aside previous tweak
+       &cmp    ($len,0x20);
+       &jb     (&label("xts_enc_one"));
+
+       &pshufd ($twres,$twtmp,0x13);
+       &pxor   ($twtmp,$twtmp);
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($twres,$twmask);               # isolate carry and residue
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &pxor   ($tweak,$twres);
+       &je     (&label("xts_enc_two"));
+
+       &pshufd ($twres,$twtmp,0x13);
+       &pxor   ($twtmp,$twtmp);
+       &movdqa ($inout4,$tweak);               # put aside previous tweak
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($twres,$twmask);               # isolate carry and residue
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &pxor   ($tweak,$twres);
+       &cmp    ($len,0x40);
+       &jb     (&label("xts_enc_three"));
+
+       &pshufd ($twres,$twtmp,0x13);
+       &pxor   ($twtmp,$twtmp);
+       &movdqa ($inout5,$tweak);               # put aside previous tweak
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($twres,$twmask);               # isolate carry and residue
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &pxor   ($tweak,$twres);
+       &movdqa (&QWP(16*0,"esp"),$inout3);
+       &movdqa (&QWP(16*1,"esp"),$inout4);
+       &je     (&label("xts_enc_four"));
+
+       &movdqa (&QWP(16*2,"esp"),$inout5);
+       &pshufd ($inout5,$twtmp,0x13);
+       &movdqa (&QWP(16*3,"esp"),$tweak);
+       &paddq  ($tweak,$tweak);                # &psllq($inout0,1);
+       &pand   ($inout5,$twmask);              # isolate carry and residue
+       &pxor   ($inout5,$tweak);
+
+       &movdqu ($inout0,&QWP(16*0,$inp));      # load input
+       &movdqu ($inout1,&QWP(16*1,$inp));
+       &movdqu ($inout2,&QWP(16*2,$inp));
+       &pxor   ($inout0,&QWP(16*0,"esp"));     # input^=tweak
+       &movdqu ($inout3,&QWP(16*3,$inp));
+       &pxor   ($inout1,&QWP(16*1,"esp"));
+       &movdqu ($inout4,&QWP(16*4,$inp));
+       &pxor   ($inout2,&QWP(16*2,"esp"));
+       &lea    ($inp,&DWP(16*5,$inp));
+       &pxor   ($inout3,&QWP(16*3,"esp"));
+       &movdqa (&QWP(16*4,"esp"),$inout5);     # save last tweak
+       &pxor   ($inout4,$inout5);
+
+       &call   ("_aesni_encrypt6");
+
+       &movaps ($tweak,&QWP(16*4,"esp"));      # last tweak
+       &xorps  ($inout0,&QWP(16*0,"esp"));     # output^=tweak
+       &xorps  ($inout1,&QWP(16*1,"esp"));
+       &xorps  ($inout2,&QWP(16*2,"esp"));
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &xorps  ($inout3,&QWP(16*3,"esp"));
+       &movups (&QWP(16*1,$out),$inout1);
+       &xorps  ($inout4,$tweak);
+       &movups (&QWP(16*2,$out),$inout2);
+       &movups (&QWP(16*3,$out),$inout3);
+       &movups (&QWP(16*4,$out),$inout4);
+       &lea    ($out,&DWP(16*5,$out));
+       &jmp    (&label("xts_enc_done"));
+
+&set_label("xts_enc_one",16);
+       &movups ($inout0,&QWP(16*0,$inp));      # load input
+       &lea    ($inp,&DWP(16*1,$inp));
+       &xorps  ($inout0,$inout3);              # input^=tweak
+       if ($inline)
+       {   &aesni_inline_generate1("enc");     }
+       else
+       {   &call       ("_aesni_encrypt1");    }
+       &xorps  ($inout0,$inout3);              # output^=tweak
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &lea    ($out,&DWP(16*1,$out));
+
+       &movdqa ($tweak,$inout3);               # last tweak
+       &jmp    (&label("xts_enc_done"));
+
+&set_label("xts_enc_two",16);
+       &movaps ($inout4,$tweak);               # put aside last tweak
+
+       &movups ($inout0,&QWP(16*0,$inp));      # load input
+       &movups ($inout1,&QWP(16*1,$inp));
+       &lea    ($inp,&DWP(16*2,$inp));
+       &xorps  ($inout0,$inout3);              # input^=tweak
+       &xorps  ($inout1,$inout4);
+       &xorps  ($inout2,$inout2);
+
+       &call   ("_aesni_encrypt3");
+
+       &xorps  ($inout0,$inout3);              # output^=tweak
+       &xorps  ($inout1,$inout4);
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &movups (&QWP(16*1,$out),$inout1);
+       &lea    ($out,&DWP(16*2,$out));
+
+       &movdqa ($tweak,$inout4);               # last tweak
+       &jmp    (&label("xts_enc_done"));
+
+&set_label("xts_enc_three",16);
+       &movaps ($inout5,$tweak);               # put aside last tweak
+       &movups ($inout0,&QWP(16*0,$inp));      # load input
+       &movups ($inout1,&QWP(16*1,$inp));
+       &movups ($inout2,&QWP(16*2,$inp));
+       &lea    ($inp,&DWP(16*3,$inp));
+       &xorps  ($inout0,$inout3);              # input^=tweak
+       &xorps  ($inout1,$inout4);
+       &xorps  ($inout2,$inout5);
+
+       &call   ("_aesni_encrypt3");
+
+       &xorps  ($inout0,$inout3);              # output^=tweak
+       &xorps  ($inout1,$inout4);
+       &xorps  ($inout2,$inout5);
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &movups (&QWP(16*1,$out),$inout1);
+       &movups (&QWP(16*2,$out),$inout2);
+       &lea    ($out,&DWP(16*3,$out));
+
+       &movdqa ($tweak,$inout5);               # last tweak
+       &jmp    (&label("xts_enc_done"));
+
+&set_label("xts_enc_four",16);
+       &movaps ($inout4,$tweak);               # put aside last tweak
+
+       &movups ($inout0,&QWP(16*0,$inp));      # load input
+       &movups ($inout1,&QWP(16*1,$inp));
+       &movups ($inout2,&QWP(16*2,$inp));
+       &xorps  ($inout0,&QWP(16*0,"esp"));     # input^=tweak
+       &movups ($inout3,&QWP(16*3,$inp));
+       &lea    ($inp,&DWP(16*4,$inp));
+       &xorps  ($inout1,&QWP(16*1,"esp"));
+       &xorps  ($inout2,$inout5);
+       &xorps  ($inout3,$inout4);
+
+       &call   ("_aesni_encrypt4");
+
+       &xorps  ($inout0,&QWP(16*0,"esp"));     # output^=tweak
+       &xorps  ($inout1,&QWP(16*1,"esp"));
+       &xorps  ($inout2,$inout5);
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &xorps  ($inout3,$inout4);
+       &movups (&QWP(16*1,$out),$inout1);
+       &movups (&QWP(16*2,$out),$inout2);
+       &movups (&QWP(16*3,$out),$inout3);
+       &lea    ($out,&DWP(16*4,$out));
+
+       &movdqa ($tweak,$inout4);               # last tweak
+       &jmp    (&label("xts_enc_done"));
+
+&set_label("xts_enc_done6x",16);               # $tweak is pre-calculated
+       &mov    ($len,&DWP(16*7+0,"esp"));      # restore original $len
+       &and    ($len,15);
+       &jz     (&label("xts_enc_ret"));
+       &movdqa ($inout3,$tweak);
+       &mov    (&DWP(16*7+0,"esp"),$len);      # save $len%16
+       &jmp    (&label("xts_enc_steal"));
+
+&set_label("xts_enc_done",16);
+       &mov    ($len,&DWP(16*7+0,"esp"));      # restore original $len
+       &pxor   ($twtmp,$twtmp);
+       &and    ($len,15);
+       &jz     (&label("xts_enc_ret"));
+
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &mov    (&DWP(16*7+0,"esp"),$len);      # save $len%16
+       &pshufd ($inout3,$twtmp,0x13);
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($inout3,&QWP(16*6,"esp"));     # isolate carry and residue
+       &pxor   ($inout3,$tweak);
+
+&set_label("xts_enc_steal");
+       &movz   ($rounds,&BP(0,$inp));
+       &movz   ($key,&BP(-16,$out));
+       &lea    ($inp,&DWP(1,$inp));
+       &mov    (&BP(-16,$out),&LB($rounds));
+       &mov    (&BP(0,$out),&LB($key));
+       &lea    ($out,&DWP(1,$out));
+       &sub    ($len,1);
+       &jnz    (&label("xts_enc_steal"));
+
+       &sub    ($out,&DWP(16*7+0,"esp"));      # rewind $out
+       &mov    ($key,$key_);                   # restore $key
+       &mov    ($rounds,$rounds_);             # restore $rounds
+
+       &movups ($inout0,&QWP(-16,$out));       # load input
+       &xorps  ($inout0,$inout3);              # input^=tweak
+       if ($inline)
+       {   &aesni_inline_generate1("enc");     }
+       else
+       {   &call       ("_aesni_encrypt1");    }
+       &xorps  ($inout0,$inout3);              # output^=tweak
+       &movups (&QWP(-16,$out),$inout0);       # write output
+
+&set_label("xts_enc_ret");
+       &mov    ("esp",&DWP(16*7+4,"esp"));     # restore %esp
+&function_end("aesni_xts_encrypt");
+
+&function_begin("aesni_xts_decrypt");
+       &mov    ($key,&wparam(4));              # key2
+       &mov    ($inp,&wparam(5));              # clear-text tweak
+
+       &mov    ($rounds,&DWP(240,$key));       # key2->rounds
+       &movups ($inout0,&QWP(0,$inp));
+       if ($inline)
+       {   &aesni_inline_generate1("enc");     }
+       else
+       {   &call       ("_aesni_encrypt1");    }
+
+       &mov    ($inp,&wparam(0));
+       &mov    ($out,&wparam(1));
+       &mov    ($len,&wparam(2));
+       &mov    ($key,&wparam(3));              # key1
+
+       &mov    ($key_,"esp");
+       &sub    ("esp",16*7+8);
+       &and    ("esp",-16);                    # align stack
+
+       &xor    ($rounds_,$rounds_);            # if(len%16) len-=16;
+       &test   ($len,15);
+       &setnz  (&LB($rounds_));
+       &shl    ($rounds_,4);
+       &sub    ($len,$rounds_);
+
+       &mov    (&DWP(16*6+0,"esp"),0x87);      # compose the magic constant
+       &mov    (&DWP(16*6+4,"esp"),0);
+       &mov    (&DWP(16*6+8,"esp"),1);
+       &mov    (&DWP(16*6+12,"esp"),0);
+       &mov    (&DWP(16*7+0,"esp"),$len);      # save original $len
+       &mov    (&DWP(16*7+4,"esp"),$key_);     # save original %esp
+
+       &mov    ($rounds,&DWP(240,$key));       # key1->rounds
+       &mov    ($key_,$key);                   # backup $key
+       &mov    ($rounds_,$rounds);             # backup $rounds
+
+       &movdqa ($tweak,$inout0);
+       &pxor   ($twtmp,$twtmp);
+       &movdqa ($twmask,&QWP(6*16,"esp"));     # 0x0...010...87
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+
+       &and    ($len,-16);
+       &sub    ($len,16*6);
+       &jc     (&label("xts_dec_short"));
+
+       &shr    ($rounds,1);
+       &mov    ($rounds_,$rounds);
+       &jmp    (&label("xts_dec_loop6"));
+
+&set_label("xts_dec_loop6",16);
+       for ($i=0;$i<4;$i++) {
+           &pshufd     ($twres,$twtmp,0x13);
+           &pxor       ($twtmp,$twtmp);
+           &movdqa     (&QWP(16*$i,"esp"),$tweak);
+           &paddq      ($tweak,$tweak);        # &psllq($tweak,1);
+           &pand       ($twres,$twmask);       # isolate carry and residue
+           &pcmpgtd    ($twtmp,$tweak);        # broadcast upper bits
+           &pxor       ($tweak,$twres);
+       }
+       &pshufd ($inout5,$twtmp,0x13);
+       &movdqa (&QWP(16*$i++,"esp"),$tweak);
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+        &$movekey      ($rndkey0,&QWP(0,$key_));
+       &pand   ($inout5,$twmask);              # isolate carry and residue
+        &movups        ($inout0,&QWP(0,$inp)); # load input
+       &pxor   ($inout5,$tweak);
+
+       # inline _aesni_encrypt6 prologue and flip xor with tweak and key[0]
+       &movdqu ($inout1,&QWP(16*1,$inp));
+        &xorps         ($inout0,$rndkey0);     # input^=rndkey[0]
+       &movdqu ($inout2,&QWP(16*2,$inp));
+        &pxor          ($inout1,$rndkey0);
+       &movdqu ($inout3,&QWP(16*3,$inp));
+        &pxor          ($inout2,$rndkey0);
+       &movdqu ($inout4,&QWP(16*4,$inp));
+        &pxor          ($inout3,$rndkey0);
+       &movdqu ($rndkey1,&QWP(16*5,$inp));
+        &pxor          ($inout4,$rndkey0);
+       &lea    ($inp,&DWP(16*6,$inp));
+       &pxor   ($inout0,&QWP(16*0,"esp"));     # input^=tweak
+       &movdqa (&QWP(16*$i,"esp"),$inout5);    # save last tweak
+       &pxor   ($inout5,$rndkey1);
+
+        &$movekey      ($rndkey1,&QWP(16,$key_));
+        &lea           ($key,&DWP(32,$key_));
+       &pxor   ($inout1,&QWP(16*1,"esp"));
+        &aesdec        ($inout0,$rndkey1);
+       &pxor   ($inout2,&QWP(16*2,"esp"));
+        &aesdec        ($inout1,$rndkey1);
+       &pxor   ($inout3,&QWP(16*3,"esp"));
+        &dec           ($rounds);
+        &aesdec        ($inout2,$rndkey1);
+       &pxor   ($inout4,&QWP(16*4,"esp"));
+        &aesdec        ($inout3,$rndkey1);
+       &pxor           ($inout5,$rndkey0);
+        &aesdec        ($inout4,$rndkey1);
+        &$movekey      ($rndkey0,&QWP(0,$key));
+        &aesdec        ($inout5,$rndkey1);
+       &call           (&label("_aesni_decrypt6_enter"));
+
+       &movdqa ($tweak,&QWP(16*5,"esp"));      # last tweak
+       &pxor   ($twtmp,$twtmp);
+       &xorps  ($inout0,&QWP(16*0,"esp"));     # output^=tweak
+       &pcmpgtd        ($twtmp,$tweak);                # broadcast upper bits
+       &xorps  ($inout1,&QWP(16*1,"esp"));
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &xorps  ($inout2,&QWP(16*2,"esp"));
+       &movups (&QWP(16*1,$out),$inout1);
+       &xorps  ($inout3,&QWP(16*3,"esp"));
+       &movups (&QWP(16*2,$out),$inout2);
+       &xorps  ($inout4,&QWP(16*4,"esp"));
+       &movups (&QWP(16*3,$out),$inout3);
+       &xorps  ($inout5,$tweak);
+       &movups (&QWP(16*4,$out),$inout4);
+       &pshufd ($twres,$twtmp,0x13);
+       &movups (&QWP(16*5,$out),$inout5);
+       &lea    ($out,&DWP(16*6,$out));
+       &movdqa ($twmask,&QWP(16*6,"esp"));     # 0x0...010...87
+
+       &pxor   ($twtmp,$twtmp);
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($twres,$twmask);               # isolate carry and residue
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &mov    ($rounds,$rounds_);             # restore $rounds
+       &pxor   ($tweak,$twres);
+
+       &sub    ($len,16*6);
+       &jnc    (&label("xts_dec_loop6"));
+
+       &lea    ($rounds,&DWP(1,"",$rounds,2)); # restore $rounds
+       &mov    ($key,$key_);                   # restore $key
+       &mov    ($rounds_,$rounds);
+
+&set_label("xts_dec_short");
+       &add    ($len,16*6);
+       &jz     (&label("xts_dec_done6x"));
+
+       &movdqa ($inout3,$tweak);               # put aside previous tweak
+       &cmp    ($len,0x20);
+       &jb     (&label("xts_dec_one"));
+
+       &pshufd ($twres,$twtmp,0x13);
+       &pxor   ($twtmp,$twtmp);
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($twres,$twmask);               # isolate carry and residue
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &pxor   ($tweak,$twres);
+       &je     (&label("xts_dec_two"));
+
+       &pshufd ($twres,$twtmp,0x13);
+       &pxor   ($twtmp,$twtmp);
+       &movdqa ($inout4,$tweak);               # put aside previous tweak
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($twres,$twmask);               # isolate carry and residue
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &pxor   ($tweak,$twres);
+       &cmp    ($len,0x40);
+       &jb     (&label("xts_dec_three"));
+
+       &pshufd ($twres,$twtmp,0x13);
+       &pxor   ($twtmp,$twtmp);
+       &movdqa ($inout5,$tweak);               # put aside previous tweak
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($twres,$twmask);               # isolate carry and residue
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &pxor   ($tweak,$twres);
+       &movdqa (&QWP(16*0,"esp"),$inout3);
+       &movdqa (&QWP(16*1,"esp"),$inout4);
+       &je     (&label("xts_dec_four"));
+
+       &movdqa (&QWP(16*2,"esp"),$inout5);
+       &pshufd ($inout5,$twtmp,0x13);
+       &movdqa (&QWP(16*3,"esp"),$tweak);
+       &paddq  ($tweak,$tweak);                # &psllq($inout0,1);
+       &pand   ($inout5,$twmask);              # isolate carry and residue
+       &pxor   ($inout5,$tweak);
+
+       &movdqu ($inout0,&QWP(16*0,$inp));      # load input
+       &movdqu ($inout1,&QWP(16*1,$inp));
+       &movdqu ($inout2,&QWP(16*2,$inp));
+       &pxor   ($inout0,&QWP(16*0,"esp"));     # input^=tweak
+       &movdqu ($inout3,&QWP(16*3,$inp));
+       &pxor   ($inout1,&QWP(16*1,"esp"));
+       &movdqu ($inout4,&QWP(16*4,$inp));
+       &pxor   ($inout2,&QWP(16*2,"esp"));
+       &lea    ($inp,&DWP(16*5,$inp));
+       &pxor   ($inout3,&QWP(16*3,"esp"));
+       &movdqa (&QWP(16*4,"esp"),$inout5);     # save last tweak
+       &pxor   ($inout4,$inout5);
+
+       &call   ("_aesni_decrypt6");
+
+       &movaps ($tweak,&QWP(16*4,"esp"));      # last tweak
+       &xorps  ($inout0,&QWP(16*0,"esp"));     # output^=tweak
+       &xorps  ($inout1,&QWP(16*1,"esp"));
+       &xorps  ($inout2,&QWP(16*2,"esp"));
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &xorps  ($inout3,&QWP(16*3,"esp"));
+       &movups (&QWP(16*1,$out),$inout1);
+       &xorps  ($inout4,$tweak);
+       &movups (&QWP(16*2,$out),$inout2);
+       &movups (&QWP(16*3,$out),$inout3);
+       &movups (&QWP(16*4,$out),$inout4);
+       &lea    ($out,&DWP(16*5,$out));
+       &jmp    (&label("xts_dec_done"));
+
+&set_label("xts_dec_one",16);
+       &movups ($inout0,&QWP(16*0,$inp));      # load input
+       &lea    ($inp,&DWP(16*1,$inp));
+       &xorps  ($inout0,$inout3);              # input^=tweak
+       if ($inline)
+       {   &aesni_inline_generate1("dec");     }
+       else
+       {   &call       ("_aesni_decrypt1");    }
+       &xorps  ($inout0,$inout3);              # output^=tweak
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &lea    ($out,&DWP(16*1,$out));
+
+       &movdqa ($tweak,$inout3);               # last tweak
+       &jmp    (&label("xts_dec_done"));
 
+&set_label("xts_dec_two",16);
+       &movaps ($inout4,$tweak);               # put aside last tweak
+
+       &movups ($inout0,&QWP(16*0,$inp));      # load input
+       &movups ($inout1,&QWP(16*1,$inp));
+       &lea    ($inp,&DWP(16*2,$inp));
+       &xorps  ($inout0,$inout3);              # input^=tweak
+       &xorps  ($inout1,$inout4);
+
+       &call   ("_aesni_decrypt3");
+
+       &xorps  ($inout0,$inout3);              # output^=tweak
+       &xorps  ($inout1,$inout4);
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &movups (&QWP(16*1,$out),$inout1);
+       &lea    ($out,&DWP(16*2,$out));
+
+       &movdqa ($tweak,$inout4);               # last tweak
+       &jmp    (&label("xts_dec_done"));
+
+&set_label("xts_dec_three",16);
+       &movaps ($inout5,$tweak);               # put aside last tweak
+       &movups ($inout0,&QWP(16*0,$inp));      # load input
+       &movups ($inout1,&QWP(16*1,$inp));
+       &movups ($inout2,&QWP(16*2,$inp));
+       &lea    ($inp,&DWP(16*3,$inp));
+       &xorps  ($inout0,$inout3);              # input^=tweak
+       &xorps  ($inout1,$inout4);
+       &xorps  ($inout2,$inout5);
+
+       &call   ("_aesni_decrypt3");
+
+       &xorps  ($inout0,$inout3);              # output^=tweak
+       &xorps  ($inout1,$inout4);
+       &xorps  ($inout2,$inout5);
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &movups (&QWP(16*1,$out),$inout1);
+       &movups (&QWP(16*2,$out),$inout2);
+       &lea    ($out,&DWP(16*3,$out));
+
+       &movdqa ($tweak,$inout5);               # last tweak
+       &jmp    (&label("xts_dec_done"));
+
+&set_label("xts_dec_four",16);
+       &movaps ($inout4,$tweak);               # put aside last tweak
+
+       &movups ($inout0,&QWP(16*0,$inp));      # load input
+       &movups ($inout1,&QWP(16*1,$inp));
+       &movups ($inout2,&QWP(16*2,$inp));
+       &xorps  ($inout0,&QWP(16*0,"esp"));     # input^=tweak
+       &movups ($inout3,&QWP(16*3,$inp));
+       &lea    ($inp,&DWP(16*4,$inp));
+       &xorps  ($inout1,&QWP(16*1,"esp"));
+       &xorps  ($inout2,$inout5);
+       &xorps  ($inout3,$inout4);
+
+       &call   ("_aesni_decrypt4");
+
+       &xorps  ($inout0,&QWP(16*0,"esp"));     # output^=tweak
+       &xorps  ($inout1,&QWP(16*1,"esp"));
+       &xorps  ($inout2,$inout5);
+       &movups (&QWP(16*0,$out),$inout0);      # write output
+       &xorps  ($inout3,$inout4);
+       &movups (&QWP(16*1,$out),$inout1);
+       &movups (&QWP(16*2,$out),$inout2);
+       &movups (&QWP(16*3,$out),$inout3);
+       &lea    ($out,&DWP(16*4,$out));
+
+       &movdqa ($tweak,$inout4);               # last tweak
+       &jmp    (&label("xts_dec_done"));
+
+&set_label("xts_dec_done6x",16);               # $tweak is pre-calculated
+       &mov    ($len,&DWP(16*7+0,"esp"));      # restore original $len
+       &and    ($len,15);
+       &jz     (&label("xts_dec_ret"));
+       &mov    (&DWP(16*7+0,"esp"),$len);      # save $len%16
+       &jmp    (&label("xts_dec_only_one_more"));
+
+&set_label("xts_dec_done",16);
+       &mov    ($len,&DWP(16*7+0,"esp"));      # restore original $len
+       &pxor   ($twtmp,$twtmp);
+       &and    ($len,15);
+       &jz     (&label("xts_dec_ret"));
+
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &mov    (&DWP(16*7+0,"esp"),$len);      # save $len%16
+       &pshufd ($twres,$twtmp,0x13);
+       &pxor   ($twtmp,$twtmp);
+       &movdqa ($twmask,&QWP(16*6,"esp"));
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($twres,$twmask);               # isolate carry and residue
+       &pcmpgtd($twtmp,$tweak);                # broadcast upper bits
+       &pxor   ($tweak,$twres);
+
+&set_label("xts_dec_only_one_more");
+       &pshufd ($inout3,$twtmp,0x13);
+       &movdqa ($inout4,$tweak);               # put aside previous tweak
+       &paddq  ($tweak,$tweak);                # &psllq($tweak,1);
+       &pand   ($inout3,$twmask);              # isolate carry and residue
+       &pxor   ($inout3,$tweak);
+
+       &mov    ($key,$key_);                   # restore $key
+       &mov    ($rounds,$rounds_);             # restore $rounds
+
+       &movups ($inout0,&QWP(0,$inp));         # load input
+       &xorps  ($inout0,$inout3);              # input^=tweak
+       if ($inline)
+       {   &aesni_inline_generate1("dec");     }
+       else
+       {   &call       ("_aesni_decrypt1");    }
+       &xorps  ($inout0,$inout3);              # output^=tweak
+       &movups (&QWP(0,$out),$inout0);         # write output
+
+&set_label("xts_dec_steal");
+       &movz   ($rounds,&BP(16,$inp));
+       &movz   ($key,&BP(0,$out));
+       &lea    ($inp,&DWP(1,$inp));
+       &mov    (&BP(0,$out),&LB($rounds));
+       &mov    (&BP(16,$out),&LB($key));
+       &lea    ($out,&DWP(1,$out));
+       &sub    ($len,1);
+       &jnz    (&label("xts_dec_steal"));
+
+       &sub    ($out,&DWP(16*7+0,"esp"));      # rewind $out
+       &mov    ($key,$key_);                   # restore $key
+       &mov    ($rounds,$rounds_);             # restore $rounds
+
+       &movups ($inout0,&QWP(0,$out));         # load input
+       &xorps  ($inout0,$inout4);              # input^=tweak
+       if ($inline)
+       {   &aesni_inline_generate1("dec");     }
+       else
+       {   &call       ("_aesni_decrypt1");    }
+       &xorps  ($inout0,$inout4);              # output^=tweak
+       &movups (&QWP(0,$out),$inout0);         # write output
+
+&set_label("xts_dec_ret");
+       &mov    ("esp",&DWP(16*7+4,"esp"));     # restore %esp
+&function_end("aesni_xts_decrypt");
+}
+}
+\f
+######################################################################
 # void $PREFIX_cbc_encrypt (const void *inp, void *out,
 #                           size_t length, const AES_KEY *key,
 #                           unsigned char *ivp,const int enc);
 &function_begin("${PREFIX}_cbc_encrypt");
        &mov    ($inp,&wparam(0));
+       &mov    ($rounds_,"esp");
        &mov    ($out,&wparam(1));
+       &sub    ($rounds_,24);
        &mov    ($len,&wparam(2));
+       &and    ($rounds_,-16);
        &mov    ($key,&wparam(3));
-       &test   ($len,$len);
        &mov    ($key_,&wparam(4));
-       &je     (&label("cbc_ret"));
+       &test   ($len,$len);
+       &jz     (&label("cbc_abort"));
 
        &cmp    (&wparam(5),0);
-       &movups ($ivec,&QWP(0,$key_));  # load IV
+       &xchg   ($rounds_,"esp");               # alloca
+       &movups ($ivec,&QWP(0,$key_));          # load IV
        &mov    ($rounds,&DWP(240,$key));
-       &mov    ($key_,$key);           # backup $key
-       &mov    ($rounds_,$rounds);     # backup $rounds
+       &mov    ($key_,$key);                   # backup $key
+       &mov    (&DWP(16,"esp"),$rounds_);      # save original %esp
+       &mov    ($rounds_,$rounds);             # backup $rounds
        &je     (&label("cbc_decrypt"));
 
        &movaps ($inout0,$ivec);
@@ -304,15 +1749,17 @@ if ($PREFIX eq "aesni") {
        &jmp    (&label("cbc_enc_loop"));
 
 &set_label("cbc_enc_loop",16);
-       &movups ($ivec,&QWP(0,$inp));
+       &movups ($ivec,&QWP(0,$inp));           # input actually
        &lea    ($inp,&DWP(16,$inp));
-       &pxor   ($inout0,$ivec);
-       &call   ("_aesni_encrypt1");
-       &sub    ($len,16);
+       if ($inline)
+       {   &aesni_inline_generate1("enc",$inout0,$ivec);       }
+       else
+       {   &xorps($inout0,$ivec); &call("_aesni_encrypt1");    }
        &mov    ($rounds,$rounds_);     # restore $rounds
        &mov    ($key,$key_);           # restore $key
-       &movups (&QWP(0,$out),$inout0);
+       &movups (&QWP(0,$out),$inout0); # store output
        &lea    ($out,&DWP(16,$out));
+       &sub    ($len,16);
        &jnc    (&label("cbc_enc_loop"));
        &add    ($len,16);
        &jnz    (&label("cbc_enc_tail"));
@@ -331,71 +1778,151 @@ if ($PREFIX eq "aesni") {
        &mov    ($inp,$out);            # $inp and $out are the same
        &mov    ($key,$key_);           # restore $key
        &jmp    (&label("cbc_enc_loop"));
-
+######################################################################
 &set_label("cbc_decrypt",16);
-       &sub    ($len,0x30);
-       &jc     (&label("cbc_dec_tail"));
-       &jmp    (&label("cbc_dec_loop3"));
+       &cmp    ($len,0x50);
+       &jbe    (&label("cbc_dec_tail"));
+       &movaps (&QWP(0,"esp"),$ivec);          # save IV
+       &sub    ($len,0x50);
+       &jmp    (&label("cbc_dec_loop6_enter"));
 
-&set_label("cbc_dec_loop3",16);
-       &movups ($inout0,&QWP(0,$inp));
-       &movups ($inout1,&QWP(0x10,$inp));
-       &movups ($inout2,&QWP(0x20,$inp));
-       &movaps ($in0,$inout0);
-       &movaps ($in1,$inout1);
-       &call   ("_aesni_decrypt3");
-       &sub    ($len,0x30);
-       &lea    ($inp,&DWP(0x30,$inp));
-       &pxor   ($inout0,$ivec);
-       &pxor   ($inout1,$in0);
-       &movups ($ivec,&QWP(-0x10,$inp));
-       &pxor   ($inout2,$in1);
+&set_label("cbc_dec_loop6",16);
+       &movaps (&QWP(0,"esp"),$rndkey0);       # save IV
+       &movups (&QWP(0,$out),$inout5);
+       &lea    ($out,&DWP(0x10,$out));
+&set_label("cbc_dec_loop6_enter");
+       &movdqu ($inout0,&QWP(0,$inp));
+       &movdqu ($inout1,&QWP(0x10,$inp));
+       &movdqu ($inout2,&QWP(0x20,$inp));
+       &movdqu ($inout3,&QWP(0x30,$inp));
+       &movdqu ($inout4,&QWP(0x40,$inp));
+       &movdqu ($inout5,&QWP(0x50,$inp));
+
+       &call   ("_aesni_decrypt6");
+
+       &movups ($rndkey1,&QWP(0,$inp));
+       &movups ($rndkey0,&QWP(0x10,$inp));
+       &xorps  ($inout0,&QWP(0,"esp"));        # ^=IV
+       &xorps  ($inout1,$rndkey1);
+       &movups ($rndkey1,&QWP(0x20,$inp));
+       &xorps  ($inout2,$rndkey0);
+       &movups ($rndkey0,&QWP(0x30,$inp));
+       &xorps  ($inout3,$rndkey1);
+       &movups ($rndkey1,&QWP(0x40,$inp));
+       &xorps  ($inout4,$rndkey0);
+       &movups ($rndkey0,&QWP(0x50,$inp));     # IV
+       &xorps  ($inout5,$rndkey1);
        &movups (&QWP(0,$out),$inout0);
-       &mov    ($rounds,$rounds_)      # restore $rounds
        &movups (&QWP(0x10,$out),$inout1);
-       &mov    ($key,$key_);           # restore $key
+       &lea    ($inp,&DWP(0x60,$inp));
        &movups (&QWP(0x20,$out),$inout2);
-       &lea    ($out,&DWP(0x30,$out));
-       &jnc    (&label("cbc_dec_loop3"));
+       &mov    ($rounds,$rounds_);             # restore $rounds
+       &movups (&QWP(0x30,$out),$inout3);
+       &mov    ($key,$key_);                   # restore $key
+       &movups (&QWP(0x40,$out),$inout4);
+       &lea    ($out,&DWP(0x50,$out));
+       &sub    ($len,0x60);
+       &ja     (&label("cbc_dec_loop6"));
 
+       &movaps ($inout0,$inout5);
+       &movaps ($ivec,$rndkey0);
+       &add    ($len,0x50);
+       &jle    (&label("cbc_dec_tail_collected"));
+       &movups (&QWP(0,$out),$inout0);
+       &lea    ($out,&DWP(0x10,$out));
 &set_label("cbc_dec_tail");
-       &add    ($len,0x30);
-       &jz     (&label("cbc_ret"));
-
        &movups ($inout0,&QWP(0,$inp));
-       &cmp    ($len,0x10);
        &movaps ($in0,$inout0);
+       &cmp    ($len,0x10);
        &jbe    (&label("cbc_dec_one"));
+
        &movups ($inout1,&QWP(0x10,$inp));
-       &cmp    ($len,0x20);
        &movaps ($in1,$inout1);
+       &cmp    ($len,0x20);
        &jbe    (&label("cbc_dec_two"));
+
        &movups ($inout2,&QWP(0x20,$inp));
-       &call   ("_aesni_decrypt3");
-       &pxor   ($inout0,$ivec);
-       &movups ($ivec,&QWP(0x20,$inp));
-       &pxor   ($inout1,$in0);
-       &pxor   ($inout2,$in1);
+       &cmp    ($len,0x30);
+       &jbe    (&label("cbc_dec_three"));
+
+       &movups ($inout3,&QWP(0x30,$inp));
+       &cmp    ($len,0x40);
+       &jbe    (&label("cbc_dec_four"));
+
+       &movups ($inout4,&QWP(0x40,$inp));
+       &movaps (&QWP(0,"esp"),$ivec);          # save IV
+       &movups ($inout0,&QWP(0,$inp));
+       &xorps  ($inout5,$inout5);
+       &call   ("_aesni_decrypt6");
+       &movups ($rndkey1,&QWP(0,$inp));
+       &movups ($rndkey0,&QWP(0x10,$inp));
+       &xorps  ($inout0,&QWP(0,"esp"));        # ^= IV
+       &xorps  ($inout1,$rndkey1);
+       &movups ($rndkey1,&QWP(0x20,$inp));
+       &xorps  ($inout2,$rndkey0);
+       &movups ($rndkey0,&QWP(0x30,$inp));
+       &xorps  ($inout3,$rndkey1);
+       &movups ($ivec,&QWP(0x40,$inp));        # IV
+       &xorps  ($inout4,$rndkey0);
        &movups (&QWP(0,$out),$inout0);
        &movups (&QWP(0x10,$out),$inout1);
-       &movaps ($inout0,$inout2);
-       &lea    ($out,&DWP(0x20,$out));
+       &movups (&QWP(0x20,$out),$inout2);
+       &movups (&QWP(0x30,$out),$inout3);
+       &lea    ($out,&DWP(0x40,$out));
+       &movaps ($inout0,$inout4);
+       &sub    ($len,0x50);
        &jmp    (&label("cbc_dec_tail_collected"));
 
-&set_label("cbc_dec_one");
-       &call   ("_aesni_decrypt1");
-       &pxor   ($inout0,$ivec);
+&set_label("cbc_dec_one",16);
+       if ($inline)
+       {   &aesni_inline_generate1("dec");     }
+       else
+       {   &call       ("_aesni_decrypt1");    }
+       &xorps  ($inout0,$ivec);
        &movaps ($ivec,$in0);
+       &sub    ($len,0x10);
        &jmp    (&label("cbc_dec_tail_collected"));
 
-&set_label("cbc_dec_two");
+&set_label("cbc_dec_two",16);
+       &xorps  ($inout2,$inout2);
        &call   ("_aesni_decrypt3");
-       &pxor   ($inout0,$ivec);
-       &pxor   ($inout1,$in0);
+       &xorps  ($inout0,$ivec);
+       &xorps  ($inout1,$in0);
        &movups (&QWP(0,$out),$inout0);
        &movaps ($inout0,$inout1);
-       &movaps ($ivec,$in1);
        &lea    ($out,&DWP(0x10,$out));
+       &movaps ($ivec,$in1);
+       &sub    ($len,0x20);
+       &jmp    (&label("cbc_dec_tail_collected"));
+
+&set_label("cbc_dec_three",16);
+       &call   ("_aesni_decrypt3");
+       &xorps  ($inout0,$ivec);
+       &xorps  ($inout1,$in0);
+       &xorps  ($inout2,$in1);
+       &movups (&QWP(0,$out),$inout0);
+       &movaps ($inout0,$inout2);
+       &movups (&QWP(0x10,$out),$inout1);
+       &lea    ($out,&DWP(0x20,$out));
+       &movups ($ivec,&QWP(0x20,$inp));
+       &sub    ($len,0x30);
+       &jmp    (&label("cbc_dec_tail_collected"));
+
+&set_label("cbc_dec_four",16);
+       &call   ("_aesni_decrypt4");
+       &movups ($rndkey1,&QWP(0x10,$inp));
+       &movups ($rndkey0,&QWP(0x20,$inp));
+       &xorps  ($inout0,$ivec);
+       &movups ($ivec,&QWP(0x30,$inp));
+       &xorps  ($inout1,$in0);
+       &movups (&QWP(0,$out),$inout0);
+       &xorps  ($inout2,$rndkey1);
+       &movups (&QWP(0x10,$out),$inout1);
+       &xorps  ($inout3,$rndkey0);
+       &movups (&QWP(0x20,$out),$inout2);
+       &lea    ($out,&DWP(0x30,$out));
+       &movaps ($inout0,$inout3);
+       &sub    ($len,0x40);
 
 &set_label("cbc_dec_tail_collected");
        &and    ($len,15);
@@ -403,21 +1930,21 @@ if ($PREFIX eq "aesni") {
        &movups (&QWP(0,$out),$inout0);
        &jmp    (&label("cbc_ret"));
 
-&set_label("cbc_dec_tail_partial");
-       &mov    ($key_,"esp");
-       &sub    ("esp",16);
-       &and    ("esp",-16);
+&set_label("cbc_dec_tail_partial",16);
        &movaps (&QWP(0,"esp"),$inout0);
+       &mov    ("ecx",16);
        &mov    ($inp,"esp");
-       &mov    ("ecx",$len);
+       &sub    ("ecx",$len);
        &data_word(0xA4F3F689);         # rep movsb
-       &mov    ("esp",$key_);
 
 &set_label("cbc_ret");
+       &mov    ("esp",&DWP(16,"esp")); # pull original %esp
        &mov    ($key_,&wparam(4));
        &movups (&QWP(0,$key_),$ivec);  # output IV
+&set_label("cbc_abort");
 &function_end("${PREFIX}_cbc_encrypt");
-
+\f
+######################################################################
 # Mechanical port from aesni-x86_64.pl.
 #
 # _aesni_set_encrypt_key is private interface,
@@ -436,7 +1963,7 @@ if ($PREFIX eq "aesni") {
        &jz     (&label("bad_pointer"));
 
        &movups ("xmm0",&QWP(0,"eax")); # pull first 128 bits of *userKey
-       &pxor   ("xmm4","xmm4");        # low dword of xmm4 is assumed 0
+       &xorps  ("xmm4","xmm4");        # low dword of xmm4 is assumed 0
        &lea    ($key,&DWP(16,$key));
        &cmp    ($rounds,256);
        &je     (&label("14rounds"));
@@ -446,7 +1973,7 @@ if ($PREFIX eq "aesni") {
        &jne    (&label("bad_keybits"));
 
 &set_label("10rounds",16);
-       &mov            ($rounds,10);
+       &mov            ($rounds,9);
        &$movekey       (&QWP(-16,$key),"xmm0");        # round 0
        &aeskeygenassist("xmm1","xmm0",0x01);           # round 1
        &call           (&label("key_128_cold"));
@@ -478,17 +2005,17 @@ if ($PREFIX eq "aesni") {
        &lea            ($key,&DWP(16,$key));
 &set_label("key_128_cold");
        &shufps         ("xmm4","xmm0",0b00010000);
-       &pxor           ("xmm0","xmm4");
-       &shufps         ("xmm4","xmm0",0b10001100,);
-       &pxor           ("xmm0","xmm4");
-       &pshufd         ("xmm1","xmm1",0b11111111);     # critical path
-       &pxor           ("xmm0","xmm1");
+       &xorps          ("xmm0","xmm4");
+       &shufps         ("xmm4","xmm0",0b10001100);
+       &xorps          ("xmm0","xmm4");
+       &shufps         ("xmm1","xmm1",0b11111111);     # critical path
+       &xorps          ("xmm0","xmm1");
        &ret();
 
 &set_label("12rounds",16);
        &movq           ("xmm2",&QWP(16,"eax"));        # remaining 1/3 of *userKey
-       &mov            ($rounds,12);
-       &$movekey       (&QWP(-16,$key),"xmm0")         # round 0
+       &mov            ($rounds,11);
+       &$movekey       (&QWP(-16,$key),"xmm0");        # round 0
        &aeskeygenassist("xmm1","xmm2",0x01);           # round 1,2
        &call           (&label("key_192a_cold"));
        &aeskeygenassist("xmm1","xmm2",0x02);           # round 2,3
@@ -517,11 +2044,11 @@ if ($PREFIX eq "aesni") {
        &movaps         ("xmm5","xmm2");
 &set_label("key_192b_warm");
        &shufps         ("xmm4","xmm0",0b00010000);
-       &movaps         ("xmm3","xmm2");
-       &pxor           ("xmm0","xmm4");
+       &movdqa         ("xmm3","xmm2");
+       &xorps          ("xmm0","xmm4");
        &shufps         ("xmm4","xmm0",0b10001100);
        &pslldq         ("xmm3",4);
-       &pxor           ("xmm0","xmm4");
+       &xorps          ("xmm0","xmm4");
        &pshufd         ("xmm1","xmm1",0b01010101);     # critical path
        &pxor           ("xmm2","xmm3");
        &pxor           ("xmm0","xmm1");
@@ -540,7 +2067,7 @@ if ($PREFIX eq "aesni") {
 
 &set_label("14rounds",16);
        &movups         ("xmm2",&QWP(16,"eax"));        # remaining half of *userKey
-       &mov            ($rounds,14);
+       &mov            ($rounds,13);
        &lea            ($key,&DWP(16,$key));
        &$movekey       (&QWP(-32,$key),"xmm0");        # round 0
        &$movekey       (&QWP(-16,$key),"xmm2");        # round 1
@@ -580,11 +2107,11 @@ if ($PREFIX eq "aesni") {
        &lea            ($key,&DWP(16,$key));
 &set_label("key_256a_cold");
        &shufps         ("xmm4","xmm0",0b00010000);
-       &pxor           ("xmm0","xmm4");
+       &xorps          ("xmm0","xmm4");
        &shufps         ("xmm4","xmm0",0b10001100);
-       &pxor           ("xmm0","xmm4");
-       &pshufd         ("xmm1","xmm1",0b11111111);     # critical path
-       &pxor           ("xmm0","xmm1");
+       &xorps          ("xmm0","xmm4");
+       &shufps         ("xmm1","xmm1",0b11111111);     # critical path
+       &xorps          ("xmm0","xmm1");
        &ret();
 
 &set_label("key_256b",16);
@@ -592,11 +2119,11 @@ if ($PREFIX eq "aesni") {
        &lea            ($key,&DWP(16,$key));
 
        &shufps         ("xmm4","xmm2",0b00010000);
-       &pxor           ("xmm2","xmm4");
+       &xorps          ("xmm2","xmm4");
        &shufps         ("xmm4","xmm2",0b10001100);
-       &pxor           ("xmm2","xmm4");
-       &pshufd         ("xmm1","xmm1",0b10101010);     # critical path
-       &pxor           ("xmm2","xmm1");
+       &xorps          ("xmm2","xmm4");
+       &shufps         ("xmm1","xmm1",0b10101010);     # critical path
+       &xorps          ("xmm2","xmm1");
        &ret();
 
 &set_label("bad_pointer",4);
@@ -625,10 +2152,10 @@ if ($PREFIX eq "aesni") {
        &mov    ($key,&wparam(2));
        &call   ("_aesni_set_encrypt_key");
        &mov    ($key,&wparam(2));
-       &shl    ($rounds,4)     # actually rounds after _aesni_set_encrypt_key
+       &shl    ($rounds,4);    # rounds-1 after _aesni_set_encrypt_key
        &test   ("eax","eax");
        &jnz    (&label("dec_key_ret"));
-       &lea    ("eax",&DWP(0,$key,$rounds));   # end of key schedule
+       &lea    ("eax",&DWP(16,$key,$rounds));  # end of key schedule
 
        &$movekey       ("xmm0",&QWP(0,$key));  # just swap
        &$movekey       ("xmm1",&QWP(0,"eax"));
@@ -636,18 +2163,17 @@ if ($PREFIX eq "aesni") {
        &$movekey       (&QWP(0,$key),"xmm1");
        &lea            ($key,&DWP(16,$key));
        &lea            ("eax",&DWP(-16,"eax"));
-       &jmp            (&label("dec_key_inverse"));
 
-&set_label("dec_key_inverse",16);
+&set_label("dec_key_inverse");
        &$movekey       ("xmm0",&QWP(0,$key));  # swap and inverse
        &$movekey       ("xmm1",&QWP(0,"eax"));
        &aesimc         ("xmm0","xmm0");
        &aesimc         ("xmm1","xmm1");
        &lea            ($key,&DWP(16,$key));
        &lea            ("eax",&DWP(-16,"eax"));
-       &cmp            ("eax",$key);
        &$movekey       (&QWP(16,"eax"),"xmm0");
        &$movekey       (&QWP(-16,$key),"xmm1");
+       &cmp            ("eax",$key);
        &ja             (&label("dec_key_inverse"));
 
        &$movekey       ("xmm0",&QWP(0,$key));  # inverse middle