link もっと前
   2020年 7月 16日 -
      2020年 7月 7日  
link もっと後

link 未来から過去へ表示(*)
link 過去から未来へ表示

link permalink

link 編集する

GCC を調べる - その 16 - 自動ベクトル化を有効にする

目次: GCC を調べる - まとめリンク

GCC には自動ベクトル化(tree-vectorize)機能があります。ループ処理を自動的に SIMD 命令に置き換えるために使われているようです。現状の GCC が可変長のベクトル長に対応しているかどうかはわかりません。未対応ならば可変長のベクトル長に対応する実装が必要になりますが、非常に難しそうです。

可変長のベクトルの扱いはひとまず横に置くとして、RISC-V のベクトルを「とても長い固定長の SIMD」とみなして自動ベクトル化を動かします。

自動ベクトル化を有効にするコード

// gcc/config/riscv/riscv.c

/* Implement TARGET_VECTORIZE_AUTOVECTORIZE_VECTOR_MODES.  */

static unsigned int
riscv_autovectorize_vector_modes (vector_modes *modes, bool)
{
  if (TARGET_VECTOR)
    {
      modes->safe_push (V64SImode);
      modes->safe_push (V32SImode);
    }

  return 0;
}

...

#undef TARGET_VECTORIZE_AUTOVECTORIZE_VECTOR_MODES
#define TARGET_VECTORIZE_AUTOVECTORIZE_VECTOR_MODES riscv_autovectorize_vector_modes

自動ベクトル化を有効にする方法は簡単で、これだけです。

テスト用の int 配列をコピーする関数

void *cpy(void *dst, const void *src, int n)
{
	int *d = dst;
	const int *s = src;
	int i;

	for (i = 0; i < n / sizeof(*d); i++) {
		d[i] = s[i];
	}

	return dst;
}

この関数がベクトル化あり、なしでどのように変わるか見ます。

自動ベクトル化の結果

