Hello Michael,

On Sat, Jan 23, 2021 at 2:45 AM Michael Weiser <[email protected]>
wrote:

> I've just retested and reread some ARM documents. Here's a patch that
> uses ld1.16b and thus eliminates almost all special BE treatment but
> subsequently has to leave in all the rev64s as well. This has the
> testsuite passing on BE and (still) LE. My take at an explanation below.
>
> diff --git a/arm64/v8/gcm-hash.asm b/arm64/v8/gcm-hash.asm
> index 1c14db54..8c8a370e 100644
> --- a/arm64/v8/gcm-hash.asm
> +++ b/arm64/v8/gcm-hash.asm
> @@ -55,17 +55,10 @@ C common macros:
>  .endm
>
>  .macro REDUCTION out
> -IF_BE(`
> -    pmull          T.1q,F.1d,POLY.1d
> -    ext            \out\().16b,F.16b,F.16b,#8
> -    eor            R.16b,R.16b,T.16b
> -    eor            \out\().16b,\out\().16b,R.16b
> -',`
>      pmull          T.1q,F.1d,POLY.1d
>      eor            R.16b,R.16b,T.16b
>      ext            R.16b,R.16b,R.16b,#8
>      eor            \out\().16b,F.16b,R.16b
> -')
>  .endm
>
>      C void gcm_init_key (union gcm_block *table)
> @@ -108,27 +101,20 @@ define(`H4M', `v29')
>  define(`H4L', `v30')
>
>  .macro PMUL_PARAM in, param1, param2
> -IF_BE(`
> -    pmull2         Hp.1q,\in\().2d,POLY.2d
> -    ext            Hm.16b,\in\().16b,\in\().16b,#8
> -    eor            Hm.16b,Hm.16b,Hp.16b
> -    zip            \param1\().2d,\in\().2d,Hm.2d
> -    zip2           \param2\().2d,\in\().2d,Hm.2d
> -',`
>      pmull2         Hp.1q,\in\().2d,POLY.2d
>      eor            Hm.16b,\in\().16b,Hp.16b
>      ext            \param1\().16b,Hm.16b,\in\().16b,#8
>      ext            \param2\().16b,\in\().16b,Hm.16b,#8
>      ext            \param1\().16b,\param1\().16b,\param1\().16b,#8
> -')
>  .endm
>
>  PROLOGUE(_nettle_gcm_init_key)
> -    ldr            HQ,[TABLE,#16*H_Idx]
> +    C LSB vector load: x1+0 into H.b[0] and x1+15 into H.b[15]
> +    add            x1,TABLE,#16*H_Idx
> +    ld1            {H.16b},[x1]
>      dup            EMSB.16b,H.b[0]
> -IF_LE(`
> +    C treat H as two MSB doublewords
>      rev64          H.16b,H.16b
> -')
>      mov            x1,#0xC200000000000000
>      mov            x2,#1
>      mov            POLY.d[0],x1
> @@ -221,9 +207,7 @@ PROLOGUE(_nettle_gcm_hash)
>      mov            POLY.d[0],x4
>
>      ld1            {D.16b},[X]
> -IF_LE(`
>      rev64          D.16b,D.16b
> -')
>
>      ands           x4,LENGTH,#-64
>      b.eq           L2x
> @@ -234,12 +218,10 @@ IF_LE(`
>
>  L4x_loop:
>      ld1            {C0.16b,C1.16b,C2.16b,C3.16b},[DATA],#64
> -IF_LE(`
>      rev64          C0.16b,C0.16b
>      rev64          C1.16b,C1.16b
>      rev64          C2.16b,C2.16b
>      rev64          C3.16b,C3.16b
> -')
>
>      eor            C0.16b,C0.16b,D.16b
>
> @@ -262,10 +244,8 @@ L2x:
>      ld1            {H1M.16b,H1L.16b,H2M.16b,H2L.16b},[TABLE]
>
>      ld1            {C0.16b,C1.16b},[DATA],#32
> -IF_LE(`
>      rev64          C0.16b,C0.16b
>      rev64          C1.16b,C1.16b
> -')
>
>      eor            C0.16b,C0.16b,D.16b
>
> @@ -283,9 +263,7 @@ L1x:
>      ld1            {H1M.16b,H1L.16b},[TABLE]
>
>      ld1            {C0.16b},[DATA],#16
> -IF_LE(`
>      rev64          C0.16b,C0.16b
> -')
>
>      eor            C0.16b,C0.16b,D.16b
>
> @@ -335,9 +313,7 @@ Lmod_8_done:
>      REDUCTION D
>
>  Ldone:
> -IF_LE(`
>      rev64          D.16b,D.16b
> -')
>      st1            {D.16b},[X]
>      ret
>  EPILOGUE(_nettle_gcm_hash)


I have one question here, do operations on doublewords transpose both
doubleword parts in BE mode? for example pmull instruction transpose
doublewords on LE mode when operated, in BE I don't expect the same
behavior hence we can't get this patch working on BE mode. The core of
pmull instruction is shift and xor operations so we can't perform pmull
instruction on byte-reversed doublewords as it's gonna produce wrong
results.


>
My understanding is that ld1 and st1 are "single-element structure"
> operations. (Identical to vld1 in arm32 NEON we discussed recently for
> chacha and salsa2 asm.) That means they load a number of elements of a
> given type from consecutive memory locations into the corresponding
> vector register indices.
>
> ld1 {v0.4s},[x0] would load four 32bit words from consecutive memory
> locations and put them into v0.s[0] through v0.s[3]. So x0+0..3 (bytes)
> would go into v0.s[0], x0+4..7 would to into v0.s[1] and so on.
> Endianness would apply to the internal byte order of the elements, so
> each word would be loaded MSB-first in BE-mode and LSB-first in LE-mode.
>
> So, given memory content such as:
>
> x0 + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
> byte 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
>
> We should get on BE:
>
>          MSB     LSB
> v0.s[0]:     0 1 2 3
> v0.s[1]:     4 5 6 7
> v0.s[2]:   8 9 10 11
> v0.s[3]: 12 13 14 15
>
> Or looked at as byte-vectors:
>
>         |v0.s[0]|v0.s[1]| v0.s[2] | v0.s[3]  |
>       v0.b[0]                             v0.b[15]
> v0.16b:  3 2 1 0 7 6 5 4 11 10 9 8 15 14 13 12
>
> On LE we should get:
>
>          MSB     LSB
> v0.d[0]:     3 2 1 0
> v0.d[1]:     7 6 5 4
> v0.d[2]:   11 10 9 8
> v0.d[3]: 15 14 13 12
>
>       v0.b[0]                             v0.b[15]
> v0.16b: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
>
> This was just meant as intro. I've not actually tested this. I hope I
> got it right and not just added to everyone's confusion (mine included).
> :/
>
> Back to ld1.16b: This now loads a vector of 16 bytes consecutively.
> Since bytes have no endianness there will be no changes in order on
> either LE and BE modes. The register content will look the same on both:
>
> x0 +    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
> byte:   0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
>     v0.b[0]                             v0.b[15]
> v0.16b: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
>
> So larger datatypes loaded that way should be stored little-endian in
> memory to make sense as e.g. .d[0] after such a load. Or we need to
> rev64 them.
>
> > load the H value by using ld1 "ld1 {H.16b},[x1]" in this way we can still
> > have to deal with LE as transposed doublewords and with BE in normal way
> > (not transposed doublewords or transposed quadword).
>
> After sending my last email I realised that the doublewords aren't
> actually transposed with BE as such. They're just transposed compared to
> the original LE routine because the ldr instruction loads in completely
> reversed order in each mode and the LE routine does convert the internal
> byte order of the doublewords to BE but not the overall order of the
> 128bit quadword because it doesn't need to and regards them as a
> vector of two doublewords anyway.
>
> ld1.16b doesn't change that at all. It just behaves the same on LE and
> BE. So we'll always load vectors of bytes. And it'll always be an LSB
> load. And if we want to treat them as big-endian doublewords we have to
> adjust them accordingly. That's why we now also need all the rev64s on
> BE above.
>
> That opens another topic: As you may have noticed I haven't got the
> slightest idea of what the code is actually doing. Assembly also isn't
> my first language either. I'm only mechanically trying to get BE mode to
> produce the same results as LE.
>
> This made me realise that I haven't the faintest idea what we're getting
> as input and producing as output either. :/ So are we working on blocks
> of bytes and producing blocks of bytes and just treating them as
> big-endian 64bit doublewords internally to exploit availability of
> instructions that can work on these types or could we actually declare
> the elements of TABLE to be quadwords in host endianness? Then we could
> actually throw ld1.2d at them and eliminate all the rev64s.
>
> Duh, I think we can regardless, at least for BE:
>
> diff --git a/arm64/v8/gcm-hash.asm b/arm64/v8/gcm-hash.asm
> index 1c14db54..642e3840 100644
> --- a/arm64/v8/gcm-hash.asm
> +++ b/arm64/v8/gcm-hash.asm
> @@ -55,17 +55,10 @@ C common macros:
>  .endm
>
>  .macro REDUCTION out
> -IF_BE(`
> -    pmull          T.1q,F.1d,POLY.1d
> -    ext            \out\().16b,F.16b,F.16b,#8
> -    eor            R.16b,R.16b,T.16b
> -    eor            \out\().16b,\out\().16b,R.16b
> -',`
>      pmull          T.1q,F.1d,POLY.1d
>      eor            R.16b,R.16b,T.16b
>      ext            R.16b,R.16b,R.16b,#8
>      eor            \out\().16b,F.16b,R.16b
> -')
>  .endm
>
>      C void gcm_init_key (union gcm_block *table)
> @@ -108,27 +101,20 @@ define(`H4M', `v29')
>  define(`H4L', `v30')
>
>  .macro PMUL_PARAM in, param1, param2
> -IF_BE(`
> -    pmull2         Hp.1q,\in\().2d,POLY.2d
> -    ext            Hm.16b,\in\().16b,\in\().16b,#8
> -    eor            Hm.16b,Hm.16b,Hp.16b
> -    zip            \param1\().2d,\in\().2d,Hm.2d
> -    zip2           \param2\().2d,\in\().2d,Hm.2d
> -',`
>      pmull2         Hp.1q,\in\().2d,POLY.2d
>      eor            Hm.16b,\in\().16b,Hp.16b
>      ext            \param1\().16b,Hm.16b,\in\().16b,#8
>      ext            \param2\().16b,\in\().16b,Hm.16b,#8
>      ext            \param1\().16b,\param1\().16b,\param1\().16b,#8
> -')
>  .endm
>
>  PROLOGUE(_nettle_gcm_init_key)
> -    ldr            HQ,[TABLE,#16*H_Idx]
> -    dup            EMSB.16b,H.b[0]
> +    add            x1,TABLE,#16*H_Idx
> +    ld1            {H.2d},[x1]
>  IF_LE(`
>      rev64          H.16b,H.16b
>  ')
> +    dup            EMSB.16b,H.b[7]
>      mov            x1,#0xC200000000000000
>      mov            x2,#1
>      mov            POLY.d[0],x1
> @@ -220,7 +206,7 @@ PROLOGUE(_nettle_gcm_hash)
>      mov            x4,#0xC200000000000000
>      mov            POLY.d[0],x4
>
> -    ld1            {D.16b},[X]
> +    ld1            {D.2d},[X]
>  IF_LE(`
>      rev64          D.16b,D.16b
>  ')
> @@ -233,7 +219,7 @@ IF_LE(`
>      ld1            {H3M.16b,H3L.16b,H4M.16b,H4L.16b},[x5]
>
>  L4x_loop:
> -    ld1            {C0.16b,C1.16b,C2.16b,C3.16b},[DATA],#64
> +    ld1            {C0.2d,C1.2d,C2.2d,C3.2d},[DATA],#64
>  IF_LE(`
>      rev64          C0.16b,C0.16b
>      rev64          C1.16b,C1.16b
> @@ -261,7 +247,7 @@ L2x:
>
>      ld1            {H1M.16b,H1L.16b,H2M.16b,H2L.16b},[TABLE]
>
> -    ld1            {C0.16b,C1.16b},[DATA],#32
> +    ld1            {C0.2d,C1.2d},[DATA],#32
>  IF_LE(`
>      rev64          C0.16b,C0.16b
>      rev64          C1.16b,C1.16b
> @@ -282,7 +268,7 @@ L1x:
>
>      ld1            {H1M.16b,H1L.16b},[TABLE]
>
> -    ld1            {C0.16b},[DATA],#16
> +    ld1            {C0.2d},[DATA],#16
>  IF_LE(`
>      rev64          C0.16b,C0.16b
>  ')
> @@ -335,9 +321,7 @@ Lmod_8_done:
>      REDUCTION D
>
>  Ldone:
> -IF_LE(`
>      rev64          D.16b,D.16b
> -')
>      st1            {D.16b},[X]
>      ret
>  EPILOGUE(_nettle_gcm_hash)
>

I like your ideas so far as you're shrinking the gap between both
endianness code but if my previous concern is right we still can't get this
patch works too.


> Please excuse my laboured and longwinded thinking. ;) I really have to
> start thinking in vectors also.
>

Actually, I'm impressed how you get and handle all these ideas in your mind
and turn around quickly once you get a new one. Dealing with vector
registers in aarch64 is really challenging, both x86_64 and PowerPC don't
drag the endianness issues to vector registers, it's only applied to memory
and once the data loaded from memory into vector register all
endianness concerns are ended. Although PowerPC supports both endianness
modes, AltiVec instructions operate the same on vector registers on both
modes. It's a weird decision made by the Arm side.


> And as always after all this guesswork I have found a likely very
> relevant comment in gcm.c:
>
>   /* Shift uses big-endian representation. */
> #if WORDS_BIGENDIAN
>   reduce = shift_table[x->u64[1] & 0xff];
>
> Is that it? Or is TABLE just internal to the routine and we can store
> there however we please? (Apart from H at TABLE[128] initialised for us
> by gcm_set_key and stored BE?)
>

The assembly implementation of GHASH has a whole different scheme from C
table-lookup implementation, you don't have to worry about any of that.

regards,
Mamone
_______________________________________________
nettle-bugs mailing list
[email protected]
http://lists.lysator.liu.se/mailman/listinfo/nettle-bugs

Reply via email to