SSEでエンディアン変換

サラリと検索して見当たらなかったので、覚書として、
SSEでエンディアンを変換したいときにどうすればいいか。

SSE2までのSSE命令を使っています。
コンパイラはVisualC++を使っています。
構文チェックをしていないので、セミコロンなどが抜けてるかもしれません。


# 16bitづつをひっくり返す場合。
1. _mm_sll_epi16()及び_mm_srl_epi16()で16bitづつに区切って8bitづつシフト。
2. それらをORで結合して出来上がり。

```cpp:swap_word.cpp
/**
 * change byte endian in 8 words
 *
 * @note src = 1,2,3,4, ..., 16
 *       srca = 0,1,0,3, ..., 15
 *       srcb = 2,0,4,0, ..., 0
 *       result = 2,1,4,3, ..., 15
 */
__m128i swap_epi16(__m128i src)
{
  __declspec(align(16)) count8[2] = {
    8, 8
  };
  __m128i srca = _mm_sll_epi16(src, *(__m128i*)count8);
  __m128i srcb = _mm_srl_epi16(src, *(__m128i*)count8);
  __m128i result = _mm_or_si128(srca, srcb);

  return result;
}
```

# 32bitづつをひっくり返す場合。
1. _mm_sll_epi16()及び_mm_srl_epi16()で16bitづつに区切って8bitづつシフト。
2. それらをORで結合して第１段階完了。
3. 更にそれを32bitづつに区切って16bitづつシフト。
4. それらをORで結合して出来上がり。

```cpp:swap_dword.cpp
/**
 * change byte endian in 4 dwords
 *
 * @note src = 1,2,3,4, ..., 16
 *       srca = 0,1,0,3, ..., 15
 *       srcb = 2,0,4,0, ..., 0
 *       srcc = 2,1,4,3, ..., 15
 *       srca = 0,0,2,1, ..., 13
 *       srcb = 4,3,0,0, ..., 0
 *       result = 4,3,2,1, ..., 13
 */
__m128i swap_epi32(__m128i src)
{
  __declspec(align(16)) count8[2] = {
    8, 8
  };
  __declspec(align(16)) count16[2] = {
    16, 16
  };
  __m128i srca = _mm_sll_epi16(src, *(__m128i*)count8);
  __m128i srcb = _mm_srl_epi16(src, *(__m128i*)count8);
  __m128i srcc = _mm_or_si128(srca, srcb);
  srca = _mm_sll_epi32(srcc, *(__m128i*)count16);
  srcb = _mm_srl_epi32(srcc, *(__m128i*)count16);
  //__m128i result = _mm_or_si128(src1a, src1b);  誤り
  __m128i result = _mm_or_si128(srca, srcb);

  return result;
}
```

count8をレジスタに読み込んどいたほうが速そうとか、count16はcount8を2倍(1bit左シフト)すれば使えるとかはお任せします。

# 64bitづつをひっくり返す場合。
1. _mm_sll_epi16()及び_mm_srl_epi16()で16bitづつに区切って8bitづつシフト。
2. それらをORで結合して第１段階完了。
3. 更にそれを32bitづつに区切って16bitづつシフト。
4. それらをORで結合して第２段階完了。
5. 更にそれを64bitづつに区切って32bitづつシフト。
6. それらをORで結合して出来上がり。

```cpp:swap_qword.cpp
/**
 * change byte endian in 2 qwords
 *
 * @note src = 1,2,3,4, ..., 16
 *       srca = 0,1,0,3, ..., 15
 *       srcb = 2,0,4,0, ..., 0
 *       srcc = 2,1,4,3, ..., 15
 *       srca = 0,0,2,1, ..., 13
 *       srcb = 4,3,0,0, ..., 0
 *       srcc = 4,3,2,1, ..., 13
 *       srca = 0,0,0,0, ..., 9
 *       srcb = 8,7,6,5, ..., 0
 *       result = 8,7,6,5, ..., 9
 */
__m128i swap_epi64(__m128i src)
{
  __declspec(align(16)) count8[2] = {
    8, 8
  };
  __declspec(align(16)) count16[2] = {
    16, 16
  };
  __declspec(align(16)) count32[2] = {
    32, 32
  };
  __m128i srca = _mm_sll_epi16(src, *(__m128i*)count8);
  __m128i srcb = _mm_srl_epi16(src, *(__m128i*)count8);
  __m128i srcc = _mm_or_si128(srca, srcb);
  srca = _mm_sll_epi32(srcc, *(__m128i*)count16);
  srcb = _mm_srl_epi32(srcc, *(__m128i*)count16);
  srcc = _mm_or_si128(srca, srcb);
  srca = _mm_sll_epi64(srcc, *(__m128i*)count32);
  srcb = _mm_srl_epi64(srcc, *(__m128i*)count32);
  //__m128i result = _mm_or_si128(src1a, src1b);  誤り
  __m128i result = _mm_or_si128(srca, srcb);

  return result;
}
```
count8をレジスタに読み込んどいたほうが速そうとか、count32/count16はcount8を4/2倍(2/1bit左シフト)すれば使えるとかはお任せします。

# avxで4バイトづつ(2023/06/21追記)  
最近のCPUはavxまで載っていたりするので、shuffle命令版。
``` c:swap_4byte_avx.c
__m256i swap_epi32_avx(__m256i src)
    __m256i ptn = _mm256_set_epi32(
        0x0C0D0E0F, 0x08090A0B, 0x04050607, 0x00010203,
        0x0C0D0E0F, 0x08090A0B, 0x04050607, 0x00010203);
    return _mm256_shuffle_epi8(src, ptn);
}

__m128i swap_epi32_sse(__m128i src)
    __m128i ptn = _mm_set_epi32(0x0C0D0E0F, 0x08090A0B, 0x04050607, 0x00010203);
    // __m128i ptn = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
    return _mm_shuffle_epi8(src, ptn);
}
```


# 参考
こちらをかなり参考にさせて頂いています。--> http://www.officedaytime.com/tips/simd.html

# 余談
* Visual C++であれば、_byteswap_uint64(), _byteswap_ulong(), _byteswap_ushort()といった便利な関数があるので、SIMDにしなくていい時はこれを使うのが良いのではないかと。
* この調子だと、4bitづつのスワップも出来るのかと思ったら、8bit単位のビットシフト命令は無かったｗ