LinuxのIntel x86のIntrinsic関数をRISC-V、IBM PowerやARM用にポートする(1)

最近LinuxアプリケーションのIntel x86-64 intrinsic関数をRISC-VやIBM Power用にポートする関係の作業やってます。これは結局、x86でしか動かないLinuxアプリケーションをRISC-VやIBM Powerでも動かせるようにしたいということで、ソフトウェアのアーキテクチャ依存性の解消の作業になります。
これができれば、肥大化し複雑化しがちなCPUをシンプルに保つことができるかもしれません。
現在クラウドやPCを支配してるのはIntel x86-64系になります。
Intel x86はCISC（Complex Instruction Set Computer）であり、かなり長い歴史があり、かつてIntel x86-64で動いていたバイナリコードは今後出るx86-64でも動かないといけないため、かなり過去の資産を背負ってます。IntelなどのCISCは内部でμOPなどのRISC命令に変換しており、そのための回路も必要となり、ダイサイズが大きくなり、消費電力が大きくなり非効率になるなどのオーバーヘッドがあります。
また、命令はどんどん追加されるが(平均月に一つ以上）、バイナリ互換性を確保するため、命令を削除することが出来ず、かなりISAが肥大化しており、迷路のようになっているようです。
また、Intelの商業的都合で命令が追加されることもあるようです。ここらへんはオープンISAのRISC-Vが利点があります。
それに対し、IBM PowerはRISC(Reduced Instruction Set Computer)と呼ばれ、これも一度動いたバイナリコードは今後出るCPUでも同様に動かないといけないという意味では同じですが、命令セットがシンプルで、比較的新しい技術を使えるようです。内部でμOPへの変換も少ないです。
なので、スパコンなどのランキングの上位は、富士通の富岳や、IBMのSummitなど、RISCとなっています。
ただ、スパコンのマーケットシェアはIntel x86-64が一位となっており、これは憶測ですが、世の中のLinux用のアプリケーションの殆どがIntel x86-64用に作られているためだと思っています。世の中のLinuxアプリケーションがIntelを前提に作られている理由は、歴史的経緯によります。
よって、Intel x86-64用に作られたアプリケーションをARMやIBM PowerやARMやさらにRISC-Vでパフォーマンス最適化された形で動かすことができれば、世界の科学技術の発展に貢献することができると考えられます。
ムーアの法則が終焉を迎えつつある今、次なる進歩は、Intel x86-64からRISC-Vへの移植が鍵になります。
IBM Powerや、ARMなども、Intel x86-64よりは効率が良いですが、ここはOpen ISAであるRISC-Vの肩を持ちたいところです。


そこで、Intel x86-64で動くソフトウェアを、IBM Powerや他のアーキテクチャで動かすようにするためには、Intel Intrinsic関数をIBM Powerにポートする作業が必要となります。

Intel Intrinsic関数は膨大にあり、IBM Powerなら種類は少ないですが、RISC-VのCPUは様々な種類が考えられることから、ポートプロセスの何らかの形での自動化は必須になると考えられます。

そこで、以下のドキュメントが参考になります。

Linux on Power Porting Guide
https://openpowerfoundation.org/specifications/vectorintrinsicportingguide/

Intel Intrinsic APIは、Intelが長年に渡って加え続けている、命令セット拡張へのアクセスを提供します。SIMD(Single Instruction Multiple Data)もこれに含まれます。

Intel x86-64 intrinsic関数をIBM Powerにポートするときは、独特のラップ構造を取ります。以下がその例です。

```C
extern __inline __m128d __attribute__((__gnu_inline__, __always_inline__,__artificial__))
_mm_add_pd (__m128d __A, __m128d __B)
{
   return (__m128d) ((__v2df)__A + (__v2df)__B);
}
```
`_mm_add_pd`はIntelのIntrinsic関数で、`__A`と`__B`をここでは足しています。IntelのIntrinsic関数が、このコードを加えることにより、IBM Powerでも動くようになります。

また別の例を示すと

```C
extern __inline __m128d __attribute__((__gnu_inline__, __always_inline__,__artificial__))
_mm_set1_pd (double __F)
{
   return __extension__ (__m128d){ __F, __F };
}
```

これは、`__F`の値をコピーして2つにしてベクトル形式である`__m128d`に格納してます。

僕が今構想しているのはポートした内容をテストする方法です。これらのポートは正確性を担保する必要があります。そこで、クラウド上にあるIntelとIBM Powerをファイルを相互で移動させ、Intel IntrinsicとそれをIBM Powerにポートしたものに、それぞれにおなじ値を入力し、返ってきた値が同じであることを確認するテストフレームワークを考えています。Akariテストフレームワークと名付けてます。
　Intel　PCやサーバで、特定のIntrinsic関数のInputとOutputを、Inputをランダムに生成し、OutputをIntel Intrinsic関数を実行して生成し、それらのデータをファイルにまとめます。それを外付けHDDなどのストレージに貯蔵し、IntelからIBM PowerやRISC-Vなどに差し替え、ポートしたコードをテストします。

 InputとOutputのセットがあるため、IBM PowerやRISC-V側では、人の手によって、試行錯誤でポートコードを作成することができます。ポートコードを記述し、テストフレームワークを実行して、エラーやレイテンシがすぐに表示されます。

要するにこれは、ポート前とポート後、最適化前と最適化後の同じ挙動をするべきモジュールが、同じ数値を入れると同じ結果が帰るかどうかの検証をするテストフレームワークとなります。全ての最適化に必要になると思われるので、汎用性はあると思います。

https://www.slideshare.net/OkaDaisuke/testing-framework-to-port-and-optimize-simd-library-to-open-power-systems

OpenPower summit北米２０２１で発表しました。刺さる人には刺さったようです。


ちなみに、僕はIntelのバイナリコードをRISC-V用に変換することは考えていません。コンパイル前のC言語を変換することにより、IntelとRISC-Vの双方のバイナリコードにコンパイルすることを考えています。

既存のポートは、アーキテクチャごとに分岐して処理しており、一つのアーキテクチャの処理が変わったら、別のアーキテクチャのところも書き換えなければなりません。このタイプのポート（このラップ構造）をすれば、Intelの部分だけ修正すれば、他のアーキテクチャ部分も自動的に書き換えられます。（Takahashi Akariさんの助言で気が付きました。ありがとうございます。）

間違いなどあるかも知れません。指摘していただければ嬉しいです。

*  追記　IBM Powerが微妙に使えなくなったので、AmazonでWayPonDEVが出しているRISC-V開発ボードを使うことを考えています。
*  追記　実ハードウェアの代わりにエミュレーションを使う手もありますが、コードのバグかエミュレーションのバグか区別がつかない問題もある

*  追記　　C言語以外のネットワークフレームワークや、多言語の仕様変更やサポートが切れるのが怖いので、C言語のみでソケットプログラミングで作る手がある。最初はローカル同士でつなぐため。ActiveMQは使えないかも。結構危険なテストのため、ネット上に値情報を流したくない。ローカルのみで完結するためC言語が良いかも。Nothing better than Cという格言もある。
勉強会やってます
*  追記　　ポートするコードを作成するAIっぽいのを作っても良いかも

https://connpass.com/user/daisukeokaoss/

* 追記　テストフレームワークAkariのアイコンをAIで作ってみました

![_38bc4eed-b4ac-4a89-851a-82d2c3edfa90.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/53172/54f38a2e-57e8-883b-f18c-528e7078d2b0.jpeg)



* 追記　Intel開発者ガイドも熟読する必要あります。
https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html

* 追記　
`__v2df`は

https://github.com/gcc-mirror/gcc/blob/master/gcc/config/rs6000/emmintrin.h

このソースコードでは`typedef __vector double __v2df;`こう宣言されていて、`__vector`はIBMのベクトル拡張のため、RISC-Vで同じ記法では使えない可能性と、同じ使い方もできない可能性がある。

https://www.ibm.com/docs/ja/xl-c-aix/13.1.0?topic=tokens-keywords#keyw__suplink9

IBMのベクトル拡張の実装は、GCCには記述されていないようだ。使用されているだけ。RISC-Vで、ベクトル拡張をC++で記述して、C言語で使えるようにすればいいかもしれない。
IBMのポートコードと同じものをそのまま使いたい場合は、同じ仕様のベクトル拡張をクローンしてもいいかもしれない。