//★★自動ベクトル化、あり

                d[i] = s[i];
   100b8:       1202e007                vlw.v   v0,(t0)
   100bc:       10038393                addi    t2,t2,256
   100c0:       f0038793                addi    a5,t2,-256
   100c4:       10028293                addi    t0,t0,256
   100c8:       0207e027                vsw.v   v0,(a5)
        for (i = 0; i < n / sizeof(*d); i++) {
   100cc:       fee296e3                bne     t0,a4,100b8 <cpy+0x44>
   100d0:       00661293                slli    t0,a2,0x6
   100d4:       02568963                beq     a3,t0,10106 <cpy+0x92>
   100d8:       959a                    add     a1,a1,t1
   100da:       932a                    add     t1,t1,a0
                d[i] = s[i];
   100dc:       0005a383                lw      t2,0(a1)


//★★自動ベクトル化、なし

                d[i] = s[i];
   10080:       0005a303                lw      t1,0(a1)
        for (i = 0; i < n / sizeof(*d); i++) {
   10084:       0591                    addi    a1,a1,4
   10086:       0291                    addi    t0,t0,4
                d[i] = s[i];
   10088:       fe62ae23                sw      t1,-4(t0)
        for (i = 0; i < n / sizeof(*d); i++) {
   1008c:       fe759ae3                bne     a1,t2,10080 <cpy+0xc>
        }

        return dst;
}
   10090:       8082                    ret

ソースコードではベクトル型を使っていませんが、自動ベクトル化により 256バイト(= 64要素)ずつ処理され、vlw.v, vsw.v 命令が使われるようになったことがわかります。

[編集者: すずき]
[更新: 2020年 7月 16日 10:17]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

OpenCL と ICD

OpenCL は複数のベンダーのデバイスを同時に扱うことができます。ICD(Installable Client Driver)というそうです。ICD は GPU などのデバイスを制御し、アプリケーションと ICD の間に ICD ローダーが存在します。


アプリケーション、ICD ローダー、ICD の関係


Debian でのライブラリ名

Debian Testing では ICD ローダーとして ocl-icd-2.2.12 が使われています。

Debian Testing の ICD loader
$ apt-cache search ocl-icd-libopencl1

ocl-icd-libopencl1 - Generic OpenCL ICD Loader

ローダーのソースコードは GitHub(リンク)にあります。

先程、図示したもの以外にも ICD はいくつか実装があります。現時点の Debian Testing では下記が提供されていました。

  • pocl-opencl-icd: 主に CPU 向け
  • beignet-opencl-icd: 比較的古い Intel GPU 向け
  • intel-opencl-icd: 比較的新しい Intel GPU 向け、Testing にしか存在しない
  • mesa-opencl-icd: AMD GPU 向け
  • nvidia-opencl-icd: nVidia GPU 向け、ソースコードは公開されていない

Intel は ICD のソースコードを完全にオープンにしています。NVIDIA は公開していません。AMD もないのかな?

ICD のロード手順

動作を追ってみたいと思います。アプリケーションには clinfo を使います。ocl-icd は環境変数 OCL_ICD_DEBUG=15 に設定すると、動作時に詳細なログを出力します。デバッガで追うのと併用するとわかりやすいです。

  • clinfo: clGetPlatformIDs() を呼ぶ
  • ocl-icd: _initClIcd() -> _initClIcd_real() -> __initClIcd()
  • ocl-icd: _find_num_icds(): /etc/OpenCL/vendors の *.icd ファイルを見る(*.so へのパスが書いてある)
  • ocl-icd: _load_icd(): *.so を dlopen()
  • ocl-icd: _find_and_check_platforms(): 後述

ローダーが走査するディレクトリは /etc/OpenCL/vendors がハードコードされていますが、環境変数 OPENCL_VENDOR_PATH で変更できます。

ICD のロードの中心となる処理は _find_and_check_platforms() です。

  • dlsym() で clGetExtensionFunctionAddress() のアドレスを得る
  • clGetExtensionFunctionAddress() で clIcdGetPlatformIDsKHR() のアドレスを得る
  • clGetExtensionFunctionAddress() で clGetPlatformInfo() のアドレスを得る
  • clIcdGetPlatformIDsKHR() でプラットフォーム数とプラットフォームの情報を得る

プラットフォームは説明が難しいですが、OpenCL API の実体+任意のドライバ固有のデータとでも言いましょうか。変数の型は cl_platform_id * 型です。cl_platform_id は少なくとも先頭のメンバは struct _cl_icd_dispatch *dispatch でなければなりません。dispatch の後ろには他の情報が入っていても問題ないようです。

cl_platform_id と dispatch

// ocl-icd/ocl_icd_loader.c

static inline void _find_and_check_platforms(cl_uint num_icds) {
  cl_uint i;

...

    cl_platform_id *platforms = (cl_platform_id *) malloc( sizeof(cl_platform_id) * num_platforms);
    error = (*plt_fn_ptr)(num_platforms, platforms, NULL);

...

    for(j=0; j<num_platforms; j++) {
      debug(D_LOG, "Checking platform %i", j);
      struct platform_icd *p=&_picds[_num_picds];
      char *param_value=NULL;
      p->extension_suffix=NULL;
      p->vicd=&_icds[i];
      p->pid=platforms[j];    //★★pid = platform ID のことらしい

      /* If clGetPlatformInfo is not exported and we are here, it
       * means that OCL_ICD_ASSUME_ICD_EXTENSION. Si we try to take it
       * from the dispatch * table. If that fails too, we have to
       * bail.
       */
      if (plt_info_ptr == NULL) {
        plt_info_ptr = p->pid->dispatch->clGetPlatformInfo;    //★★dispatch メンバが存在することを前提としている

      
// ocl-icd/khronos-headers/CL/cl.h

typedef struct _cl_platform_id *    cl_platform_id;


// ocl-icd/(build-dir)/ocl_icd_loader_gen.h

struct _cl_platform_id { struct _cl_icd_dispatch *dispatch; };    //★★dispatch 以外は特に規定がなさそう


// ocl-icd/(build-dir)/ocl_icd.h

struct _cl_icd_dispatch {
#ifdef CL_VERSION_1_0
  CL_API_ENTRY cl_int (CL_API_CALL*clGetPlatformIDs)(
    cl_uint          /* num_entries */,
    cl_platform_id * /* platforms */,
    cl_uint *        /* num_platforms */
  ) CL_API_SUFFIX__VERSION_1_0;
#else
  CL_API_ENTRY cl_int (CL_API_CALL* clUnknown0)(void);
#endif

#ifdef CL_VERSION_1_0
  CL_API_ENTRY cl_int (CL_API_CALL*
  clGetPlatformInfo)(
    cl_platform_id   /* platform */,
    cl_platform_info /* param_name */,
    size_t           /* param_value_size */,
    void *           /* param_value */,
    size_t *         /* param_value_size_ret */
  ) CL_API_SUFFIX__VERSION_1_0;
#else
  CL_API_ENTRY cl_int (CL_API_CALL* clUnknown1)(void);
#endif

...

//★★こんな調子で関数ポインタの定義が延々と続く

先頭の dispatch はたくさんの関数ポインタが並んだ巨大な構造体です。アプリケーションから見ると OpenCL の API は ocl-icd が提供しているように見えますが、ocl-icd の API 実装は dispatch の関数ポインタを呼ぶラッパー関数であり、関数ポインタと、OpenCL API 実装の本体を提供するのは各 ICD の役割です。

[編集者: すずき]
[更新: 2020年 7月 15日 20:37]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする




link permalink

link 編集する

MAD Tower Tycoon コンプリート

MAD Tower Tycoon でゾーン方式のエレベータの作り方を悩んで色々やっていたら、いつのまにかレベル 100、実績コンプリートしていました。ゲーム内時間は表示されないので詳しくはわかりませんが、おそらく 700日くらい?


MAD Tower Tycoon 実績コンプリート

The Tower と比べて色々思うところはありますが、総合的にみれば面白いと思います。

序盤だけ金欠になりやすいですが、難易度はかなり低めですし、ビル建築シミュレーション初めての方にオススメしたいゲームです。

高層ビルを作りたい

MAD Tower Tycoon は住人を追尾する機能があり、ビルの住人が困っていないか把握するのに便利です。基本的には目的地に行って、家に帰る(住人の場合)、もしくはビルから出る(ゲストの場合)だけですけど、大きなビルを作ると変な行動が目立ちます。

  • ビルの端から端まで歩いて、わざわざ遠いエレベーターで乗り換え
  • 近所のレストランを無視して、わざわざ遠いレストランに行く

前回(2020年 7月 10日の日記参照)も書きましたが、MAD Tower Tycoon はゾーン方式のエレベーターを作る方法がわかりません。今はメンテナンス施設の射程(上下 6F に効果がある)の関係で、7階+ 8階(※)おきに乗り換え階を作っています。

どうもこの方式だと 40階くらいで限界っぽいです。乗り換えに時間が掛かりすぎて、目的地に行くだけで半日費やしている気の毒な住民がいます。彼らはなぜか不満は言いませんが、見ていると不憫です……。

救いとしては MAD Tower Tycoon はビルの横幅がめちゃくちゃ広く取れるので、45階もあれば、レベル100、五つ星ビルが余裕で作れることです。だけど、やっぱり The Tower にあやかるなら、100階建て目指したいですよね?

(※)効率重視ならば 7F, 14F 乗り換えが最適ですが、スカイロビーを作成できるのは 15F からなので、あえて 14F を空きフロアにして、15F 乗り換えにしています。

どうしても The Tower と比較してしまう

MAD Tower Tycoon は、The Tower とかなり似ているがゆえに、つい比較してしまいます。

良いところ

  • エレベータ乗り換えがロビー階以外でも可能
  • 何度でも乗り継げる(1F → 7F → 15F → 23F のような乗り継ぎが可能)
  • かなり上の階でも階段で行ける(The Tower は確か 4F くらいしか上らない)

悪いところ

  • エレベータの通過階設定ができない(今、一番困っている制限)
  • 一度上に行ってから、下に行く乗り換えが不可能(1F → 15F → 7F みたいな乗り換えはしないようだ)

ゾーン方式のエレベーターが作れたら、高層ビルに効率的に人を運べるようになって、もっと面白くなるはずなのに。もったいないよ〜。

[編集者: すずき]
[更新: 2020年 7月 12日 19:53]

コメント一覧

  • わしだ 
    The Tower懐かしいですね。
    話題とずれる荒しみたいなコメントになりますが、旧姓でエゴサーチして最も古い自分の名前が出るのが、Geocitiesの他人のサイトに投稿したThe towerのビルでした。小6くらいだったのですが、本名とビル名に「第百ビル」とか付けてて。
    その10年以上後、新卒で会社入って最初の移動先の課長に「第百ビルの人?」って聞かれてびびりました。(会社入って一年くらいは旧姓だったので)
    いまはGeocitiesも閉鎖になったので出てきませんが、いい思い出ではあります。
    ということでとにもかくにも100階建てにしてチャペル建てたくなる気持ちを思い出しました。 
    (2020年08月10日 22:36:51)
  • すずき 
    小学生でサイトに投稿はスゴイです。そして会社バレしてるのも奇遇というかなんというか。
    The Tower で遊んでいたのは、たしか中学生時代だったと思いますが、当時インターネットとかパソコン通信はさっぱりわからず、ビルを投稿しようという考えが全くなかったです。
    Mad Tower Tycoon も 100階にチャペルを建てられますが、The Tower ほどの重要なポジションではない(集客施設の一つに過ぎない)です。星 3〜4辺りからゲーム性が極端に下がってダレてしまうのも、ちょっと残念なポイントです。 
    (2020年08月11日 18:59:01)
open/close この記事にコメントする



link permalink

link 編集する

STATIONflow 実績コンプリート

目次: STATIONflow - まとめリンク

STATIONflow の実績をコンプリートしました。「ラッキーセブン」と「東京」は自動化マクロを組まないと取れませんでした。


STATIONflow 実績コンプリート

本来、このゲームにハマって色々なマップをずっと遊んでいたらいつのまにか取れていた、というタイプの実績ですが、申し訳ないことに、私はそこまでの情熱がなかったです。

ゲームやったらわかりますけど、この 2つの実績だけ条件設定が異常すぎます。

  • 1日の利用者数は最大 17,000人程度(全出入口&乗り場が最大レベルの駅)
  • 1390万人達成には単純計算でゲーム内時間 800日程度
  • STATIONflow の 1日は最速でも 15分、普通に遊ぶと 30分〜1時間くらい
  • 最速でも 200時間は必要

実績の条件(総利用者数 700万人、1,390万人)がいかに異常かがわかると思います。

自動化

自動化の方法は簡単で、PowerShell で Enter キーを 3秒に 1回送るマクロを組んで、会社行っている間や夜間に放置するだけです。2週間くらいで取れました。

STATIONflow は放置しても悪いイベントが起きない(=駅が壊れない)優しい仕様になっているため、自動化+放置が可能でしたが、他のシミュレーションゲームだと偶発的に悪いイベントが起きるため、この方法は使えません。

まあ、明らかに製作者の想定した取り方ではないし、こんな方法で実績取っても嬉しくないし、無理に実績取るのは今後はやめておきます。

[編集者: すずき]
[更新: 2020年 9月 9日 10:39]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

MAD Tower Tycoon 楽しい

最近 Steam で買った、MAD Tower Tycoon(開発: Eggcode)で遊んでいます。


MAD Tower Tycoon

ビル建築&経営シミュレーションゲームです。プレイヤーはビルを拡張していって、テナントや住居を作ります。テナントには住人やゲストが押し寄せてきますので、住人のストレスを溜めないように、メンテナンス施設を足したり、階段、エスカレーター、エレベーターでうまく捌き、儲けを出して、ビルをさらに拡張させるというゲームです。

The Tower の後継者?

ビル建築&経営シミュレーションの名作は The Tower(開発: OPeNBooK)だと思います。The Tower は続編が出なくなって久しく寂しかったですが、MAD Tower Tycoon はかなり The Tower に近い作りです。

後追いだけあって基本的に The Tower より良くなっていますが、残念な部分もあって、

  • 上階に伸ばすときの建設が面倒くさい
  • 中盤から金が余りまくる
  • オフィス、住宅からの変な苦情(人通りが多い)
  • 横移動のストレスがない、もしくは非常に低い
  • エレベーターは速いが、自由度が低い

難易度は最近の風潮で優しめだとしても、最後のエレベーターの劣化は残念です。

エレベーターの変な仕様

エレベーターは The Tower のキモで、説明書でも詳しく説明していました。MAD Tower Tycoon のエレベーターの場合は、

  • エレベータのカゴ数が最大 3 で少ない
  • 通過階の設定ができない(降車専用ならある)

特に後者の制限が厳しくて、ゾーン方式(※)エレベーターが実質建設不可能で、高層ビルを作るには厳しい仕様となっています。

(※)エレベーターを複数基用意して、1つ目は 1〜5階のみ、2つ目は 5〜10階のみなど、一部の階しか止まらないエレベーターを作る方式のことです。現実でも高層ビルでよく見かけます。

むりやりゾーン方式エレベーターを作るとどうなる?

バグなのか仕様なのかわからないですが、MAD Tower Tycoon ではバルコニーでフロアをぶち抜いて、スカイブリッジを作ると、どことも繋がらない孤立したフロアを作れます。


普通の四角いビルを作成


バルコニーを作成


バルコニーを破壊すると、スカイブリッジが完成

孤立したフロアにエレベーターだけを設置することで、実質的に乗車降車禁止階にできます。


スカイブリッジ経由で利用してくれない

ところがこの乗車降車禁止エレベータを作っても、なぜか住人は一切利用してくれません。訳が分かりません。どうしたら良いんでしょうか??

オープンブックの紆余曲折

The Tower 開発元の OPeNBooK は合併と名前変更を繰り返しています。合併相手は 9003, inc で、AQUAZONE という熱帯魚育成&水槽シミュレーションで名を馳せたベンダーです。

1993                                          2000
OPeNBooK  ---,    1996                   ,--> オープンブック(The Tower の版権を持つ)
             +--> オープンブック 9003 ---+--> シノミクス
9003, inc ---'
1990

Wikipedia を見るとこんな経歴でした。お互い、元の鞘に収まったという感じがします。合併したけどやることがなかったんですかね?

[編集者: すずき]
[更新: 2020年 7月 12日 00:56]

コメント一覧

  • わしだ 
    オープンブック9003時代に鳥(Pinna?)飼ってました・・・熱帯魚はともかくなぜ鳥飼育ゲーを買ったのか、今となっては心境が分かりません・・・。 
    (2020年08月10日 22:40:38)
  • すずき 
    鳥のゲームは知りませんでした。色々やってるなあ。オープンブック……。
    あの頃は、PCの解像度がどんどん上がって綺麗になっていて、飼育ゲームとかアクアリウム的な、眺めるタイプの奴が流行ってましたよね。 
    (2020年08月11日 18:59:24)
open/close この記事にコメントする



link permalink

link 編集する

ノート PC の発熱を抑える方法 - その 2

前回(2020年 6月 29日の日記参照)、ノート PC の発熱を抑えるために CPU のクロック上限を抑える設定をしました。

前回は TurboBoost だけ無効化する方法がわかりませんでしたが、ググっていたら割と簡単に TurboBoost を無効化できることがわかりました。

レジストリエディタで、
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\PowerSettings\54533251-82be-4824-96c1-47b60b740d00\be337238-0d82-4146-a960-4f3749d470c7
の Attributes という DWORD 値を 1 から 0 or 2 に変更します(※)。

すると下記の「プロセッサ パフォーマンスの向上モード」オプションが出現しますので、無効を選択します。


TurboBoost を無効化すると、最大のプロセッサの状態を 100% にしても、ベース周波数の 1.6GHz までしか周波数が上がらなくなります。これは良い感じだ。

  • 〜54%: 0.90GHz
  • 〜60%: 0.99GHz
  • 〜65%: 1.10GHz
  • 〜71%: 1.19GHz
  • 〜76%: 1.30GHz
  • 〜82%: 1.39GHz
  • 〜87%: 1.49GHz
  • 〜100%: 3.36GHz → 1.59GHz

(※)私は Surface Pro で CPU クロックが最大に張り付くという問題(フォーラムへのリンク)の解決策に載っていたものを見つけたのですが、どうも昔の Windows 8 で消えた設定らしい(ASCII.jp : Windows 8.1で消えた詳細な電源管理項目を表示する!より)です。

[編集者: すずき]
[更新: 2020年 9月 23日 02:37]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

GCC を調べる - その 15-3 - ベクトルレジスタを使用する machine mode

目次: GCC を調べる - まとめリンク

前回(2020年 7月 7日の日記参照)はベクトルレジスタが選択されてしまう仕組みが何となくわかりました。

今回やりたかったことを復習しておくと「ベクトルの演算以外でベクトルレジスタを使わないでほしい」でした。つまり ira_prohibited_class_mode_regs[cl][j] のうちベクトル以外の machine mode かつベクトルレジスタに相当するビットを「セット」つまり割り当て禁止状態にすれば良いはずです。

配列の次元のうち cl はレジスタのクラス(enum reg_class)で、j は machine mode です。レジスタのクラスは以前(2020年 3月 28日の日記参照)ちょっとだけ使いました。幸いなことに、今回はレジスタのクラスは気にしなくて良いです、というかコードの if 文の条件 hard_regno_mode_ok(hard_regno, (machine_mode) j) を見るとわかるように、そもそもレジスタのクラスが渡されないので、ターゲット側(=RISC-V 依存の実装部分)で何もできないです。

重要なのは machine mode で、ベクトル以外のモード(浮動小数点など)だったらベクトルレジスタを割り当て禁止状態にすれば良いです。

targetm.hard_regno_mode_ok() の RISC-V 向け実装

// gcc/config/riscv/riscv.c

/* Implement TARGET_HARD_REGNO_MODE_OK.  */

static bool
riscv_hard_regno_mode_ok (unsigned int regno, machine_mode mode)
{
  unsigned int nregs = riscv_hard_regno_nregs (regno, mode);

  if (GP_REG_P (regno))
    {
      if (!GP_REG_P (regno + nregs - 1))
	return false;
    }
  else if (FP_REG_P (regno))
    {
      if (!FP_REG_P (regno + nregs - 1))
	return false;

      if (GET_MODE_CLASS (mode) != MODE_FLOAT
	  && GET_MODE_CLASS (mode) != MODE_COMPLEX_FLOAT)
	return false;

      /* Only use callee-saved registers if a potential callee is guaranteed
	 to spill the requisite width.  */
      if (GET_MODE_UNIT_SIZE (mode) > UNITS_PER_FP_REG
	  || (!call_used_or_fixed_reg_p (regno)
	      && GET_MODE_UNIT_SIZE (mode) > UNITS_PER_FP_ARG))
	return false;
    }
  else if (VP_REG_P (regno))    //★★前回足した実装
    {
      return true;
    }
  else
    return false;

...

ここでベクトル系の machine mode を直接記述(mode == V64SImode など)しても間違いではないと思うのですが、単純にモードがたくさんあると鬱陶しいですし、該当するモードがあとで増えたときの修正が大変です。こういうときは machine mode のクラスが便利です。クラスってなんだったかというと、machmode.def や riscv-modes.def に書いたあれです。

machine mode クラスの定義

// gcc/machmode.def

...

/* Basic integer modes.  We go up to TI in generic code (128 bits).
   TImode is needed here because the some front ends now genericly
   support __int128.  If the front ends decide to generically support
   larger types, then corresponding modes must be added here.  The
   name OI is reserved for a 256-bit type (needed by some back ends).
    */

//★★MODE_INT クラスになる

INT_MODE (QI, 1);
INT_MODE (HI, 2);
INT_MODE (SI, 4);
INT_MODE (DI, 8);
INT_MODE (TI, 16);

...


// gcc/config/riscv/riscv-modes.def

//★★MODE_FLOAT クラスになる

FLOAT_MODE (TF, 16, ieee_quad_format);

//★★以前、追加した実装
//★★VECTOR_MODE の場合は少し特殊で、MODE_VECTOR + 最初の引数 クラスになる
//★★この例だと MODE_VECTOR_INT になる

VECTOR_MODE (INT, SI, 32);
VECTOR_MODE (INT, SI, 64);

クラスは上記の通り各所の *.def にて定義されますが、正直言ってどこにあるかわかりにくいし、クラスの名前も見えません。machine mode とクラスの対応を確認するだけなら、ビルド時に生成される insn-modes.c を見たほうが早いです。

machine mode とクラスの対応、早見方法

// build_gcc/insn-modes.c

const unsigned char mode_class[NUM_MACHINE_MODES] =
{
  MODE_RANDOM,             /* VOID */
  MODE_RANDOM,             /* BLK */
  MODE_CC,                 /* CC */
  MODE_INT,                /* BI */
  MODE_INT,                /* QI */
  MODE_INT,                /* HI */
  MODE_INT,                /* SI */
  MODE_INT,                /* DI */
  MODE_INT,                /* TI */
  MODE_FRACT,              /* QQ */
  MODE_FRACT,              /* HQ */
  MODE_FRACT,              /* SQ */
  MODE_FRACT,              /* DQ */
  MODE_FRACT,              /* TQ */
  MODE_UFRACT,             /* UQQ */
  MODE_UFRACT,             /* UHQ */
  MODE_UFRACT,             /* USQ */
  MODE_UFRACT,             /* UDQ */
  MODE_UFRACT,             /* UTQ */
  MODE_ACCUM,              /* HA */
  MODE_ACCUM,              /* SA */
  MODE_ACCUM,              /* DA */
  MODE_ACCUM,              /* TA */
  MODE_UACCUM,             /* UHA */
  MODE_UACCUM,             /* USA */
  MODE_UACCUM,             /* UDA */
  MODE_UACCUM,             /* UTA */
  MODE_FLOAT,              /* SF */
  MODE_FLOAT,              /* DF */
  MODE_FLOAT,              /* TF */

...

  MODE_COMPLEX_FLOAT,      /* SC */
  MODE_COMPLEX_FLOAT,      /* DC */
  MODE_COMPLEX_FLOAT,      /* TC */
  MODE_VECTOR_INT,         /* V32SI */
  MODE_VECTOR_INT,         /* V64SI */
};

ベクトル系の machine mode を引っ掛けるには MODE_VECTOR_INT を使えば良さそうです。今の実装では使っていませんが RISC-V のベクトルは浮動小数点のベクトル(MODE_VECTOR_FLOAT)も扱えるはずなので、これも条件に追加しておきましょう。

targetm.hard_regno_mode_ok() の RISC-V 向け実装、修正版

// gcc/config/riscv/riscv.c

/* Implement TARGET_HARD_REGNO_MODE_OK.  */

static bool
riscv_hard_regno_mode_ok (unsigned int regno, machine_mode mode)
{
  unsigned int nregs = riscv_hard_regno_nregs (regno, mode);

  if (GP_REG_P (regno))
    {
      if (!GP_REG_P (regno + nregs - 1))
	return false;
    }
  else if (FP_REG_P (regno))
    {
      if (!FP_REG_P (regno + nregs - 1))
	return false;

      if (GET_MODE_CLASS (mode) != MODE_FLOAT
	  && GET_MODE_CLASS (mode) != MODE_COMPLEX_FLOAT)
	return false;

      /* Only use callee-saved registers if a potential callee is guaranteed
	 to spill the requisite width.  */
      if (GET_MODE_UNIT_SIZE (mode) > UNITS_PER_FP_REG
	  || (!call_used_or_fixed_reg_p (regno)
	      && GET_MODE_UNIT_SIZE (mode) > UNITS_PER_FP_ARG))
	return false;
    }
  else if (VP_REG_P (regno))    //★★前回足した実装
    {
      if (GET_MODE_CLASS (mode) != MODE_VECTOR_INT
	  && GET_MODE_CLASS (mode) != MODE_VECTOR_FLOAT)    //★★今回足した実装
	return false;

      return true;
    }
  else
    return false;

...

修正後のコンパイラは浮動小数点数を 60個使うコードを正常にコンパイルできます。たったこの 3行を説明するだけで、えらい時間を費やしました。GCC は魔界ですね。

お気づきの方もいるかと思いますが、実は 1つ上の else if 節にほぼ全く同じ判定文が既にあります。何も考えずに上からパクって MODE の名前を書き換えれば、今回の問題は直るんですけど、それだとどうして直るのか全くわからないんですよ……。

[編集者: すずき]
[更新: 2020年 7月 11日 04:55]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link permalink

link 編集する

GCC を調べる - その 15-2 - ベクトルレジスタが選ばれてしまう原因追跡

目次: GCC を調べる - まとめリンク

前回(2020年 7月 6日の日記参照)はエラー出力のコードを追いましたが、エラーの原因とは無関係でした。

問題解決のヒントは RTL のダンプファイルにあります。エラーを起こす 282r.reload の 1つ前のパス 281r.ira のダンプ(オプション --dump-rtl-all で出力できます)を見ると下記のような記述が見つかります。

エラーが起きたときの 281r.ira
...

      Popping a57(r107,l0)  -- assign reg 26
      Popping a58(r106,l0)  -- assign reg 27
      Popping a59(r105,l0)  -- assign reg 64    ★64は v0 レジスタの番号
      Popping a60(r104,l0)  -- assign reg 65    ★65は v1 レジスタの番号
      Popping a61(r165,l0)  -- assign reg 5

...

レジスタ番号にベクトルレジスタが出てきます。とても怪しいですね。このメッセージを出しているコードを追います。

281r.ira で Popping ... を出力しているコード

// gcc/ira-color.c

/* Pop the coloring stack and assign hard registers to the popped
   allocnos.  */
static void
pop_allocnos_from_stack (void)
{
  ira_allocno_t allocno;
  enum reg_class aclass;

  for (;allocno_stack_vec.length () != 0;)
    {
      allocno = allocno_stack_vec.pop ();
      aclass = ALLOCNO_CLASS (allocno);
      if (internal_flag_ira_verbose > 3 && ira_dump_file != NULL)
	{
	  fprintf (ira_dump_file, "      Popping");  //★★Popping はここで出力
	  ira_print_expanded_allocno (allocno);
	  fprintf (ira_dump_file, "  -- ");
	}
      if (aclass == NO_REGS)
	{
	  ALLOCNO_HARD_REGNO (allocno) = -1;
	  ALLOCNO_ASSIGNED_P (allocno) = true;
	  ira_assert (ALLOCNO_UPDATED_HARD_REG_COSTS (allocno) == NULL);
	  ira_assert
	    (ALLOCNO_UPDATED_CONFLICT_HARD_REG_COSTS (allocno) == NULL);
	  if (internal_flag_ira_verbose > 3 && ira_dump_file != NULL)
	    fprintf (ira_dump_file, "assign memory\n");
	}
      else if (assign_hard_reg (allocno, false))    //★★レジスタの割当をする
	{
	  if (internal_flag_ira_verbose > 3 && ira_dump_file != NULL)
	    fprintf (ira_dump_file, "assign reg %d\n",
		     ALLOCNO_HARD_REGNO (allocno));    //★★ assign reg 64 はここで出力
	}


// gcc/ira-build.c

/* This recursive function outputs allocno A and if it is a cap the
   function outputs its members.  */
void
ira_print_expanded_allocno (ira_allocno_t a)
{
  basic_block bb;

  //★★Popping の後ろの axx(rxx, lxx) を出力している

  fprintf (ira_dump_file, " a%d(r%d", ALLOCNO_NUM (a), ALLOCNO_REGNO (a));
  if ((bb = ALLOCNO_LOOP_TREE_NODE (a)->bb) != NULL)
    fprintf (ira_dump_file, ",b%d", bb->index);
  else
    fprintf (ira_dump_file, ",l%d", ALLOCNO_LOOP_TREE_NODE (a)->loop_num);
  if (ALLOCNO_CAP_MEMBER (a) != NULL)
    {
      fprintf (ira_dump_file, ":");
      ira_print_expanded_allocno (ALLOCNO_CAP_MEMBER (a));
    }
  fprintf (ira_dump_file, ")");
}


// gcc/ira-int.h

#define ALLOCNO_NUM(A) ((A)->num)
#define ALLOCNO_REGNO(A) ((A)->regno)
...
#define ALLOCNO_HARD_REGNO(A) ((A)->hard_regno)
...

281r.ira の Popping の値
いずれも ira_allocno_t のメンバを参照する。

(例)
Popping a59(r105,l0)  -- assign reg 64

  - a59   : num
  - r105  : regno
  - l0    : loop_num
  - reg 64: hard_regno

割り当てたレジスタ番号は allocno->hard_regno に格納されているようです。割り当てる関数はメッセージ出力のすぐ上にある assign_hard_reg() 関数が行います。

281r.ira でレジスタ割当を決めるコード

// gcc/ira-color.c

static bool
assign_hard_reg (ira_allocno_t a, bool retry_p)
{
  HARD_REG_SET conflicting_regs[2], profitable_hard_regs;
  int i, j, hard_regno, best_hard_regno, class_size;

...

  best_hard_regno = -1;

...

  /* We don't care about giving callee saved registers to allocnos no
     living through calls because call clobbered registers are
     allocated first (it is usual practice to put them first in
     REG_ALLOC_ORDER).  */
  mode = ALLOCNO_MODE (a);
  for (i = 0; i < class_size; i++)
    {
      hard_regno = ira_class_hard_regs[aclass][i];
#ifdef STACK_REGS
      if (no_stack_reg_p
	  && FIRST_STACK_REG <= hard_regno && hard_regno <= LAST_STACK_REG)
	continue;
#endif
      if (! check_hard_reg_p (a, hard_regno,
			      conflicting_regs, profitable_hard_regs))    //★★このチェックを通過すると、割り当てる候補になる
	continue;
      cost = costs[i];
      full_cost = full_costs[i];
      if (!HONOR_REG_ALLOC_ORDER)
	{

...

	}
      if (min_cost > cost)
	min_cost = cost;
      if (min_full_cost > full_cost
	  || (!HONOR_REG_ALLOC_ORDER && min_full_cost == full_cost
	      && best_hard_regno > hard_regno))
	{
	  min_full_cost = full_cost;
	  best_hard_regno = hard_regno;    //★★どのハードウェアレジスタに割り当てるか決まる
	  ira_assert (hard_regno >= 0);
	}
      if (internal_flag_ira_verbose > 5 && ira_dump_file != NULL)
	fprintf (ira_dump_file, "(%d=%d,%d) ", hard_regno, cost, full_cost);
    }

...

  if (! retry_p)
    restore_costs_from_copies (a);
  ALLOCNO_HARD_REGNO (a) = best_hard_regno;    //★★allocno->hard_regno に格納
  ALLOCNO_ASSIGNED_P (a) = true;
  if (best_hard_regno >= 0)
    update_costs_from_copies (a, true, ! retry_p);
  ira_assert (ALLOCNO_CLASS (a) == aclass);
  /* We don't need updated costs anymore.  */
  ira_free_allocno_updated_costs (a);
  return best_hard_regno >= 0;    //★★best_hard_regno に何か値が入っていれば成功
}

関数 assign_hard_reg() は色んな複雑なことをやっていますが、レジスタ割当の決め手は最後のループです。ループ内では基本的に先頭のレジスタから使います(※)。ループの先頭に check_hard_reg_p() というチェックがあって、このチェックをパスしたレジスタが割当の候補になります。

(※)厳密にはどのレジスタから割当を試みるかは、ira_class_hard_regs で決まります。この配列を初期化するのは setup_class_hard_regs() で、コードを見た感じだと順序を変更することもできるようです。今回、詳細は調べていません。

281r.ira でレジスタ割当候補を判断するコード

// gcc/ira-color.c

/* Return true if HARD_REGNO is ok for assigning to allocno A with
   PROFITABLE_REGS and whose objects have CONFLICT_REGS.  */
static inline bool
check_hard_reg_p (ira_allocno_t a, int hard_regno,
		  HARD_REG_SET *conflict_regs, HARD_REG_SET profitable_regs)
{
  int j, nwords, nregs;
  enum reg_class aclass;
  machine_mode mode;

  aclass = ALLOCNO_CLASS (a);
  mode = ALLOCNO_MODE (a);
  if (TEST_HARD_REG_BIT (ira_prohibited_class_mode_regs[aclass][mode],
			 hard_regno))    //★★このチェックを通過したら割当候補
    return false;
  /* Checking only profitable hard regs.  */
  if (! TEST_HARD_REG_BIT (profitable_regs, hard_regno))    //★★このチェックは基本的に通過するはず
    return false;

...


// gcc/ira-int.h

#define ALLOCNO_MODE(A) ((A)->mode)
...
#define ALLOCNO_CLASS(A) ((A)->aclass)
...

ベクトルレジスタ(番号 64 以降)が check_hard_reg_p() に渡されるときの aclass, mode の値を見ておきます。今はどうでも良いんですが、次の章で使います。

aclass, mode の値

ブレークに設定する条件は if hard_regno == 64

(gdb) p aclass
$4 = ALL_REGS

(gdb) p mode
$5 = E_SFmode

チェックのキモは ira_prohibited_class_mode_regs で、レジスタ番号が示す位置のビットがセットされていると、チェックに引っかかって、割り当て候補から外れます。この配列を初期化している箇所は setup_prohibited_class_mode_regs() です。

281r.ira でレジスタ割当候補を判断に使う ira_prohibited_class_mode_regs を初期化するコード

// gcc/ira.c

static void
setup_prohibited_class_mode_regs (void)
ira_prohibited_class_mode_regs
{
  int j, k, hard_regno, cl, last_hard_regno, count;

  for (cl = (int) N_REG_CLASSES - 1; cl >= 0; cl--)
    {
      temp_hard_regset = reg_class_contents[cl] & ~no_unit_alloc_regs;
      for (j = 0; j < NUM_MACHINE_MODES; j++)
	{
	  count = 0;
	  last_hard_regno = -1;
	  CLEAR_HARD_REG_SET (ira_prohibited_class_mode_regs[cl][j]);
	  for (k = ira_class_hard_regs_num[cl] - 1; k >= 0; k--)
	    {
	      hard_regno = ira_class_hard_regs[cl][k];
	      if (!targetm.hard_regno_mode_ok (hard_regno, (machine_mode) j))    //★★hard_regno_mode_ok() が true であれば割当候補になる
		SET_HARD_REG_BIT (ira_prohibited_class_mode_regs[cl][j],
				  hard_regno);
	      else if (in_hard_reg_set_p (temp_hard_regset,
					  (machine_mode) j, hard_regno))
		{
		  last_hard_regno = hard_regno;
		  count++;
		}
	    }
	  ira_class_singleton[cl][j] = (count == 1 ? last_hard_regno : -1);
	}
    }
}


// gcc/config/riscv/riscv.h

enum reg_class
{
  NO_REGS,			/* no registers in set */
  SIBCALL_REGS,			/* registers used by indirect sibcalls */
  JALR_REGS,			/* registers used by indirect calls */
  GR_REGS,			/* integer registers */
  FP_REGS,			/* floating-point registers */
  VP_REGS,			/* vector registers */
  FRAME_REGS,			/* arg pointer and frame pointer */
  ALL_REGS,			/* all registers */
  LIM_REG_CLASSES		/* max value + 1 */
};

#define N_REG_CLASSES (int) LIM_REG_CLASSES

配列の名前 prohibited の通り、この配列のビットがセットされているレジスタは割り当て「禁止」です。最初に配列のビットをクリアし(許可状態)、条件 targetm.hard_regno_mode_ok() が true だったら変更せず(許可状態のまま)、false だったらビットをセット(禁止状態)します。

次回は原因を修正します。

[編集者: すずき]
[更新: 2020年 7月 9日 22:34]

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
   2020年 7月 16日 -
      2020年 7月 7日  
link もっと後

管理用メニュー

link 記事を新規作成

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDF ファイル RSS 1.0
QR コード QR コード

最終更新: 9/24 21:43

カレンダー

<2020>
<<<07>>>
---1234
567891011
12131415161718
19202122232425
262728293031-

最近のコメント 5件

  • link 20年09月20日
    hdk 「最近は音楽聞く時やビデオ視聴時はミニコン...」
    (更新:09/24 21:43)
  • link 20年09月20日
    すずき 「ありゃー、同じ壊れ方ですね。\n新たなヘ...」
    (更新:09/24 00:23)
  • link 20年09月20日
    hdk 「うちのATH-AD300もやはり頭にプラ...」
    (更新:09/23 12:26)
  • link 20年07月10日
    すずき 「鳥のゲームは知りませんでした。色々やって...」
    (更新:08/11 18:59)
  • link 20年07月12日
    すずき 「小学生でサイトに投稿はスゴイです。そして...」
    (更新:08/11 18:59)

最近の記事 3件

link もっとみる
  • link 20年09月19日
    すずき 「[SHARP のマスク] 今となっては、高級マスクになってしまった...」
    (更新:09/23 04:48)
  • link 20年09月20日
    すずき 「[ヘッドフォンが壊れた] 以前(2012年 11月 8日の日記参照...」
    (更新:09/23 03:06)
  • link 20年09月22日
    すずき 「[3DMark] Steam のセールで 3DMark が意味不明...」
    (更新:09/23 02:55)

こんてんつ

open/close wiki
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報