link permalink

link 編集する

Amazon でよくあること

Amazon でハンダごて一式を購入しました。

まず、ハンダが来ました。

次に、ハンダこて台、ハンダ吸い取り線が来ました。

肝心の、ハンダごてが来ません……(後日、ハンダごても届きました)。

Amazon は一括で注文できますが、発送元の違いで一括で発送されないことは良くあります。たまに不思議な順番で送られてきてちょっと面白いですよね。

メモ: 技術系の話は Facebook から転記しておくことにした。

[編集者: すずき]
[更新: 2020年 5月 6日 22:29]

コメント一覧

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



link permalink

link 編集する

ThinkPad と追加の GPU

今、使ってるノート PC(ThinkPad E480 カスタムオーダー)には、Intel のグラフィクスと、Radeon RX 550 が搭載されています。

Radeon RX 550 はモバイル用としては結構強力で、主にゲームの時に助かっているのですが、Radeon を使ってゲームをしばらくやっていると、本体が触れないくらい熱くなり、しまいに熱暴走でクラッシュしてしまいます。

せっかくカスタムオーダーで追加したのに微妙な奴だな〜。

ドライバが挙動不審

この GPU はもうひとつ変なところがあって、デバイスマネージャで Radeon RX 550 を「無効」にして PC を再起動すると、冷却ファンが唸り続けます。

CPU 利用率は極めて低いままにも関わらず、冷却ファンが全力稼働しているので、ファンの制御がおかしくなっているように思います。GPU の負荷を勘違いしているのだろうか?

プロセスを片っ端から終了させても収まらなかったところをみると、恐らくドライバが変なところにハマっている?のでしょうか。

デバイスを有効にするか、有効にしたあとに再度無効にすれば、CPU ファンは静かになりますが、なんだか挙動不審ですよね……。

[編集者: すずき]
[更新: 2020年 5月 6日 23:40]

コメント一覧

  • hdk 
    デスクトップ用のNVIDIA Quadro FX 1400は電源投入時から冷却ファンが全開稼働で、グラフィックスドライバーが読み込まれる(Windowsの起動中画面が切り替わるとか、FreeBSDでプロプライエタリドライバーを入れたXサーバーが起動するとか)までそのままだったと思います。ノートPCでGPU用冷却ファンが別に搭載されているのかどうかは知りませんが、別に搭載されているなら似たような話がありそうですね。 
    (2020年05月07日 20:30:10)
  • すずき 
    結構、怖い制御に見えますね……。
    その世代の GeForce と Quadro って、ドライバがバグるとファンが回らなくなって、コアが焼けるやつでしたっけ? 
    (2020年05月08日 15:23:45)
  • すずき 
    ちょっと調べたところ、コアが焼けると騒ぎになった GPU Killing Driver が、
    GeForce/ION Driver 196.75
    というバージョンで、その世代の GPU は GeForce GTX 280 辺りでした。

    2004, NV40/41/45(130nm): GeForce 6800 GT: Quadro FX 1400
    2006, G71(90nm): GeForce 7900 GTX
    2007, G84(80nm): GeForce 8600 GT
    2008, G92(65nm): GeForce 9800 GTX
    2008, GT200(65nm): GeForce GTX 280

    Quadro FX 1400 は GeForce でいうと GeForce 6800 くらいなので、結構前ですね。前からこういう制御なのか……。怖い作りだなあ。 
    (2020年05月08日 15:43:30)
open/close この記事にコメントする



link permalink

link 編集する

C 言語のマクロ

C 言語のマクロによる置換を、循環参照させたらどうなるでしょう?

循環するマクロ定義

A B C D
#define A B
A B C D
#define B C
A B C D
#define C A
A B C D

結論から言うと問題ありません。下記のような結果になります。

循環するマクロ定義、置換結果

A B C D

B B C D

C C C D

A B C D

4つ目の結果は、置換前の A B C D と何も変わっていないように見えますが、実はそうではありません。下記のように定義するとわかります。

循環するマクロ定義、マクロによる置換結果を見やすくする

A B C D
#define A 1 B
A B C D 
#define B 2 C
A B C D 
#define C 3 A
A B C D
循環するマクロ定義、置換結果が見やすいはず

A B C D

1 B B C D

1 2 C 2 C C D

1 2 3 A 2 3 1 B 3 1 2 C D

4つ目の結果の「A」を例にとると、A -> B -> C -> A と 3回のマクロの置換が行われた結果、A に戻っているわけです。#define A B のマクロは 1度しか適用されないようです。

C 言語の仕様

C 言語の仕様(C11 final draft (N1570) - 6.10.3.4 Rescanning and further replacement の第 2項)を見ると、

2
If the name of the macro being replaced is found during this scan of the replacement list (not including the rest of the source file's preprocessing tokens), it is not replaced. Furthermore, if any nested replacements encounter the name of the macro being replaced, it is not replaced. These nonreplaced macro name preprocessing tokens are no longer available for further replacement even if they are later (re)examined in contexts in which that macro name preprocessing token would otherwise have been replaced.

(直訳)
2
置換されるマクロの名前が replacement list のスキャン中に見つかった場合(ソースファイルの残りの前処理トークンは含まれません)、そのマクロは置換されません。 さらに、入れ子になった置換が、置換されているマクロの名前に遭遇した場合、それは置換されません。 後にそのマクロ名の前処理トークンが置換されていたであろうコンテキストで(再)検査されても、これらの置換されていないマクロ名の前処理トークンはそれ以上の置換はできなくなります。

正直言って何言ってんだお前……?って感じがしますけども、平たく言うと同じマクロを 2回適用しない、ように読めます。

複雑版

下記のように同じマクロで何度も置換できそうなマクロを定義してみます。

循環するマクロ定義、より複雑版

#define A B C
#define B C A
#define C A B
A B C D
循環するマクロ定義、より複雑版、結果

A B A A C A B C B B A B C A C C B C D

1つ 1つのトークンがどのマクロで展開されているか図示します。


循環するマクロ定義、展開の様子

複雑に見えますが、どのトークンを見ても同じマクロを 2回適用されたものはないことがわかります。

関数型マクロの謎

しかし関数型マクロの場合は、不思議な挙動を示します。

循環する関数型マクロ定義

#define F(a) a G
#define G(a) a F(a)

F(7)(8)(9)
循環する関数型マクロ定義、結果


7 8 8 G(9)

展開の様子は下記のようになると思われますが、


循環する関数マクロ定義、展開の様子

どうして 7 8 8 G(9) で展開が終わるのかが良くわかりません……。マクロ F(a) を 2回適用しない、というルールならば、7 8 F(8)(9) で止まらなければおかしいように思いますが、結果を見るとなぜか F(8) も展開されています。

[編集者: すずき]
[更新: 2020年 5月 6日 23:04]

コメント一覧

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



link permalink

link 編集する

プレーヤー人口が増えるのは良いことだ

Splatoon 2 のガチマッチ(C ランク)の難易度が下がった気がします。

1〜2 か月前は、20kill 対 0kill で負けたり、1分でノックアウトされたり、超フルボッコで負けまくるのが当たり前で、すっかりやる気がなくなっていましたが、今日久しぶりにやったら一度も負けず、あっさり C+ ランクになりました。

アクションゲームの腕は 1か月やそこらで急に上達しないので、私が上達したというよりも、絶対勝てないおかしいレベルのプレーヤーとマッチする割合が減った、そんな感じです。

Stay Home とかゴールデンウイークとかで、Splatoon 2 のプレーヤー人口が盛り返して、初心者クラス(C-〜C+ ランクあたり)に合う人が増えたんじゃないかと推測しています。

最初からこのくらいの難易度だったら、ガチマッチ嫌いにならずに済んだんですけど、すっかりガチマッチ嫌いになってしまった(レギュラーマッチは好き)ので、もう遅いんだよな〜……。

[編集者: すずき]
[更新: 2020年 5月 6日 23:21]

コメント一覧

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



link permalink

link 編集する

RISC-V の gas

マクロの名前に Typo と思しきものがあったので、riscv-binutils-gdb(サイトへのリンク)に Pull Request をしてみました。

RISC-V 向けの gas の実装では、命令に対応した名前のマクロがあります。

命令の名前とマクロの名前の関係

//opcodes/riscv-opc.c

//通常は命令の名前からドットを除いて、大文字にした名前
//    vadd.vv -> MATCH_VADDVV

{"vadd.vv",    0, INSN_CLASS_V,  "Vd,Vt,VsVm", MATCH_VADDVV, MASK_VADDVV, match_opcode, 0 },
{"vadd.vx",    0, INSN_CLASS_V,  "Vd,Vt,sVm", MATCH_VADDVX, MASK_VADDVX, match_opcode, 0 },
{"vadd.vi",    0, INSN_CLASS_V,  "Vd,Vt,ViVm", MATCH_VADDVI, MASK_VADDVI, match_opcode, 0 },


//Reduce 系の命令だけ名前が違う
//    vredsum.vs -> MATCH_VREDSUMV"S" のはずなのに、MATCH_VREDSUMV"V" になっている

{"vredsum.vs", 0, INSN_CLASS_V, "Vd,Vt,VsVm", MATCH_VREDSUMVV, MASK_VREDSUMVV, match_opcode, 0},

パッチの中身は簡単で、ベクトル命令の一部で、命令の名前とマクロの名前が違っていたので修正しただけです。この手の間違いがいくつあるか分からなかったので、ちょっとした Python スクリプトを書いてチェックしました。

マクロの名前チェックに使ったスクリプト

#!/usr/bin/python

import re
import sys

fname = sys.argv[1]
f = open(fname, 'r')

line = f.readline()
while line:
	if not line.startswith('{"'):
		line = f.readline()
		continue;
	line = line.strip().replace('}', '')
	line = re.sub('\{"([^,]*)",', r'\1,', line)
	line = re.sub('".*",', '', line)
	line = re.sub(' *', '', line)
	items = line.split(',')

	insnOrg = items[0]
	insn = items[0].upper()
	classInsn = items[2]
	matchInsn = items[3]
	maskInsn = items[4]
	aliasInsn = items[6]
	if not classInsn.startswith('INSN_CLASS_V'):
		line = f.readline()
		continue;
	if not matchInsn.startswith('MATCH_') or not maskInsn.startswith('MASK_'):
		line = f.readline()
		continue;
	if aliasInsn.startswith('INSN_ALIAS'):
		line = f.readline()
		continue;

	insn = insn.replace('.', '')
	matchInsn = matchInsn.replace('MATCH_', '')
	maskInsn = maskInsn.replace('MASK_', '')
	if matchInsn != maskInsn:
		print("MATCH != MASK: {:s} != {:s}".format(matchInsn, maskInsn))
	if insn != matchInsn:
		print("INSN != MATCH: {:s} != {:s}".format(insnOrg, matchInsn))
	line = f.readline()

条件を適当に継ぎ足して書いたのと、Python の経験値が低いのが相まって、エレガントさの欠片もないですね。仕方ない。実行結果はこんな感じです。

マクロの名前チェックに使ったスクリプト
$ ../checker.py opcodes/riscv-opc.c

INSN != MATCH: vzext.vf2 != VZEXT_VF2
INSN != MATCH: vsext.vf2 != VSEXT_VF2
INSN != MATCH: vzext.vf4 != VZEXT_VF4
INSN != MATCH: vsext.vf4 != VSEXT_VF4
INSN != MATCH: vzext.vf8 != VZEXT_VF8
INSN != MATCH: vsext.vf8 != VSEXT_VF8
INSN != MATCH: vredsum.vs != VREDSUMVV
INSN != MATCH: vredmaxu.vs != VREDMAXUVV
INSN != MATCH: vredmax.vs != VREDMAXVV
INSN != MATCH: vredminu.vs != VREDMINUVV
INSN != MATCH: vredmin.vs != VREDMINVV
INSN != MATCH: vredand.vs != VREDANDVV
INSN != MATCH: vredor.vs != VREDORVV
INSN != MATCH: vredxor.vs != VREDXORVV
INSN != MATCH: vwredsumu.vs != VWREDSUMUVV
INSN != MATCH: vwredsum.vs != VWREDSUMVV
INSN != MATCH: vfredosum.vs != VFREDOSUMV
INSN != MATCH: vfredsum.vs != VFREDSUMV
INSN != MATCH: vfredmax.vs != VFREDMAXV
INSN != MATCH: vfredmin.vs != VFREDMINV
INSN != MATCH: vfwredosum.vs != VFWREDOSUMV
INSN != MATCH: vfwredsum.vs != VFWREDSUMV
INSN != MATCH: vcompress.vm != VCOMPRESSV

明らかに Typo に見えるのは vred/vfred/vcompress 系の命令で、vs と vv を取り違えています。

微妙なところなのは vzext です。他はドットを除いた名前なのに、vzext だけドットをアンダースコアに置換した名前です。ルールに一貫性が無いだけか、Typo か、どちらとも言い難いため、今回出した Pull Request では修正していません。

Pull Request 受け付けてるのかな?

リポジトリを見ていてちょっと気になったのは SiFive の人以外、変更がほとんどないことです。著名プロジェクトでは珍しいです。もしかすると GitHub で Pull Request を受け付けてない(※1)可能性があります。

変更を提案するのはここじゃないとか、そもそも変更は受け付けてませんとか、何でも良いので反応があると嬉しいですね、週明けまで待ちましょうかね……。

(※1)本家および開発の場が GitHub 以外に存在していて、GitHub をミラーにしているプロジェクトの場合、GitHub 上で何か言っても無視されることがあるようです。

[編集者: すずき]
[更新: 2020年 5月 10日 15:44]

コメント一覧

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



link permalink

link 編集する

Kindle for PC はダウンロードが遅い?

Kindle のアプリは Kindle Fire 版、Android 版、PC 版など、いくつか種類があります。普段使っているのは Kindle Fire と Kindle for PC です。どうも Kindle for PC のダウンロードが遅い気がします。

Kindle Fire も決して速いとは思いませんが、大抵はマンガ 1冊が 1〜2分でダウンロードできているので、5Mbps くらいは出ているんじゃないかと思います。

Kindle for PC はかなり遅い(1〜2Mbps くらい、日によって違う)です。同じネットワークを使っているのに、差が出るものですかね?PC 向けだけ帯域ケチるとか、そんな面倒なことしないよなあ?うーん?

Kindle アプリの動きがまた変わったけど、いつもどこかがダメ

Kindle Fire HD のアプリはたまにアップデートされて動きが変わります。今年の頭くらいだったか?覚えてないですけど、また動作が変わりました。

良くなったところ
フィルター「すべて」を選択したとき、本が多すぎると、数十秒フリーズしたり、アプリがクラッシュするバグが直ったこと
悪くなったところ
本のグループが意図せず表示されてしまうバグが埋め込まれたこと

新たなバグは再現率 100%です。再現方法も簡単です。

  • ホーム → 「本」のタブ → すべての本を表示、をタップ
  • 本の一覧が表示される
  • グループ化された本をタップ(マンガの1〜最新巻までをグルーピングする機能)を選択
  • ホームボタンでホームへ → 「本」のタブ → すべての本を表示、をタップ
  • 先ほど見ていたグループ化された本が表示される(バグ)

この順に操作したとき、本来は本の一覧が出なければなりませんが、グループ化された本が再表示されてしまいます。明らかにバグってます。

このバグは、ユーザー側の操作で回避可能です。

  • グループ化された本が意図せず表示される(バグ)
  • ホームボタン「ではなく」戻るボタンでホームに戻る
  • もう一度、すべての本を表示、をタップ
  • 本の一覧が表示される

ユーザーの操作に影響が出るバグですし、テスターに触らせたら数分で見つけそうなのにね?Kindle って UI のテストしてないのかなあ??

[編集者: すずき]
[更新: 2020年 5月 13日 23:56]

コメント一覧

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



link permalink

link 編集する

Python の変な API

Python の文字列置換は "string".replace() ですが、正規表現ライブラリ re だと、なぜか re.sub() です。同じ機能なのに、API の名前も、引数の指定順序も違います。どうしてこうなった。

改定の度に魔界化する C/C++ に比べると、Python は明瞭に思えます。とはいえ Python も何だかんだ長い歴史ですし、祓いきれない闇があるんでしょうねえ。

メモ: 技術系の話は Facebook から転記しておくことにした。

[編集者: すずき]
[更新: 2020年 5月 13日 21:44]

コメント一覧

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



link permalink

link 編集する

ヤマザキ春のパン祭り

今年のヤマザキ春のパン祭りは、2枚もゲットできました。


2020年、春のパン祭りの戦利品

去年は品川勤務かつコンビニ昼飯がメインだったので、ヤマザキパンを買う機会がほぼなく、お皿をもらえるほど点がたまりませんでした。今年は COVID-19 による在宅勤務で、近所のスーパーでパンを買う機会が大幅に増えたため、2枚も手に入ったわけです。在宅勤務の意外な効果です。

コンビニに行かない人にとっては意外かもしれませんが、コンビニはヤマザキパンをほとんど置いていません。自社のプライベートブランドのパンばかりです。10年位前は他社のパンも置いていたんですが、今やコンビニは 9割方がプライベートブランドのパンです。

コンビニのプライベートブランドのパンは、大手メーカー(ヤマザキパン、敷島製パン、フジパンなど)が作っていますが、コストの都合か何だか知りませんが、味に劣る気がします。好みの問題なのかな……?

強化ガラス

パン祭りのお皿、裏側に何か書いてるなーと思って写真を撮ってみました。


お皿の裏の説明

ちょっと見づらいので文字に起こすと、

ARTICLE YAMAZAKI
MADE IN FRANCE

ZENMEN
BUTSURIKYOUKA
GARASU

「全面物理強化ガラス」ってローマ字で書いてありますね。フランス製なのに何でローマ字?フランス語で書かれても読めないから?

見ていて素朴な疑問が沸きました。あえて「全面」と強調する理由はなんでしょうね?皿のように厚みのない製品で「片面」物理強化ガラスにすることは可能?可能だったとしてもやる意味がある?

[編集者: すずき]
[更新: 2020年 5月 13日 23:29]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 9 - ベクトル型用マシンモードの追加

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

以前(2020年 3月 27日の日記2020年 3月 28日の日記2020年 3月 29日の日記参照)ベクトルレジスタを扱えるようにした際に、下記の問題が残っていました。

  • 変数が int なので sizeof(v) が 4 になる、ベクトルを扱いたい
  • 最適化オプションを O0 にするとコンパイラが internal error を出す

1つ目の問題に取り組んでいきたいと思います。RISC-V 32 の場合、int の変数は 32bit 整数のデータ型として扱われます。GCC 内部の表現(RTL)では SImode というマシンモード(※)で表されます。他の大きさのデータ型を示すマシンモードも当然存在していて 8, 16, 64bit 整数はそれぞれ BImode, HImode, DImode で表されます。

普通の型に対応するモードは GCC が定義済みですが、ベクトル型を表すモードは標準では存在しないため、自分で新規に定義する必要があります。

(※)マシンモード(Machine Mode)については、GCC Internals の 14.6 Machine Modes に簡単な説明と標準的なモードの一覧が載っています。これによれば SI は Single Integer の略らしいです。変な名前だなあ。

マシンモードはどこにある?

以前(2020年 3月 14日の日記参照)説明したとおりですが、軽くおさらいすると、標準的なマシンモードは gcc/machmode.def、アーキテクチャ固有のマシンモードは gcc/config/arch/arch-modes.def にあります。例えば RISC-V なら gcc/config/riscv/riscv-modes.def です。

現在のところ RISC-V 固有のマシンモードは 1つしか定義されていません。

riscv-modes.def

FLOAT_MODE (TF, 16, ieee_quad_format);

このファイルにベクトル型を表すマシンモードを追加します。

riscv-modes.def に追加したモード

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

とりあえず整数(INT, SI)が 8, 16, 32, 64(それぞれ 32, 64, 128, 256 バイト)個連結されているデータ型を想定して作りました。ベクトル型を語る上では浮動小数点型も大事ですが、とりあえず今回は整数型のみを定義しています。

マシンモード追加できたかな

マシンモードを正しく追加できたか確かめる方法は色々あるのでしょうけど、個人的に簡単だと思うのは一旦ビルドしてしまう方法です。

GCC をビルドするとビルド用のディレクトリ(以降 build_gcc と呼びます)に、モードが全部書いてあるヘッダ insn-modes.h が生成されます。生成されたヘッダを検索すれば一発です。

モードが定義されているヘッダ

// gcc/build_gcc/insn-modes.h

enum machine_mode
{
  E_VOIDmode,              /* machmode.def:189 */
#define HAVE_VOIDmode
#ifdef USE_ENUM_MODES
#define VOIDmode E_VOIDmode
#else
#define VOIDmode ((void) 0, E_VOIDmode)
#endif
  E_BLKmode,               /* machmode.def:193 */
#define HAVE_BLKmode
#ifdef USE_ENUM_MODES
#define BLKmode E_BLKmode
#else
#define BLKmode ((void) 0, E_BLKmode)
#endif

...

  E_V32SImode,             /* config/riscv/riscv-modes.def:26 */
#define HAVE_V32SImode
#ifdef USE_ENUM_MODES
#define V32SImode E_V32SImode
#else
#define V32SImode ((void) 0, E_V32SImode)
#endif
  E_V64SImode,             /* config/riscv/riscv-modes.def:27 */
#define HAVE_V64SImode
#ifdef USE_ENUM_MODES
#define V64SImode E_V64SImode
#else
#define V64SImode ((void) 0, E_V64SImode)
#endif

...

新たなマシンモード V64SImode(SI が 64個連結されたデータ型)が追加されたことがわかると思います。コメントにマシンモードの定義されている場所も書かれていて、とても親切です。

ほぼ全域に渡って意味不明コードだらけの GCC では珍しい部類の、わかりやすさ&親切さです。自動生成コードには気を使っているんでしょうか?他のところもこれくらい親切だと嬉しいんですけどねえ。

[編集者: すずき]
[更新: 2020年 6月 1日 14:07]

コメント一覧

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



link permalink

link 編集する

ノート PC のファンがうるさい

ノート PC(ThinkPad E480)の冷却ファンが回り続けていてうるさいです。特に何かしている訳でもないのに、良い勢いでファンが回ってます。

ここ最近、ノート PC を酷使(ゲーム、在宅勤務など)したため、ファンに埃が詰まって、冷却能力が下がったか?と予想して、頑張って ThinkPad の裏蓋を開けました。しかし思ったほど汚れていません。

どうしてファンが回りっぱなしなんでしょう?純粋に冷却能力が足りないだけなんだろうか??

ThinkPad の裏蓋

ThinkPad E480 の裏蓋はめちゃくちゃ開けにくいです。ネジ 9か所と爪で止まっているので、ネジを緩めた後、マイナスドライバーでこじ開けるしかありません。

爪を外すとき、バキっ!ベキっ!というすごい嫌な音がします。案の定、ヒンジ付近(ノート PC とノート PC ディスプレイが接続されている方)の爪が 4か所ほど折れました。

せっかく裏蓋まで開けたにも関わらず、何も収穫がありませんでした。裏蓋を止める爪が 4か所壊れただけです。相変わらずファンはうるせーし、嬉しくない結末です。
[編集者: すずき]
[更新: 2020年 5月 14日 00:24]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 10-1 - ベクトル型を使うとエラー(TARGET_VECTOR_MODE_SUPPORTED_P 追加編)

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

以前(2020年 3月 27日の日記2020年 3月 28日の日記2020年 3月 29日の日記参照)ベクトルレジスタを扱えるようにした際に、下記の問題が残っていました。

  • 変数が int なので sizeof(v) が 4 になる、ベクトルを扱いたい
  • 最適化オプションを O0 にするとコンパイラが internal error を出す

前回(2020年 5月 12日の日記参照)はベクトル型に向けてマシンモードを追加しました。引き続き、1つ目の問題に取り組んでいきたいと思います。

ベクトル型は基本型(SI, DI, SF, DF など)が複数連結されているデータ型です。個数は 2のべき乗(2, 4, 8, 16, ...)でなければなりません、3 個や 10 個はダメです。型は通常 GCC の実装で定義します。ベクトル型も当然同じで GCC の実装で定義しますが、ベクトル型はやや特殊で、GCC の attribute でも定義することができます。今回は attribute を使ってみます。

ベクトル型を使ったコード

typedef int __v64si __attribute__((__vector_size__(256)));

void _start()
{
	int b[100];
	__v64si v1;

	__asm__ volatile ("vlw.v %0, %1\n"
		: "=&v"(v1) : "A"(b[10]));
}

ベクトル型を使ってインラインアセンブラを書くとエラーが出ます。

ベクトル型を使うと impossible constraint エラー
$ riscv32-unknown-elf-gcc -Wall -march=rv32gcv b.c -O2 -nostdlib -S

b.c: In function '_start':
b.c:7:2: error: impossible constraint in 'asm'
    7 |  __asm__ volatile ("vlw.v %0, %1\n"
      |  ^~~~~~~

このエラーは以前(2020年 3月 6日の日記参照)、register constraint に 'v' を追加したときに解析した部分で見ました。詳細は昔の日記を見ていただくとして、以前との差を示します。

エラーになる箇所

// gcc/recog.c

int
asm_operand_ok (rtx op, const char *constraint, const char **constraints)
{

...

	default:
	  cn = lookup_constraint (constraint);
	  switch (get_constraint_type (cn))
	    {
	    case CT_REGISTER:
	      if (!result
		  && reg_class_for_constraint (cn) != NO_REGS  //★★以前引っかかっていたのはこちら
		  && GET_MODE (op) != BLKmode        //★★今回はこちらの条件に引っかかる
		  && register_operand (op, VOIDmode))
		result = 1;
	      break;

新たなマシンモード V64SImode を追加(2020年 5月 12日の日記参照)を追加したのに、どうして BLKmode が選択されてしまうのでしょう?

オプション --dump-tree-all --dump-rtl-all を付けて、BLKmode が選ばれるタイミングを追うと、パス expand が終わった時点で BLKmode になっていました。

236r.expand の抜粋

(insn 26 7 10 2 (set (mem/c:BLK (plus:SI (reg/f:SI 99 virtual-stack-vars)  ★★mem/c:BLK になっている
                (const_int -1168 [0xfffffffffffffb70])) [1  A128])
        (asm_operands/v:BLK ("vlw.v %0, %1") ("=&v") 0 [
                (mem/c:SI (plus:SI (reg/f:SI 99 virtual-stack-vars)
                        (const_int -872 [0xfffffffffffffc98])) [1 b+40 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:7)
            ]
             [] b.c:7)) "b.c":7:2 -1
     (nil))

パス expand は GIMPLE から RTL という中間表現に変換するパスです。RTL に変換した直後から BLKmode ですから、かなり最初の方からダメだってことがわかります。何が悪いかわからないので expand 辺りのコードを探ってみます。

BLKmode になる箇所

// gcc/cfgexpand.c

static void
expand_asm_stmt (gasm *stmt)
{

...

  for (i = 0; i < noutputs; ++i)
    {
      tree val = output_tvec[i];
      tree type = TREE_TYPE (val);
      bool is_inout, allows_reg, allows_mem, ok;
      rtx op;

...

      if ((TREE_CODE (val) == INDIRECT_REF && allows_mem)
	  || (DECL_P (val)
	      && (allows_mem || REG_P (DECL_RTL (val)))
	      && ! (REG_P (DECL_RTL (val))
		    && GET_MODE (DECL_RTL (val)) != TYPE_MODE (type)))
	  || ! allows_reg
	  || is_inout
	  || TREE_ADDRESSABLE (type))
	{

...
	}
      else
	{
	  op = assign_temp (type, 0, 1);  //★★これ
	  op = validize_mem (op);
	  if (!MEM_P (op) && TREE_CODE (val) == SSA_NAME)
	    set_reg_attrs_for_decl_rtl (SSA_NAME_VAR (val), op);

	  generating_concat_p = old_generating_concat_p;

	  push_to_sequence2 (after_rtl_seq, after_rtl_end);
	  expand_assignment (val, make_tree (type, op), false);
	  after_rtl_seq = get_insns ();
	  after_rtl_end = get_last_insn ();
	  end_sequence ();
	}


// gcc/function.c

rtx
assign_temp (tree type_or_decl, int memory_required,
	     int dont_promote ATTRIBUTE_UNUSED)
{
  tree type, decl;
  machine_mode mode;
#ifdef PROMOTE_MODE
  int unsignedp;
#endif

  if (DECL_P (type_or_decl))
    decl = type_or_decl, type = TREE_TYPE (decl);
  else
    decl = NULL, type = type_or_decl;

  mode = TYPE_MODE (type);  //★★これ


// gcc/tree.h

#define TYPE_MODE(NODE) \
  (VECTOR_TYPE_P (TYPE_CHECK (NODE)) \
   ? vector_type_mode (NODE) : (NODE)->type_common.mode)    //★★これ


// gcc/tree.c

/* Vector types need to re-check the target flags each time we report
   the machine mode.  We need to do this because attribute target can
   change the result of vector_mode_supported_p and have_regs_of_mode
   on a per-function basis.  Thus the TYPE_MODE of a VECTOR_TYPE can
   change on a per-function basis.  */
/* ??? Possibly a better solution is to run through all the types
   referenced by a function and re-compute the TYPE_MODE once, rather
   than make the TYPE_MODE macro call a function.  */

machine_mode
vector_type_mode (const_tree t)
{
  machine_mode mode;

  gcc_assert (TREE_CODE (t) == VECTOR_TYPE);

  mode = t->type_common.mode;  //★★このモードは V64SImode になる
  if (VECTOR_MODE_P (mode)
      && (!targetm.vector_mode_supported_p (mode)  //★★この判定文が偽になる
	  || !have_regs_of_mode[mode]))
    {
      scalar_int_mode innermode;

      /* For integers, try mapping it to a same-sized scalar mode.  */
      if (is_int_mode (TREE_TYPE (t)->type_common.mode, &innermode))  //★★256バイトの Int はないから、偽になる
	{
	  poly_int64 size = (TYPE_VECTOR_SUBPARTS (t)
			     * GET_MODE_BITSIZE (innermode));
	  scalar_int_mode mode;
	  if (int_mode_for_size (size, 0).exists (&mode)
	      && have_regs_of_mode[mode])
	    return mode;
	}

      return BLKmode;  //★★BLKmode になってしまう
    }

  return mode;
}

条件式にある targetm.vector_mode_supported_p() が false のため、BLKmode になってしまうようです。

[編集者: すずき]
[更新: 2020年 6月 1日 14:10]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 10-2 - ベクトル型への対応(TARGET_VECTOR_MODE_SUPPORTED_P 追加編)

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

前回(2020年 3月 27日の日記参照)の調査により、targetm.vector_mode_supported_p() が false を返すことが原因だとわかりました。この関数ポインタが指す先は、アーキテクチャターゲット(ARM とか i386 とか riscv とか)ごとに違います。RISC-V の場合は下記のようにすれば良いです。

vector_mode_supported_p の追加

// gcc/config/riscv/riscv.c

static bool
riscv_vector_mode_supported_p (machine_mode mode)
{
  if (TARGET_VECTOR)  //★★本当は mode の値を確認したほうが良いが、今回は手抜き
    return true;

  return false;
}

...

#undef TARGET_VECTOR_MODE_SUPPORTED_P
#define TARGET_VECTOR_MODE_SUPPORTED_P riscv_vector_mode_supported_p

この targetm もやや魔界感があるので、何でこの define で実装が切り替わるのか、についても調べたいところですね。それはさておいて、上記の実装を追加すると無事ベクトル型が使えるようになります。

追加後の 236r.expand の抜粋

(insn 70 2 69 2 (set (reg:V64SI 105 [ v1 ])  ★★reg:V64SI になっている
        (asm_operands/v:V64SI ("vlw.v %0, %1") ("=&v") 0 [
                (mem/c:SI (plus:SI (reg/f:SI 99 virtual-stack-vars)
                        (const_int -360 [0xfffffffffffffe98])) [1 b+40 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:7)
            ]
             [] b.c:7)) "b.c":7:2 -1
     (nil))

バイナリも正しく出力されます。sizeof(v1) も 256 が返されます。ちなみにアセンブルしなくても、オプション -S でアセンブラを見てもほぼ同じです。

追加後のバイナリ
a.out:     file format elf32-littleriscv

Disassembly of section .text:

00010054 <_start>:
   10054:       7165                    addi    sp,sp,-400
   10056:       103c                    addi    a5,sp,40
   10058:       1207e007                vlw.v   v0,(a5)
   1005c:       6159                    addi    sp,sp,400
   1005e:       8082                    ret

良かった良かった。オプション O0 の問題はまた今度です。

[編集者: すずき]
[更新: 2020年 6月 1日 14:10]

コメント一覧

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



link permalink

link 編集する

航空科学博物館を支援

Twitter で「収入ほぼゼロ」「来館者9割減」 危機に陥っている「航空科学博物館」がクラウドファンディングで支援募集 - ねとらぼ、の記事を目にしたので、微力ながら 5,000円を支援しました。後で入場券が 2枚送られてくるそうな。

100万円のコースはさすがに高すぎなのか支援者がまだ現れていませんが、30万円のコースは 3人ほど支援者がいました。愛されていますね。

公営の博物館(公益財団法人 航空科学博物館)は、国や自治体が支援するのが筋だろう、という意見も目にしましたし、言い分はわかるんですが、うだうだ言っている間に潰れてしまっては元も子もないです。クラウドファンディングで助けを求めるのは良い手段だと思います。

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

コメント一覧

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



link permalink

link 編集する

STATIONflow 始めました、超えろ、新宿駅

STATIONflow の日記一覧。

Steam で新たなゲームを買いました。2020年 4月ローンチの STATIONflow(開発: DMM GAMES)です。その名の通り地下鉄の駅を作るゲームです。


STATIONflow ロゴ

新宿や渋谷のようなモンスター駅の作り、使いづらさに憤慨したことはありませんか?「使いづらい!俺様に任せればスマートに作ってやる」と思う方はぜひチャレンジしてください。

私もそう思ってたのですが、ゲームをやってみて「思い上がりでした、ごめんなさい、JR さん尊敬してます。」反省しました。油断するとすぐに、客を何度も上り下りさせて、べらぼうに長距離歩かせて、挙句迷子になるクソ駅が爆誕します。

良いところ、嫌なところ

(気に入ったところ)

  • 動作が非常に快適。特に起動が超速くて、ノート PC でも数秒で起動します。Transport Fever 2 は寝そうなくらい遅くて辛かったので、これは嬉しいです。

(気に入らないところ)

  • 今年のゲームの割にグラフィックスがショボい。殺人現場の再現グラフィックみたいに見えます。
  • 操作方法が変。カメラワークも変ですし、一番嫌なのがマップスクロールで、なぜかミドルドラッグです。右ドラッグが普通では?

ゲームはまだ始めたばかりなので、主に操作性の面での感想です。

ゲームシステム紹介

プレイ時間が 10時間未満で、序盤しか体験してませんが、STATIONflow のシステム紹介もしておきます。システムはそんなに難しくないです。

システム紹介: 客の動きと構内案内

マップには地下鉄の改札口と、電車の到着ホームが固定で置かれます。お客さんの行動は基本的に 3つです。

  • 改札←→改札(通過)
  • ホーム←→改札(乗降車)
  • ホーム←→ホーム(乗り換え)

他の交通系ゲームと大きく違う点は、客は「最適経路を知らない」ことです。客は「構内案内」を見て行動します。目的地への経路が存在しても、構内案内を間違えると客は目的地に行けなくて迷います。視界の広さがお客さんによって違い、遠いところに構内案内を置くと、客によっては構内案内を見失って迷います。


駅のホーム、ところどころにある黄色い矢印が構内案内

遠回りで変なシステムに見えますが、よく考えると、すべての客が駅構内の経路を知り尽くしていて、常に最短経路で移動するって変じゃないですか?そんなエスパーみたいな客いませんよね。

正直いって構内案内のメンテナンスはかなり面倒ですが、ゲームとして理不尽にならないレベル(※)で現実に寄せた、素敵なアイデアだと思います。

(※)実世界だと、構内案内をあえて無視して迷う、目の前の構内案内に気づかない、話の通じないクレーマー、など不条理ばかりですが、そんなの再現してもクソゲーにしかなりません。現実に寄せれば良いってものじゃない。

システム紹介: ランクアップと構内施設

乗降客数が増えて、お客さんの不満が少ないとランクアップし、ホームと改札口が勝手に増えます。大抵は予想していない変なところにホームが勝手に増えるので、駅のレイアウトと合わなくなります。つじつま合わせが必要です。ここは腕の見せどころです。

ランクアップに伴い、トイレやレストランなどの構内施設の種類が増えます。エスカレーターしか乗らない高齢客、エレベーターしか乗らない車いす客、などお客さんのバリエーションも増えます。序盤は簡単で、ランクアップによって難易度が段々上がっていく設計に見えます。

ホームと改札の位置はマップにより固定のようです。最後まで進めて最初からやり直せば、綺麗な駅が作れるかもしれません(まだそこまで見てないので断言はできませんけど)。

メモ: 技術系の話は Facebook から転記しておくことにした。加筆&修正した。

[編集者: すずき]
[更新: 2020年 8月 11日 19:11]

コメント一覧

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



link permalink

link 編集する

さようなら SeaMonkey

かつての Mozilla のころからずっと使っていた SeaMonkey ですが、最近のサイトだと知らない子扱いされて悲しいのと、Cookie の削除がしづらくてたまらないので、ブラウザを Firefox に変えました。

SeaMonkey 今までありがとう。大変お世話になりました。

こんにちは Firefox

ブラウザに限らず、あまりアプリケーションのカスタマイズはしない方ですが、今の Windows 版 Firefox は 1点だけ嫌なところがあります。Ctrl+Tab を押したときにタブの一覧が出る挙動です。

Windows のウインドウ切り替えと似ていますが、ブラウザのタブは元から横一直線に並んでいるので、リストを出す必要はないです。隣のタブに迅速に切り替えてほしい。

調べてみると browser.ctrlTab.recentlyUsedOrder を false にすると Ctrl+Tab を押した瞬間に隣のタブに移る挙動に戻せることを知りました。とても嬉しい。これだよこれ!

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

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 11-1 - オプション O0 のエラー調査(define_expand 追加編)

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

前回(2020年 5月 16日の日記2020年 5月 17日の日記参照)の取り組みでインラインアセンブラでベクトル型を指定できるようになりましたが、下記の問題が残っていました。

  • 最適化オプションを O0 にするとコンパイラが internal error を出す

いよいよコンパイラが Internal compile error を出す問題に取り組みます。ベクトル型を使ったインラインアセンブラを O0 でコンパイルすると、下記のようなエラーメッセージが出ます。

O0 のときのエラーメッセージ
b.c: In function '_start':
b.c:25:1: internal compiler error: maximum number of generated reload insns per insn achieved (90)
   25 | }
      | ^

このエラーが出るパスは reload です。問題のある箇所に当たりをつけるため、O0 の reload と、1つ前のパス ira の RTL を比べます。

O0 の 281r.ira

;;★★asm 文

(insn 74 7 10 2 (set (reg:V64SI 109 [ v1 ])
        (asm_operands/v:V64SI ("vlw.v %0, %1") ("=&v") 0 [
                (mem/c:SI (plus:SI (reg/f:SI 97 frame)
                        (const_int -360 [0xfffffffffffffe98])) [1 b+40 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:7)
            ]
             [] b.c:7)) "b.c":7:2 -1
     (nil))

;;★★スタックへの退避かな?

(insn 10 74 11 2 (set (mem/c:SI (reg/f:SI 108) [1 v1+0 S4 A2048])*movsi_internal
     (nil))
(insn 11 10 12 2 (set (mem/c:SI (plus:SI (reg/f:SI 108)
                (const_int 4 [0x4])) [1 v1+4 S4 A32])*movsi_internal
     (nil))

...
O0 の 282r.reload

;;★★asm 文

(insn 74 147 146 2 (set (reg:V64SI 112 [orig:109 v1 ] [109])
        (asm_operands/v:V64SI ("vlw.v %0, %1") ("=&v") 0 [
                (mem/c:SI (reg:SI 113) [1 b+40 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:7)
            ]
             [] b.c:7)) "b.c":7:2 -1
     (nil))
(insn 146 74 236 2 (clobber (reg:V64SI 109 [ v1 ])) "b.c":7:2 -1
     (nil))

;;★★変な insn が大量に増えている(ここから)

(insn 236 146 235 2 (set (reg:SI 202)*movsi_internal
     (nil))
(insn 235 236 234 2 (set (reg:SI 201)*movsi_internal
     (nil))

...

(insn 144 143 145 2 (set (subreg:SI (reg:V64SI 109 [ v1 ]) 248)*movsi_internal
     (nil))
(insn 145 144 10 2 (set (subreg:SI (reg:V64SI 109 [ v1 ]) 252)*movsi_internal
     (nil))

;;★★変な insn が大量に増えている(ここまで)

;;★★スタックへの退避かな?

(insn 10 145 11 2 (set (mem/c:SI (reg/f:SI 108) [1 v1+0 S4 A2048])*movsi_internal
     (nil))
(insn 11 10 12 2 (set (mem/c:SI (plus:SI (reg/f:SI 108)
                (const_int 4 [0x4])) [1 v1+4 S4 A32])*movsi_internal
     (nil))

...

どうやら asm 文の後に出てくる代入操作らしき RTL を処理しようとすると死んでしまうようです。次に O2 と O0 の違いを調べます。パス reload より前に実行され、なおかつ O0 と O2 双方で共通のパスは ira ですから、ira の RTL を比較します。

O2 と O0 のときの 281r.ira

;;★★O2 のとき

(insn 73 7 204 2 (set (reg:V64SI 105 [ v1 ])
        (asm_operands/v:V64SI ("vlw.v %0, %1") ("=&v") 0 [
                (mem/c:SI (plus:SI (reg/f:SI 97 frame)
                        (const_int -360 [0xfffffffffffffe98])) [1 b+40 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:7)
            ]
             [] b.c:7)) "b.c":7:2 -1
     (expr_list:REG_UNUSED (reg:V64SI 105 [ v1 ])
        (nil)))
(debug_insn 204 73 203 2 (var_location:V64SI D#65 (clobber (const_int 0 [0]))) -1
     (nil))
(debug_insn 203 204 202 2 (var_location:SI D#64 (subreg:SI (debug_expr:V64SI D#65) 0)) -1
     (nil))
(debug_insn 202 203 201 2 (var_location:SI D#63 (subreg:SI (debug_expr:V64SI D#65) 4)) -1
     (nil))


;;★★O0 のとき

(insn 74 7 10 2 (set (reg:V64SI 109 [ v1 ])
        (asm_operands/v:V64SI ("vlw.v %0, %1") ("=&v") 0 [
                (mem/c:SI (plus:SI (reg/f:SI 97 frame)
                        (const_int -360 [0xfffffffffffffe98])) [1 b+40 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:7)
            ]
             [] b.c:7)) "b.c":7:2 -1
     (nil))
(insn 10 74 11 2 (set (mem/c:SI (reg/f:SI 108) [1 v1+0 S4 A2048])*movsi_internal
     (nil))
(insn 11 10 12 2 (set (mem/c:SI (plus:SI (reg/f:SI 108)
                (const_int 4 [0x4])) [1 v1+4 S4 A32])*movsi_internal
     (nil))
(insn 12 11 13 2 (set (mem/c:SI (plus:SI (reg/f:SI 108)
                (const_int 8 [0x8])) [1 v1+8 S4 A64])*movsi_internal
     (nil))

結論から先に言えば O2 でも O0 でも問題のある RTL が生成されていると考えられます。RTL の所見をまとめると、

  • O2 も O0 も似たような代入操作と思しき RTL が出力されています。
  • O2 の場合は代入操作が 243r.fwprop1 で消されます。
  • O2 も O0 も fwprop1 より前の RTL はほぼ同じです。

O2 で Internal compile error が発生しないのはラッキーに過ぎなかったようです。次回は問題となる RTL がどこから来るのか調べます。

[編集者: すずき]
[更新: 2020年 6月 1日 14:10]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 11-2 - オプション O0 のエラーを引き起こす RTL(define_expand 追加編)

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

前回(2020年 5月 22日の日記参照)の続きです。エラーの原因となる代入操作 RTL はどこから出てきているのでしょう。RTL を遡っていくと expand から出力されていることがわかります。expand は RTL の最初のパスで、GIMPLE から RTL に変換するためのパス(パス番号 236)です。最初から間違っているということですね。

236r.expand
;;★★asm 文に相当する箇所

(insn 74 7 10 2 (set (reg:V64SI 109 [ v1 ])
        (asm_operands/v:V64SI ("vlw.v %0, %1
") ("=&v") 0 [
                (mem/c:SI (plus:SI (reg/f:SI 99 virtual-stack-vars)
                        (const_int -360 [0xfffffffffffffe98])) [1 b+40 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:7)
            ]
             [] b.c:7)) "b.c":7:2 -1
     (nil))

;;★★自動的に出力される代入操作らしき RTL

(insn 10 74 11 2 (set (mem/c:SI (reg/f:SI 108) [1 v1+0 S4 A2048])
        (subreg:SI (reg:V64SI 109 [ v1 ]) 0)) "b.c":7:2 -1
     (nil))
(insn 11 10 12 2 (set (mem/c:SI (plus:SI (reg/f:SI 108)
                (const_int 4 [0x4])) [1 v1+4 S4 A32])
        (subreg:SI (reg:V64SI 109 [ v1 ]) 4)) "b.c":7:2 -1
     (nil))
(insn 12 11 13 2 (set (mem/c:SI (plus:SI (reg/f:SI 108)
                (const_int 8 [0x8])) [1 v1+8 S4 A64])
        (subreg:SI (reg:V64SI 109 [ v1 ]) 8)) "b.c":7:2 -1
     (nil))

...

ベクトル型 V64SI のまま処理してほしいのに、4バイトごとに分割されて代入処理されています。この分割はどこで行われているか調べます。GCC は insn RTL を出力するときは必ず emit_insn() という関数で出力することを利用します。

  • 代入操作の先頭の insn は UID 10 です。ダンプファイルで確認するなら insn の隣にある数字(insn '10' ←これ 74 11 2 …)が UID です。
  • emit_insn() を書き換えて、INSN_UID(x) == 10 のときに適当に作った関数 hoge() に飛ばすようにします。
  • gdb で関数 hoge() にブレークを掛けると、ちょうど目当ての RTL を出力した瞬間に実行が止まります。

ブレークで実行が止まったら、バックトレースを取れば呼び出しまでの経路がわかります。

代入操作の先頭 insn を出力したときのバックトレース
(gdb) bt
#0  hoge () at ./gcc/gcc/emit-rtl.c:5103
#1  0x000000000097ebf9 in emit_insn (x=0x7ffff7aaa400)
    at ./gcc/gcc/emit-rtl.c:5118
#2  0x00000000009ff017 in emit_move_insn_1 (x=0x7ffff7bada80, y=0x7ffff7bada98)
    at ./gcc/gcc/expr.c:3754
#3  0x00000000009ffa28 in emit_move_insn (x=0x7ffff7bada80, y=0x7ffff7bada98)
    at ./gcc/gcc/expr.c:3858

★★↓いかにも分割していそうな、怪しい名前

#4  0x00000000009fee38 in emit_move_multi_word (mode=E_V64SImode, x=0x7ffff7bada68, y=0x7ffff7bada50)
    at ./gcc/gcc/expr.c:3720
#5  0x00000000009ff505 in emit_move_insn_1 (x=0x7ffff7bada68, y=0x7ffff7bada50)
    at ./gcc/gcc/expr.c:3791
#6  0x00000000009ffa28 in emit_move_insn (x=0x7ffff7bada68, y=0x7ffff7bada50)
    at ./gcc/gcc/expr.c:3858
#7  0x0000000000a0cc8d in store_expr (exp=0x7ffff7ffb480, target=0x7ffff7bada68, call_param_p=0,
    nontemporal=false, reverse=false)
    at ./gcc/gcc/expr.c:5932
#8  0x0000000000a09064 in expand_assignment (to=0x7ffff7aa7750, from=0x7ffff7ffb480, nontemporal=false)
    at ./gcc/gcc/expr.c:5517
#9  0x000000000076c911 in expand_asm_stmt (stmt=0x7ffff7b8f4e0)
    at ./gcc/gcc/cfgexpand.c:3198
#10 0x000000000076f89a in expand_gimple_stmt_1 (stmt=0x7ffff7b8f4e0)
    at ./gcc/gcc/cfgexpand.c:3685
#11 0x00000000007705f0 in expand_gimple_stmt (stmt=0x7ffff7b8f4e0)
    at ./gcc/gcc/cfgexpand.c:3853
#12 0x000000000077e7f1 in expand_gimple_basic_block (bb=0x7ffff7aba2d8, disable_tail_calls=false)
    at ./gcc/gcc/cfgexpand.c:5893
#13 0x0000000000781a25 in (anonymous namespace)::pass_expand::execute (this=0x449bbf0, fun=0x7ffff7baa000)

バックトレースの中に怪しい名前の関数 emit_move_multi_word() があります。いかにも複数の insn を出力しそうな名前です。emit_move_multi_word までを追うと、下記のような経路を辿っています。

代入が分割されるところ

// gcc/cfgexpand.c

static void
expand_asm_stmt (gasm *stmt)
{

...

  for (i = 0; i < noutputs; ++i)
    {
      tree val = output_tvec[i];
      tree type = TREE_TYPE (val);
      bool is_inout, allows_reg, allows_mem, ok;
      rtx op;

...

      if ((TREE_CODE (val) == INDIRECT_REF && allows_mem)
	  || (DECL_P (val)
	      && (allows_mem || REG_P (DECL_RTL (val)))
	      && ! (REG_P (DECL_RTL (val))
		    && GET_MODE (DECL_RTL (val)) != TYPE_MODE (type)))
	  || ! allows_reg
	  || is_inout
	  || TREE_ADDRESSABLE (type))
	{

...

	}
      else
	{
	  op = assign_temp (type, 0, 1);
	  op = validize_mem (op);
	  if (!MEM_P (op) && TREE_CODE (val) == SSA_NAME)
	    set_reg_attrs_for_decl_rtl (SSA_NAME_VAR (val), op);

	  generating_concat_p = old_generating_concat_p;

	  push_to_sequence2 (after_rtl_seq, after_rtl_end);
	  expand_assignment (val, make_tree (type, op), false);  //★★これ
	  after_rtl_seq = get_insns ();
	  after_rtl_end = get_last_insn ();
	  end_sequence ();
	}


// gcc/expr.c

/* Expand an assignment that stores the value of FROM into TO.  If NONTEMPORAL
   is true, try generating a nontemporal store.  */

void
expand_assignment (tree to, tree from, bool nontemporal)
{

...

  /* Compute FROM and store the value in the rtx we got.  */

  push_temp_slots ();
  result = store_expr (from, to_rtx, 0, nontemporal, false);  //★★これ
  preserve_temp_slots (result);
  pop_temp_slots ();
  return;
}

rtx_insn *
emit_move_insn (rtx x, rtx y)
{
  machine_mode mode = GET_MODE (x);
  rtx y_cst = NULL_RTX;
  rtx_insn *last_insn;
  rtx set;

...

  last_insn = emit_move_insn_1 (x, y);  //★★これ

  if (y_cst && REG_P (x)
      && (set = single_set (last_insn)) != NULL_RTX
      && SET_DEST (set) == x
      && ! rtx_equal_p (y_cst, SET_SRC (set)))
    set_unique_reg_note (last_insn, REG_EQUAL, copy_rtx (y_cst));

  return last_insn;
}

rtx_insn *
emit_move_insn_1 (rtx x, rtx y)
{
  machine_mode mode = GET_MODE (x);
  enum insn_code code;

  gcc_assert ((unsigned int) mode < (unsigned int) MAX_MACHINE_MODE);

  code = optab_handler (mov_optab, mode);
  if (code != CODE_FOR_nothing)    //★★CODE_FOR_nothing になるのが怪しい
    return emit_insn (GEN_FCN (code) (x, y));    //★★こっちに行けばいいのだろうか??

  /* Expand complex moves by moving real part and imag part.  */
  if (COMPLEX_MODE_P (mode))
    return emit_move_complex (mode, x, y);    //★★もしくはこっち??

...

  return emit_move_multi_word (mode, x, y);    //★★この関数が呼ばれ、分割される
}

せっかく辿っておいてこんなこというのは若干気が引けますが、なぜこの経路を辿るのか?コードを見ても全くわかりません。特に expand_asm_stmt() から emit_move_insn() までは、各関数が非常に長く、訳のわからない if 文が山ほどあります。GCC ってどうして動いてるんでしょうね?大丈夫?これ??

GCC のコードの酷さはさておき、emit_move_multi_word() と他の関数への分岐点になっている、emit_move_insn_1() が怪しそうです。次回以降、この関数を中心に調べます。

[編集者: すずき]
[更新: 2020年 6月 1日 14:10]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 11-3 - エラーを引き起こす RTL を回避する(define_expand 追加編)

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

前回(2020年 5月 23日の日記参照)の続きです。エラーの原因となる代入操作 RTL は emit_move_multi_word() で出力されており、条件の分岐点である emit_move_insn_1() が怪しそうです。分岐条件を司る optab_handler() を追います。

分岐条件の 1つ optab_handler()

// build_gcc/insn-opinit.h

enum optab_tag {
  unknown_optab,
  sext_optab,

...

  mov_optab,

...


// gcc/optabs-query.h

/* Return the insn used to implement mode MODE of OP, or CODE_FOR_nothing
   if the target does not have such an insn.  */

inline enum insn_code
optab_handler (optab op, machine_mode mode)
{
  unsigned scode = (op << 16) | mode;  //★★もしブレーク掛けたければ op = mov_optab, mode = E_V64SImode で引っ掛けられる
  gcc_assert (op > LAST_CONV_OPTAB);
  return raw_optab_handler (scode);  //★★これ
}


// build_gcc/insn-opinit.c

enum insn_code
raw_optab_handler (unsigned scode)
{
  int i = lookup_handler (scode);  //★★これが -1 だと CODE_FOR_nothing が返る
  return (i >= 0 && this_fn_optabs->pat_enable[i]
          ? pats[i].icode : CODE_FOR_nothing);
}

static int
lookup_handler (unsigned scode)
{
  int l = 0, h = ARRAY_SIZE (pats), m;
  while (h > l)
    {
      m = (h + l) / 2;
      if (scode == pats[m].scode)
        return m;
      else if (scode < pats[m].scode)
        h = m;
      else
        l = m + 1;
    }
  return -1;
}


//★★pats[] の定義

struct optab_pat {
  unsigned scode;
  enum insn_code icode;
};

static const struct optab_pat pats[NUM_OPTAB_PATTERNS] = {
  { 0x010405, CODE_FOR_extendqihi2 },
  { 0x010406, CODE_FOR_extendqisi2 },
  { 0x010407, CODE_FOR_extendqidi2 },
  { 0x010505, CODE_FOR_extendhihi2 },

...


//★★this_fn_optabs->pat_enable[] を初期化しているコード

void
init_all_optabs (struct target_optabs *optabs)
{
  bool *ena = optabs->pat_enable;
  ena[0] = HAVE_extendqihi2;
  ena[1] = HAVE_extendqisi2;

...

この lookup_handler() が -1 を返すと emit_move_multi_word() を実行します。lookup_handler() は pats という構造体を 2分探索する単純な関数ですから、大事なのは pats の中身と変更方法です。

ところが pats をどうやって作るか?については、手がかりがありません。GCC はクソコードすぎて辛い。とりあえず CODE_FOR_ なんとか、という部分と、ena[] = HAVE_ の部分は自動生成コードっぽいですから、適当にキーワードを考えて grep します。

pats の作り方を捜索
#### ChangeLog が良く引っかかってうざいので排除推奨

$ grep -r CODE_FOR_%s | grep -v ^ChangeLog

genopinit.c:    fprintf (s_file, "  { %#08x, CODE_FOR_%s },\n", p->sort_num, p->name);
gencodes.c:     printf (",\n   CODE_FOR_%s = CODE_FOR_nothing", name);
gencodes.c:     printf (",\n  CODE_FOR_%s = %d", name, info->index);
genemit.c:      printf ("    return CODE_FOR_%s;\n", instance->name);
gentarget-def.c:  printf ("#undef TARGET_CODE_FOR_%s\n", upper_name);
gentarget-def.c:  printf ("#define TARGET_CODE_FOR_%s ", upper_name);
gentarget-def.c:    printf ("CODE_FOR_%s\n", name);

$ grep -r HAVE_ | grep 'ena\[' | grep -v ^ChangeLog

genopinit.c:    fprintf (s_file, "  ena[%u] = HAVE_%s;\n", i, p->name);

コードの形と見比べると genopinit.c が pats の作成者で間違いなさそうです。このプログラムは cc1 の一部ではなく、補助ツール genopinit のコードです。ツールの使い方がわからないのでビルドログを見ます。

genopinit の使い方

# build_gcc/Makefile

s-opinit: $(MD_DEPS) build/genopinit$(build_exeext) insn-conditions.md
	$(RUN_GEN) build/genopinit$(build_exeext) $(md_file) \
	  insn-conditions.md -htmp-opinit.h -ctmp-opinit.c


# ビルドログ

build/genopinit ./gcc/gcc/common.md ./gcc/gcc/config/riscv/riscv.md \
  insn-conditions.md -htmp-opinit.h -ctmp-opinit.c

いくつか *.md ファイルを指定するだけです。genopinit の main() 関数を見ると、2つの条件 DEFINE_INSN もしくは DEFINE_EXPAND のときだけ gen_insn() を呼びます。

genopinit の main 関数

// gcc/genopinit.c

int
main (int argc, const char **argv)
{
  FILE *h_file, *s_file;
  unsigned int i, j, n, last_kind[5];
  optab_pattern *p;

  progname = "genopinit";

  if (NUM_OPTABS > 0xffff || MAX_MACHINE_MODE >= 0xff)
    fatal ("genopinit range assumptions invalid");

  if (!init_rtx_reader_args_cb (argc, argv, handle_arg))
    return (FATAL_EXIT_CODE);

  h_file = open_outfile (header_file_name);
  s_file = open_outfile (source_file_name);

  /* Read the machine description.  */
  md_rtx_info info;
  while (read_md_rtx (&info))
    switch (GET_CODE (info.def))
      {
      case DEFINE_INSN:
      case DEFINE_EXPAND:
	gen_insn (&info);  //★★これ
	break;

      default:
	break;
      }

  /* Sort the collected patterns.  */
  patterns.qsort (pattern_cmp);

...

この記事を読むような方は、既に何を変更すれば良いかご存知かもしれませんが、せっかくなので genopinit の動きを追います。gen_insn() にブレークを掛けて、DEFINE_INSN と DEFINE_EXPAND のときに何が起きるのか見ます。

genopinit の動きを観察(DEFINE_INSN)
$ gdb build_gcc/build/genopinit

(gdb) b gen_insn

Breakpoint 1 at 0x402920: file ./gcc/gcc/genopinit.c, line 45.

(gdb) r common.md config/riscv/riscv.md build_gcc/insn-conditions.md -hhhh -cccc

Breakpoint 1, gen_insn (info=0x7fffffffd970)
    at ./gcc/gcc/genopinit.c:45
45        if (find_optab (&p, XSTR (info->def, 0)))


(gdb) p *info

$3 = {
  def = 0x4bc030,
  loc = {
    filename = 0x7fffffffde93 "config/riscv/riscv.md",  ★★ファイル名
    lineno = 431,  ★★行番号
    colno = 1
  },
  index = 1
}

(gdb) p info->def->code

$5 = DEFINE_INSN
genopinit の動きを観察(DEFINE_INSN)

;; gcc/config/riscv/riscv.md:431

★★*.md ファイルの define_insn と紐付いている

(define_insn "add<mode>3"
  [(set (match_operand:ANYF            0 "register_operand" "=f")
	(plus:ANYF (match_operand:ANYF 1 "register_operand" " f")
		   (match_operand:ANYF 2 "register_operand" " f")))]
  "TARGET_HARD_FLOAT"
  "fadd.<fmt>\t%0,%1,%2"
  [(set_attr "type" "fadd")
   (set_attr "mode" "<UNITMODE>")])

残ったのは DEFINE_EXPAND です。DEFINE_INSN では止まってほしくないので、こういうときは条件付きブレークが便利です。

genopinit の動きを観察(DEFINE_EXPAND)
(gdb) b gen_insn if info->def->code == DEFINE_EXPAND

Note: breakpoint 1 also set at pc 0x402920.
Breakpoint 2 at 0x402920: file ./gcc/gcc/genopinit.c, line 45.

(gdb) c

Breakpoint 2, gen_insn (info=0x7fffffffd970)
    at ./gcc/gcc/genopinit.c:45
45        if (find_optab (&p, XSTR (info->def, 0)))


(gdb) p *info
$6 = {
  def = 0x4c4ef0,
  loc = {
    filename = 0x7fffffffde93 "config/riscv/riscv.md",  ★★ファイル名
    lineno = 635,  ★★行番号
    colno = 1
  },
  index = 352
}
genopinit の動きを観察(DEFINE_EXPAND)

;; gcc/config/riscv/riscv.md:635

★★*.md ファイルの define_expand と紐付いている

(define_expand "<u>mulditi3"
  [(set (match_operand:TI                         0 "register_operand")
	(mult:TI (any_extend:TI (match_operand:DI 1 "register_operand"))
		 (any_extend:TI (match_operand:DI 2 "register_operand"))))]
  "TARGET_MUL && TARGET_64BIT"
{
  rtx low = gen_reg_rtx (DImode);
  emit_insn (gen_muldi3 (low, operands[1], operands[2]));

  rtx high = gen_reg_rtx (DImode);
  emit_insn (gen_<u>muldi3_highpart (high, operands[1], operands[2]));

  emit_move_insn (gen_lowpart (DImode, operands[0]), low);
  emit_move_insn (gen_highpart (DImode, operands[0]), high);
  DONE;
})

つまり *.md ファイルに define_expand もしくは define_insn を定義すれば pats の中身が増えて lookup_handler() に引っかかるはずです。riscv.md に既に存在する movsi や movdi を真似して追加します。

define_expand を追加

;; config/riscv/riscv.md

(define_expand "movv64si"
  [(set (match_operand:V64SI 0 "")
	(match_operand:V64SI 1 ""))]
  ""
{
  if (riscv_legitimize_move (V64SImode, operands[0], operands[1]))    //★★この呼び出しは正しいかわからないが、とりあえずそのまま
    DONE;
})

実行してみると、先程追加したコードが引っかかって if の条件が成立し、emit_insn() が呼び出されます。念のためにコードを再掲します。

分岐条件の 1つ optab_handler()(再掲)

// gcc/expand.c

rtx_insn *
emit_move_insn_1 (rtx x, rtx y)
{
  machine_mode mode = GET_MODE (x);
  enum insn_code code;

  gcc_assert ((unsigned int) mode < (unsigned int) MAX_MACHINE_MODE);

  code = optab_handler (mov_optab, mode);
  if (code != CODE_FOR_nothing)    //★★CODE_FOR_nothing になるのが怪しい
    return emit_insn (GEN_FCN (code) (x, y));    //★★こっちに行けばいいのだろうか??

...


// build_gcc/insn-opinit.h

/* Given an enum insn_code, access the function to construct
   the body of that kind of insn.  */
#define GEN_FCN(CODE) (insn_data[CODE].genfun)

実行して emit_move_insn_1() でブレークし、gdb で値をダンプすると下記のようになっているはずです。gen_movv64si() 関数が突然出てきますが、これは自動生成された関数です。

define_expand 追加後の code
(gdb) p code

$4 = CODE_FOR_movv64si  ★★nothing から V64SI に変わった


(gdb) p insn_data[code].genfun

$5 = {func = 0x216ecea <gen_movv64si(rtx_def*, rtx_def*)>}


(gdb) p insn_data[code]

$6 = {
  name = 0x2d764ed "movv64si",
  output = {
    single = 0x0,
    multi = 0x0,
    function = 0x0
  },
  genfun = {
    func = 0x216eec5 <gen_movv64si(rtx_def*, rtx_def*)>
  },
  operand = 0x2d73ef0 <operand_data+10512>,
  n_generator_args = 2 '\002',
  n_operands = 2 '\002',
  n_dups = 0 '\000',
  n_alternatives = 0 '\000',
  output_format = 0 '\000'
}
define_expand 追加後に生成された関数 gen_movv64si()

// build_gcc/insn-emit.c

/* ./gcc/gcc/config/riscv/riscv.md:1308 */
rtx
gen_movv64si (rtx operand0,
	rtx operand1)
{
  rtx_insn *_val = 0;
  start_sequence ();
  {
    rtx operands[2];
    operands[0] = operand0;
    operands[1] = operand1;
#define FAIL return (end_sequence (), _val)
#define DONE return (_val = get_insns (), end_sequence (), _val)
#line 1312 "./gcc/gcc/config/riscv/riscv.md"
{
  if (riscv_legitimize_move (V64SImode, operands[0], operands[1]))    //★★この呼び出しは正しい?
    DONE;
}
#undef DONE
#undef FAIL
    operand0 = operands[0];
    (void) operand0;
    operand1 = operands[1];
    (void) operand1;
  }
  emit_insn (gen_rtx_SET (operand0,
	operand1));
  _val = get_insns ();
  end_sequence ();
  return _val;
}

近くにあった define_expand("movdi") をコピーして作ったので、riscv_legitimize_move() を呼んでいますが、この実装が正しいかどうか今はわかりません。動作がおかしいようなら、後で調べたり、直したりする必要があるかもしれません。

現状、生成された RTL を見た限り set の destination 側の machine mode が mem/c:SI から mem/c:V64SI に変わっていますし、問題なさそうに見えます。

define_expand 追加後の 236r.expand

(insn 11 7 10 2 (set (reg:V64SI 109 [ v1 ])
        (asm_operands/v:V64SI ("vlw.v %0, %1
") ("=&v") 0 [
                (mem/c:SI (plus:SI (reg/f:SI 99 virtual-stack-vars)
                        (const_int -360 [0xfffffffffffffe98])) [1 b+40 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:7)
            ]
             [] b.c:7)) "b.c":7:2 -1
     (nil))
(insn 10 11 0 2 (set (mem/c:V64SI (reg/f:SI 108) [1 v1+0 S256 A2048])
        (reg:V64SI 109 [ v1 ])) "b.c":7:2 -1
     (nil))


;;★★(参考)define_expand 追加前の RTL

(insn 10 74 11 2 (set (mem/c:SI (reg/f:SI 108) [1 v1+0 S4 A2048])
        (subreg:SI (reg:V64SI 109 [ v1 ]) 0)) "b.c":7:2 -1
     (nil))

しかし残念ながら define_expand の追加だけではダメで、変更後はこんなエラーが出ます。

define_expand 追加後のエラー
b.c: In function '_start':
b.c:25:1: error: unrecognizable insn:
   25 | }
      | ^
(insn 10 11 0 2 (set (mem/c:V64SI (reg/f:SI 108) [1 v1+0 S256 A2048])
        (reg:V64SI 109 [ v1 ])) "b.c":7:2 -1
     (nil))
during RTL pass: vregs
dump file: b.c.237r.vregs
b.c:25:1: internal compiler error: in extract_insn, at recog.c:2294

続きはまた次回。

[編集者: すずき]
[更新: 2020年 6月 1日 14:10]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 12-1 - 続、オプション O0 のエラー調査(define_insn 追加編)

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

引き続き、ベクトル型を使用する際の下記の問題に取り組みます。

  • 最適化オプションを O0 にするとコンパイラが internal error を出す

前回(2020年 5月 22日の日記2020年 5月 23日の日記2020年 5月 24日の日記参照)にて define_expand を追加し、Internal compile error: maximum number of generated reload insns per insn achieved (90) は出なくなりましたが、別のエラーが出ました。

define_expand 追加後のエラー
b.c: In function '_start':
b.c:25:1: error: unrecognizable insn:
   25 | }
      | ^
(insn 10 11 0 2 (set (mem/c:V64SI (reg/f:SI 108) [1 v1+0 S256 A2048])
        (reg:V64SI 109 [ v1 ])) "b.c":7:2 -1
     (nil))
during RTL pass: vregs
dump file: b.c.237r.vregs
b.c:25:1: internal compiler error: in extract_insn, at recog.c:2294

エラーの起きている箇所は vreg というパスで、RTL のパスでは 2番目(expand の次)です。序盤のパスですし、変な RTL が後ろのパスに伝わって意味不明なエラー、というケースではなさそうです。素直にエラーが出ている箇所を見ます。

エラーが起きている箇所

// gcc/recog.c

void
extract_insn (rtx_insn *insn)
{
  int i;
  int icode;
  int noperands;
  rtx body = PATTERN (insn);

...

  switch (GET_CODE (body))
    {

...

    case SET:
      if (GET_CODE (SET_SRC (body)) == ASM_OPERANDS)
	goto asm_insn;
      else
	goto normal_insn;  //★★ここにきて、ジャンプ

...

    default:
    normal_insn:
      /* Ordinary insn: recognize it, get the operands via insn_extract
	 and get the constraints.  */

      icode = recog_memoized (insn);
      if (icode < 0)
	fatal_insn_not_found (insn);  //★★ここでエラー

...


// gcc/recog.h

/* Try recognizing the instruction INSN,
   and return the code number that results.
   Remember the code so that repeated calls do not
   need to spend the time for actual rerecognition.

   This function is the normal interface to instruction recognition.
   The automatically-generated function `recog' is normally called
   through this one.  */

static inline int
recog_memoized (rtx_insn *insn)
{
  if (INSN_CODE (insn) < 0)
    INSN_CODE (insn) = recog (PATTERN (insn), insn, 0);
  return INSN_CODE (insn);
}

// ★★ PATTERN(insn) は insn->u.fld[3]->rt_rtx を返す。

PATTERN() の 3 という数値は insn の RTL format に基づいています。insn の RTL の引数は "uuBeiie" となっており、4つ目の引数 e が insn の実行したい処理を表しているようです。残念ながら PATTERN() という関数名から、そのような事情は掴めないですよね、普通。

insn RTL の e 引数

          |u |u|B|e ↓これ
 (insn 10 11 0 2 (set (mem/c:V64SI (reg/f:SI 108) [1 v1+0 S256 A2048])
      (reg:V64SI 109 [ v1 ])) "b.c":7:2 -1
   (nil))

続きを追います。recog() という関数に入っていきます。recog から始まる関数群は自動生成されたコードです。自動生成コードは読みにくいですが、GCC 本体よりロジックがシンプルで理解しやすいです。GCC 本体は読みにくい&理解不能なので、辛くて泣けてきます。

エラーが起きている箇所(続き)

// build_gcc/insn-recog.c

int
recog (rtx x1 ATTRIBUTE_UNUSED,
	rtx_insn *insn ATTRIBUTE_UNUSED,
	int *pnum_clobbers ATTRIBUTE_UNUSED)
{
  rtx * const operands ATTRIBUTE_UNUSED = &recog_data.operand[0];
  rtx x2, x3, x4, x5, x6, x7, x8, x9;
  rtx x10, x11, x12, x13, x14, x15, x16, x17;
  rtx x18, x19, x20, x21, x22, x23, x24, x25;
  rtx x26, x27, x28;
  int res ATTRIBUTE_UNUSED;
  recog_data.insn = NULL;
  switch (GET_CODE (x1))
    {
    case SET:
      return recog_17 (x1, insn, pnum_clobbers);  //★★これ

...


static int
recog_17 (rtx x1 ATTRIBUTE_UNUSED,
	rtx_insn *insn ATTRIBUTE_UNUSED,
	int *pnum_clobbers ATTRIBUTE_UNUSED)
{
  rtx * const operands ATTRIBUTE_UNUSED = &recog_data.operand[0];
  rtx x2, x3, x4, x5, x6, x7;
  int res ATTRIBUTE_UNUSED;
  x2 = XEXP (x1, 1);
  switch (GET_CODE (x2))
    {

...

    case CONST_INT:
    case CONST_WIDE_INT:
    case CONST_POLY_INT:
    case CONST_FIXED:
    case CONST_DOUBLE:
    case CONST_VECTOR:
    case CONST:
    case REG:
    case SUBREG:
    case MEM:
    case LABEL_REF:
    case SYMBOL_REF:
    case HIGH:
      return recog_7 (x1, insn, pnum_clobbers);  //★★これ

...


static int
recog_7 (rtx x1 ATTRIBUTE_UNUSED,
	rtx_insn *insn ATTRIBUTE_UNUSED,
	int *pnum_clobbers ATTRIBUTE_UNUSED)
{
  rtx * const operands ATTRIBUTE_UNUSED = &recog_data.operand[0];
  rtx x2, x3, x4;
  int res ATTRIBUTE_UNUSED;
  x2 = XEXP (x1, 0);
  switch (GET_CODE (x2))
    {
    case REG:
    case SUBREG:
    case MEM:
      res = recog_2 (x1, insn, pnum_clobbers);  //★★これ
      if (res >= 0)
        return res;
      break;

...


static int
recog_2 (rtx x1 ATTRIBUTE_UNUSED,
	rtx_insn *insn ATTRIBUTE_UNUSED,
	int *pnum_clobbers ATTRIBUTE_UNUSED)
{
  rtx * const operands ATTRIBUTE_UNUSED = &recog_data.operand[0];
  rtx x2, x3, x4;
  int res ATTRIBUTE_UNUSED;
  x2 = XEXP (x1, 0);
  operands[0] = x2;
  x3 = XEXP (x1, 1);
  operands[1] = x3;
  switch (GET_MODE (operands[0]))
    {

...

    default:
      break;  //★★V64SImode はいずれの case にも該当しないのでここにくる
    }

...

  if (pnum_clobbers == NULL  //★★この条件に引っかかる
      || GET_CODE (x2) != MEM)
    return -1;  //★★ここにくる

...

なぜか recog() 関数はデバッグ情報がおかしくて、gdb の next コマンドが正常に動きません。非常にデバッグしづらいです。gdb で追うときは build_gcc/insn-recog.c 内の #line ディレクティブを全て削除するとデバッグしやすいです。

エラーを解消するには recog_2 の switch-case に変化を及ぼす方法を知る必要があります。しかしそんなものわかる訳ありません。仕方ないので周辺のコードを眺めます。

エラーが起きている箇所の周辺

static int
recog_2 (rtx x1 ATTRIBUTE_UNUSED,
	rtx_insn *insn ATTRIBUTE_UNUSED,
	int *pnum_clobbers ATTRIBUTE_UNUSED)
{

...

  switch (GET_MODE (operands[0]))
    {
    case E_DImode:
      if (nonimmediate_operand (operands[0], E_DImode)
          && move_operand (operands[1], E_DImode))
        {
          if (
#line 1340 "./gcc/gcc/config/riscv/riscv.md"    //★★この辺りにヒントがあるのでは??
(!TARGET_64BIT
   && (register_operand (operands[0], DImode)
       || reg_or_0_operand (operands[1], DImode))))
            return 135; /* *movdi_32bit */
          if (
#line 1350 "./gcc/gcc/config/riscv/riscv.md"
(TARGET_64BIT
   && (register_operand (operands[0], DImode)
       || reg_or_0_operand (operands[1], DImode))))
            return 136; /* *movdi_64bit */
        }
      break;

ぐちゃぐちゃの if 文の中に #line が差し込まれています。コードの一部を riscv.md から持ってきているようです。

riscv.md の該当部分

;; gcc/config/riscv/riscv.md

(define_insn "*movdi_32bit"
  [(set (match_operand:DI 0 "nonimmediate_operand" "=r,r,r,m,  *f,*f,*r,*f,*m")
	(match_operand:DI 1 "move_operand"         " r,i,m,r,*J*r,*m,*f,*f,*f"))]

★★↓このコードが recog_2 にコピーされている

  "!TARGET_64BIT
   && (register_operand (operands[0], DImode)
       || reg_or_0_operand (operands[1], DImode))"

★★↑

  { return riscv_output_move (operands[0], operands[1]); }
  [(set_attr "move_type" "move,const,load,store,mtc,fpload,mfc,fmove,fpstore")
   (set_attr "mode" "DI")])

おそらく define_insn を定義すれば、recog_2() も変わるでしょう。近しいものをコピーして作ります。

riscv.md に define_insn を追加

;; gcc/config/riscv/riscv.md

(define_attr "vecmode" "unknown,V64SI"
  (const_string "unknown"))

(define_insn "*movv64si_internal"
  [(set (match_operand:V64SI 0 "nonimmediate_operand" "=v,v,m")
	(match_operand:V64SI 1 "move_operand"         " v,m,v"))]    //★★RTL template
  "(register_operand (operands[0], V64SImode)
    || reg_or_0_operand (operands[1], V64SImode))"    //★★condition
  { return riscv_output_move (operands[0], operands[1]); }    //★★output template
  [(set_attr "move_type" "move,load,store")
   (set_attr "vecmode" "V64SI")])    //★★insn attributes

とりあえず RTL template, condition, insn attributes に出現する machine mode だけ変更しました。これが合っているのかわかりませんが、ダメなら後で直しましょう。

define_insn を追加後の recog

static int
recog_2 (rtx x1 ATTRIBUTE_UNUSED,
	rtx_insn *insn ATTRIBUTE_UNUSED,
	int *pnum_clobbers ATTRIBUTE_UNUSED)
{

...

  switch (GET_MODE (operands[0]))
    {
    case E_V64SImode:
      if (nonimmediate_operand (operands[0], E_V64SImode)
          && move_operand (operands[1], E_V64SImode)
          && 
#line 1320 "./gcc/gcc/config/riscv/riscv.md"
((register_operand (operands[0], V64SImode)
    || reg_or_0_operand (operands[1], V64SImode))))
        return 134; /* *movv64si_internal */    //★★ここにくるようになった
      break;

再び recog_2() を追いかけてみると、先程はなかった case が増えており、-1 ではない値が返されるようになりました。

define_insn を追加後のエラー
during RTL pass: final
dump file: b.c.314r.final

b.c: In function '_start':
b.c:25:1: internal compiler error: in riscv_output_move, at config/riscv/riscv.c:2000
   25 | }
      | ^

0x1ae3d41 riscv_output_move(rtx_def*, rtx_def*)

今度こそうまく行くかと思いきや、また別のエラーが発生しました。道のりは長そうです。

[編集者: すずき]
[更新: 2020年 6月 1日 14:11]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 12-2 - ベクトル命令の出力(define_insn 追加編)

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

前回(2020年 5月 25日の日記参照)にて define_insn を追加しましたが、エラーが出ました。引き続きエラーを見ます。

define_insn を追加後、エラーが起きている箇所

// gcc/config/riscv/riscv.c

/* Return the appropriate instructions to move SRC into DEST.  Assume
   that SRC is operand 1 and DEST is operand 0.  */

const char *
riscv_output_move (rtx dest, rtx src)
{
  enum rtx_code dest_code, src_code;
  machine_mode mode;
  bool dbl_p;

...

  if (dest_code == REG && FP_REG_P (REGNO (dest)))
    {
      if (src_code == MEM)
	return dbl_p ? "fld\t%0,%1" : "flw\t%0,%1";
    }
  gcc_unreachable ();    //★★ここに到達している
}

RTL に対応するアセンブラを出力する場所のようです。ベクトル型には当然対応していません。ひとまず浮動小数点のムーブ、ロード、ストアを真似して追加します。

ベクトル型のムーブ、ロード、ストアに対応する

// gcc/config/riscv/riscv.c

/* Return the appropriate instructions to move SRC into DEST.  Assume
   that SRC is operand 1 and DEST is operand 0.  */

const char *
riscv_output_move (rtx dest, rtx src)
{
  enum rtx_code dest_code, src_code;
  machine_mode mode;
  bool dbl_p;

...

  if (src_code == REG && VP_REG_P (REGNO (src)))
    {
      if (dest_code == REG && VP_REG_P (REGNO (dest)))
        return "vmv.v\t%0,%1";    //★★ムーブ
      if (dest_code == MEM)
	return "vsw.v\t%1,%0";    //★★ストア
    }
  if (dest_code == REG && VP_REG_P (REGNO (dest)))
    {
      if (src_code == MEM)
        return "vlw.v\t%0,%1";    //★★ロード
    }
  gcc_unreachable ();
}

やっとエラーが出なくなりました。本当に長かったです。

サンプルコード

typedef int __v64si __attribute__((__vector_size__(256)));
 
void _start()
{
	int b[100];
	__v64si v1;

	__asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v1) : "A"(b[10]));
}
サンプルコードビルド、逆アセンブル
$ riscv32-unknown-elf-gcc -Wall -march=rv32gcv b.c  -O0 -nostdlib -g

$ riscv32-unknown-elf-objdump -drS a.out

...

        __asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v1) : "A"(b[10]));
   10076:       e8840713                addi    a4,s0,-376
   1007a:       12076007                vlw.v   v0,(a4)
   1007e:       0207e027                vsw.v   v0,(a5)
}
   10082:       0001                    nop
   10084:       3ac12403                lw      s0,940(sp)
   10088:       3b010113                addi    sp,sp,944
   1008c:       8082                    ret

それらしいバイナリも出力されています。

冒険はまだ続く

ベクトルレジスタが 1つしか使用されていなくて寂しいので、サンプルコードを変更して vlw.v を複数書きます。するとまたエラーが出ます。

サンプルコード 2

typedef int __v64si __attribute__((__vector_size__(256)));
 
void _start()
{
	int b[100];
	__v64si v1, v2;

	__asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v1) : "A"(b[10]));
	__asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v2) : "A"(b[20]));
}
サンプルコード 2 ビルドできず
$ riscv32-unknown-elf-gcc -Wall -march=rv32gcv b.c  -O0 -nostdlib -g

/tmp/ccsg5iId.s: Assembler messages:
/tmp/ccsg5iId.s:38: Error: illegal operands `vsw.v v0,256(a5)'

コンパイラはエラーを出していませんが、アセンブラがエラーを出しています。どうも変なオペランドが出力されているようです。うーん、惜しい。アセンブラを見ると、

サンプルコード 2 アセンブラ

...

        .loc 1 8 2
        addi    a4,s0,-376
 #APP
# 8 "b.c" 1
        vlw.v v0, 0(a4)

# 0 "" 2
 #NO_APP
        vsw.v   v0,256(a5)    #★★この命令でエラー(オフセットを使っている)
        .loc 1 9 2
        addi    a4,s0,-336
 #APP
# 9 "b.c" 1
        vlw.v v0, 0(a4)

# 0 "" 2
 #NO_APP
        vsw.v   v0,0(a5)    #★★この命令は OK(オフセットを使っていない)
        .loc 1 10 1
        nop
        lw      s0,1196(sp)

...

スカラ演算用の lw, sw 命令とオペランドの制約が異なり、vlw.v, vsw.v 命令はオフセット付きアドレスをオペランドに取れません。コンパイラはオフセット付きアドレスをオペランドに出力しないように抑制する必要があります。続きはまた今度。

[編集者: すずき]
[更新: 2020年 6月 28日 01:44]

コメント一覧

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



link permalink

link 編集する

STATIONflow ランク 20

STATIONflow の日記一覧。

ゲーム進行的に 1つの区切りと思われる、ランク 20 を超えました。駅が広くなるとちょっと困った現象が発生します。

現象としては終電が出た後に、極端に評価値が下がります。仕組みはこんな感じ。

  • 終電が出る
  • 電車を目的地にしていた客の目的地がなくなる
  • 目的地がなくなった客が目的地を変える
  • 偶然にも遠い目的地を選んだ客は長距離歩く羽目になる
  • 評価値が下がる

この問題は常に発生していて、序盤は駅が狭いのと人が少ないので問題ありませんが、終盤は一気に 1,000〜2,000人規模で目的地の切り替えが発生して、最終の評価値に影響するほど下がることがあります。


STATIONflow 終電前


STATIONflow 終電後、評価値が下がっていく

終電の時間は固定で、プレーヤーの努力でどうすることもできません。理不尽だ……。

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

コメント一覧

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



link permalink

link 編集する

STATIONflow のバグ

STATIONflow の日記一覧。

STATIONflow バグってますね……。ゲーム進行速度を最大にすると勝手に評価が下がってしまいます。これはあんまりだ。

ゲーム進行速度を最大の 3 にすると、評価がかなり下がります。速度 1 or 2 なら A+(1枚目)ですが、速度 3 だと B(2枚目)です。他は何も変えず、速度だけ変えた結果です。


STATIONflow 速度 2


STATIONflow 速度 3

STATIONflow は評価と収入が直結しており、評価が 2段階下がると大赤字です。画像の右下が収入ですが、A+ 316000 から B 158000 に下がってますよね?

速度 3 のときの客の評価値を見ると、利用施設の待ち時間に対する評価が軒並み悪化しています。速度 3 だけ待ち時間の計算をミスってるんじゃないでしょうか?

あまりにも評価が乱高下するから、何が起きたのか結構悩んでしまいました。無駄に悩んだ時間を返せ。

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

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 13-1 - オフセット付きアドレスを禁止する

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

メモリからのロード、ストアに問題があるので、おそらくメモリ周りの constraint 指定が間違っていて、本来受け付けてはいけないオペランドまで受け付けていると思われますが、証拠を掴む方法が全くわかりません。

今回追加した define_expand, define_insn のうち define_expand は constraint なし、define_insn は m という標準の constraint を使っています。手がかりになりそうな constraint m から追ってみます。メモリの constraint は下記に定義があります。

constraint "m" の定義

// gcc/defaults.h

#ifndef TARGET_MEM_CONSTRAINT
#define TARGET_MEM_CONSTRAINT 'm'
#endif


// gcc/common.md

(define_memory_constraint "TARGET_MEM_CONSTRAINT"
  "Matches any valid memory."
  (and (match_code "mem")
       (match_test "/* hogehoge */ memory_address_addr_space_p (GET_MODE (op), XEXP (op, 0),
						 MEM_ADDR_SPACE (op))")))  //★★ここに適当なコメントを入れる

メモリの constraint は若干イレギュラーな定義で、アーキテクチャにより "m" 以外の文字になります(s390 のみ "e" を使います)。RISC-V では TARGET_MEM_CONSTRAINT は "m" ですから、特に気にしなくて良いです。

定義の最後にある match_test に続くコードに適当にコメントを入れて、ビルドしてからコメントの文字列で grep すると、build_gcc/tm-constrs.h の statisfies_constraint_m() という関数にコピペされていることがわかります。このルールは constraint "m" だけでなく他も同じです。関数 satisfies_constraint_(文字) が自動的に生成され、条件チェックされます。

この関数でブレークしたいですが static inline になっておりブレークが掛からないので、適当に for_break_tmp() 関数を一つ追加しておき、この関数で止めます。

constraint "m" の判定関数

// build_gcc/tm-constrs.h

static void for_break_tmp(rtx op)  //★★関数を足す
{
}

static inline bool
satisfies_constraint_m (rtx op)  //★★この関数にブレークは設定できないので…
{
  for_break_temp(op);  //★★関数呼び出しを足す

  return (GET_CODE (op) == MEM) && (
#line 26 "./gcc/gcc/common.md"
( /* hogehoge */ memory_address_addr_space_p (GET_MODE (op), XEXP (op, 0),
						 MEM_ADDR_SPACE (op))));  //★★さっきいれた適当なコメントも一緒にコピーされる
}

無条件だと何度も止まって鬱陶しいので、machine mode で条件ブレークすると良いでしょう。

constraint "m" の判定関数でブレーク
(gdb) b for_break_tmp if op->mode == E_V64SImode

(gdb) r

Breakpoint 1, for_break_tmp (op=0x7ffff7bacab0) at tm-constrs.h:9
9       }

(gdb) bt

#0  for_break_tmp (op=0x7ffff7bacab0)
    at tm-constrs.h:9
#1  0x000000000217dfa8 in satisfies_constraint_m (op=0x7ffff7bacab0)
    at tm-constrs.h:13
#2  0x0000000000ebcc07 in constraint_satisfied_p (x=0x7ffff7bacab0, c=CONSTRAINT_m)
    at ./tm-preds.h:108
#3  0x0000000000ebe8cf in satisfies_memory_constraint_p (op=0x7ffff7bacab0,
    constraint=CONSTRAINT_m)
    at ./gcc/gcc/lra-constraints.c:421
#4  0x0000000000ec7149 in process_alt_operands (only_alternative=-1)
    at ./gcc/gcc/lra-constraints.c:2338
#5  0x0000000000eceb52 in curr_insn_transform (check_only_p=false)
    at ./gcc/gcc/lra-constraints.c:3977
#6  0x0000000000ed4e71 in lra_constraints (first_p=true)
    at ./gcc/gcc/lra-constraints.c:5027
#7  0x0000000000eacae4 in lra (f=0x454f640)
    at ./gcc/gcc/lra.c:2437
#8  0x0000000000e1a557 in do_reload ()
    at ./gcc/gcc/ira.c:5523
#9  0x0000000000e1af95 in (anonymous namespace)::pass_reload::execute (this=0x449fdf0)
    at ./gcc/gcc/ira.c:5709

パスは 282r.reload です。いくつかスタックフレームを遡ると process_alt_operands() 関数にたどり着きます。ベクトルレジスタの追加(2020年 3月 29日の日記参照)のときに見ました、懐かしいですね。それはさておき下記のコードから呼ばれています。

constraint "m" を判定する箇所

static bool
process_alt_operands (int only_alternative)
{

...

	  costly_p = false;
	  do
	    {
	      switch ((c = *p, len = CONSTRAINT_LEN (c, p)), c)
		{

...

		default:
		  cn = lookup_constraint (p);
		  switch (get_constraint_type (cn))
		    {
		    case CT_REGISTER:
		      cl = reg_class_for_constraint (cn);  //★★前回はこちらに来ていた
		      if (cl != NO_REGS)
			goto reg;
		      break;

		    case CT_CONST_INT:
		      if (CONST_INT_P (op)
			  && insn_const_int_ok_for_constraint (INTVAL (op), cn))
			win = true;
		      break;

		    case CT_MEMORY:
		      if (MEM_P (op)
			  && satisfies_memory_constraint_p (op, cn))  //★★今回はこちらに来る
			win = true;
		      else if (spilled_pseudo_p (op))
			win = true;

		      /* If we didn't already win, we can reload constants
			 via force_const_mem or put the pseudo value into
			 memory, or make other memory by reloading the
			 address like for 'o'.  */
		      if (CONST_POOL_OK_P (mode, op)
			  || MEM_P (op) || REG_P (op)
			  /* We can restore the equiv insn by a
			     reload.  */
			  || equiv_substition_p[nop])
			badop = false;
		      constmemok = true;
		      offmemok = true;
		      break;


// op が指す RTL はこんな感じ

(mem/c:V64SI (plus:SI (reg/f:SI 108)
        (const_int 256 [0x100])) [1 v1+0 S256 A2048])

渡された RTL に対し、satisfies_memory_constraint_p() は constraint "m" の条件と合致するか調べ、合致していたら win フラグを true にしています。

この辺りを変えれば、オフセット付きアドレスに変化が起きそうです。続きはまた今度。

[編集者: すずき]
[更新: 2020年 6月 1日 14:11]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 13-2 - オフセット付きアドレス全部禁止

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

前回(2020年 5月 29日の日記参照)メモリというかオフセット付きアドレスの constraint をチェックしていそうな箇所を見つけました。

ベクトル命令の場合は、オフセット付きアドレスを拒否してほしいです。試しに win = true; の部分を win = false; に変更するとどんな動きをするでしょうか?

常に win = false にしたときの動作

// gcc/lra-constraints.c

static bool
process_alt_operands (int only_alternative)
{

...

  for (nalt = 0; nalt < n_alternatives; nalt++)  //★★基本的には全ての選択肢を検討するのだが、losers が 0 だと break する(ループ終端(1000 行後)で判定している)
    {

...

      for (nop = 0; nop < n_operands; nop++)
	{

...


	  costly_p = false;
	  do
	    {
	      switch ((c = *p, len = CONSTRAINT_LEN (c, p)), c)
		{

...

		default:
		  cn = lookup_constraint (p);
		  switch (get_constraint_type (cn))
		    {

...

		    case CT_MEMORY:
		      if (MEM_P (op)
			  && satisfies_memory_constraint_p (op, cn))  //★★今回はこちら
			win = false;  //★★常に false にする

...

	    }
	  while ((p += len), c);

	  scratch_p = (operand_reg[nop] != NULL_RTX
		       && lra_former_scratch_p (REGNO (operand_reg[nop])));
	  /* Record which operands fit this alternative.  */
	  if (win)  //★★不成立、this_alternative_win は false のまま
	    {
	      this_alternative_win = true;

...

	    }
	  else if (did_match)
	    this_alternative_match_win = true;
	  else
	    {
	      int const_to_mem = 0;
	      bool no_regs_p;

	      reject += op_reject;

...

	      /* If the operand is dying, has a matching constraint,
		 and satisfies constraints of the matched operand
		 which failed to satisfy the own constraints, most probably
		 the reload for this operand will be gone.  */
	      if (this_alternative_matches >= 0
		  && !curr_alt_win[this_alternative_matches]
		  && REG_P (op)
		  && find_regno_note (curr_insn, REG_DEAD, REGNO (op))
		  && (hard_regno[nop] >= 0
		      ? in_hard_reg_set_p (this_alternative_set,
					   mode, hard_regno[nop])
		      : in_class_p (op, this_alternative, NULL)))  //★★不成立
		{

...

		}
	      else
		{
		  /* Strict_low_part requires to reload the register
		     not the sub-register.  In this case we should
		     check that a final reload hard reg can hold the
		     value mode.  */
		  if (curr_static_id->operand[nop].strict_low
		      && REG_P (op)
		      && hard_regno[nop] < 0
		      && GET_CODE (*curr_id->operand_loc[nop]) == SUBREG
		      && ira_class_hard_regs_num[this_alternative] > 0
		      && (!targetm.hard_regno_mode_ok
			  (ira_class_hard_regs[this_alternative][0],
			   GET_MODE (*curr_id->operand_loc[nop]))))  //★★不成立
		    {

...

		    }
		  losers++;  //★★この変数が 0 以外だと、続けて他の選択肢も検討される
		}

...

	      if (MEM_P (op) && offmemok)
		addr_losers++;
	      else

...

	  curr_alt[nop] = this_alternative;
	  curr_alt_set[nop] = this_alternative_set;
	  curr_alt_win[nop] = this_alternative_win;  //★★false
	  curr_alt_match_win[nop] = this_alternative_match_win;
	  curr_alt_offmemok[nop] = this_alternative_offmemok;
	  curr_alt_matches[nop] = this_alternative_matches;

	  if (this_alternative_matches >= 0
	      && !did_match && !this_alternative_win)
	    curr_alt_win[this_alternative_matches] = false;

	  if (early_clobber_p && operand_reg[nop] != NULL_RTX)
	    early_clobbered_nops[early_clobbered_regs_num++] = nop;
	}

...

      ok_p = true;  //★★関数の返り値
      curr_alt_dont_inherit_ops_num = 0;

...

      /* If this alternative can be made to work by reloading, and it
	 needs less reloading than the others checked so far, record
	 it as the chosen goal for reloading.  */
      if ((best_losers != 0 && losers == 0)
	  || (((best_losers == 0 && losers == 0)
	       || (best_losers != 0 && losers != 0))
	      && (best_overall > overall
		  || (best_overall == overall
		      /* If the cost of the reloads is the same,
			 prefer alternative which requires minimal
			 number of reload regs.  */
		      && (reload_nregs < best_reload_nregs
			  || (reload_nregs == best_reload_nregs
			      && (best_reload_sum < reload_sum
				  || (best_reload_sum == reload_sum
				      && nalt < goal_alt_number))))))))
	{
	  for (nop = 0; nop < n_operands; nop++)
	    {
	      goal_alt_win[nop] = curr_alt_win[nop];  //★★false
	      goal_alt_match_win[nop] = curr_alt_match_win[nop];
	      goal_alt_matches[nop] = curr_alt_matches[nop];
	      goal_alt[nop] = curr_alt[nop];
	      goal_alt_offmemok[nop] = curr_alt_offmemok[nop];
	    }
	  goal_alt_dont_inherit_ops_num = curr_alt_dont_inherit_ops_num;
	  for (nop = 0; nop < curr_alt_dont_inherit_ops_num; nop++)
	    goal_alt_dont_inherit_ops[nop] = curr_alt_dont_inherit_ops[nop];
	  goal_alt_swapped = curr_swapped;
	  best_overall = overall;
	  best_losers = losers;
	  best_reload_nregs = reload_nregs;
	  best_reload_sum = reload_sum;
	  goal_alt_number = nalt;
	}
      if (losers == 0)  //★★最初の方の for (nalt = 0; nalt < n_alternatives; nalt++) を break するかどうか決めてる
	/* Everything is satisfied.  Do not process alternatives
	   anymore.  */
	break;
    fail:
      ;
    }
  return ok_p;
}

長ったらしくてわかりにくいですが、今回注目したい制御条件は win に関係する部分です。

  • this_alternative_win が false
  • curr_alt_win[nop] が false(nop = 0 のとき)
  • goal_alt_win[nop] が false(nop = 0 のとき)

関数 process_alt_operands() の返り値は bool 型で、false は他のオペランドの選択肢はない、true は他の選択肢があるという意味だそうです。今回は true を返します。だから何?と思いますが、あとでこの値がちょっとだけ出ます。

関数の最後に設定する goal_alt_win というグローバル変数が、process_alt_operands() 関数の「外側」の制御に影響を及ぼします。これはひどい。良い子の皆さんはこういうコードを書いてはいけません。

今回注目している insn のオペランドは 2つあり、nop = 0 がメモリのオペランド、nop = 1 がレジスタのオペランドです。デバッガで追いかけると this_ なんちゃらの値はそれぞれ下記のようになっていました。

各オペランドの win の値
---------- nop = 0
 op = (mem/c:V64SI (plus:SI (reg/f:SI 108)
        (const_int 256 [0x100])) [1 v1+0 S256 A2048])

 this_alternative = NO_REGS
 this_alternative_set = {elts = {0, 0}}
 this_alternative_win = false
 this_alternative_match_win = false
 this_alternative_offmemok = true
 this_alternative_matches = -1

---------- nop = 1
 op = (reg:V64SI 109 [ v1 ])

 this_alternative = VP_REGS
 this_alternative_set = {elts = {0, 4294967295}}
 this_alternative_win = true
 this_alternative_match_win = false
 this_alternative_offmemok = false
 this_alternative_matches = -1

似たような名前の変数 this_, curr_, goal_ が 3つ出てきます。勝ち抜き方式にして一番良い選択肢を残しているようです。一番内側の for ループで this_XX(ローカル変数)の値を作り、良さそうな値なら curr_alt_XX 配列(static 変数)に代入します。curr_alt_ はどんどん上書きされ、最後に残った値が goal_alt_XX 配列(グローバル変数)に代入されます。

処理の骨組みだけ示すと下記のようになります。

goal_alt_XX[op] が決まる仕組み

process_alt_operands
{
  for (nalt = 0; nalt < n_alternatives; nalt++)  //★★選択肢の数だけループ
    {
      for (nop = 0; nop < n_operands; nop++)  //★★オペランドの数だけループ
	{
          {
            //★★this_XX を決める戦い
          }

          //★★this_XX が決められないなら、次のオペランドを処理

          //★★選ばれし this_XX が curr_alt_XX[op] に保存(後の選択肢のほうがさらに良ければ上書きされる)
        }
    }

    //★★curr_alt_XX[op] を goal_alt_XX[op] に保存
}

最終的に goal_alt_XX の値がどうなるかというと、

最終的な goal_alt_XX の値
 goal_alt           = {NO_REGS, VP_REGS, }
 goal_alt_win       = {false, true, }
 goal_alt_match_win = {false, false, }
 goal_alt_offmemok  = {true, false, }
 goal_alt_matches   = {-1, -1, }

関数 process_alt_operands() の最後でブレークして goal_alt_XX の値をダンプしました。コードを追いかけて求めるのはかなり困難です……。

グローバル変数 goal_alt_win を変更したことによる影響は、また次回。

[編集者: すずき]
[更新: 2020年 6月 1日 14:12]

コメント一覧

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



link permalink

link 編集する

GCC を調べる - その 13-3 - オフセット付きアドレスの分割

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

前回(2020年 5月 30日の日記参照)見たとおり、関数 process_alt_operands() がグローバル変数 goal_alt_win を変更しました。

フラグの変更が呼び出し元の curr_insn_transform() にどう影響するか調べます。RTL が変化するところに、適宜コメントを入れました。

goal_alt_win の影響

// gcc/lra-constraints.c

static bool
curr_insn_transform (bool check_only_p)
{

...

  if (process_alt_operands (reused_alternative_num))
    alt_p = true;

...

  n_outputs = 0;
  outputs[0] = -1;
  for (i = 0; i < n_operands; i++)
    {

      int regno;
      bool optional_p = false;
      rtx old, new_reg;
      rtx op = *curr_id->operand_loc[i];

      //★★ goal_alt_win = {false, true, }

      if (goal_alt_win[i])  //★★ i = 0 メモリオペランドのときは成立しない
        {

...
	}

      //★★ goal_alt_matches   = {-1, -1, }

      /* Operands that match previous ones have already been handled.  */
      if (goal_alt_matches[i] >= 0)  //★★成立しない
	continue;

      //★★ goal_alt_matched[i] = {-1, -47, ...}
      //★★ goal_alt_offmemok  = {true, false, }

      /* We should not have an operand with a non-offsettable address
	 appearing where an offsettable address will do.  It also may
	 be a case when the address should be special in other words
	 not a general one (e.g. it needs no index reg).  */
      if (goal_alt_matched[i][0] == -1 && goal_alt_offmemok[i] && MEM_P (op))  //★★成立する
	{
	  enum reg_class rclass;
	  rtx *loc = &XEXP (op, 0);
	  enum rtx_code code = GET_CODE (*loc);

	  push_to_sequence (before);
	  rclass = base_reg_class (GET_MODE (op), MEM_ADDR_SPACE (op),
				   MEM, SCRATCH);
	  if (GET_RTX_CLASS (code) == RTX_AUTOINC)
	    new_reg = emit_inc (rclass, *loc, *loc,
				/* This value does not matter for MODIFY.  */
				GET_MODE_SIZE (GET_MODE (op)));
	  else if (get_reload_reg (OP_IN, Pmode, *loc, rclass, FALSE,
				   "offsetable address", &new_reg))  //★★オフセットの設定が 2つの RTL に分割される
	    {
	      rtx addr = *loc;
	      enum rtx_code code = GET_CODE (addr);
	      
	      if (code == AND && CONST_INT_P (XEXP (addr, 1)))
		/* (and ... (const_int -X)) is used to align to X bytes.  */
		addr = XEXP (*loc, 0);
	      lra_emit_move (new_reg, addr);  //★★ベースレジスタにオフセットを加算する RTL を作成

	      //★★こんなのが作成される
	      //(insn 22 0 0 (set (reg:SI 114)
	      //        (plus:SI (reg/f:SI 108)
	      //            (const_int 256 [0x100]))) 3 {addsi3}
	      //     (nil))

	      if (addr != *loc)
		emit_move_insn (new_reg, gen_rtx_AND (GET_MODE (new_reg), new_reg, XEXP (*loc, 1)));
	    }
	  before = get_insns ();
	  end_sequence ();

          //★★*loc と new_reg の値
          //
	  //*loc = (plus:SI (reg/f:SI 108)
	  //    (const_int 256 [0x100]))
	  //
	  //new_reg = (reg:SI 114)

	  //★★Before:
	  //                                   ↓この RTL plus が reg に置き換わる
	  //(insn 10 11 13 2 (set (mem/c:V64SI (plus:SI (reg/f:SI 108)
	  //                (const_int 256 [0x100])) [1 v1+0 S256 A2048])
	  //        (reg:V64SI 109 [ v1 ])) "b.c":8:2 134 {*movv64si_internal}
	  //     (expr_list:REG_DEAD (reg:V64SI 109 [ v1 ])
	  //        (nil)))
	  //
	  //★★After:
	  //
	  //(insn 10 11 13 2 (set (mem/c:V64SI (reg:SI 114) [1 v1+0 S256 A2048])
	  //        (reg:V64SI 109 [ v1 ])) "b.c":8:2 134 {*movv64si_internal}
	  //     (expr_list:REG_DEAD (reg:V64SI 109 [ v1 ])
	  //        (nil)))

	  *loc = new_reg;
	  lra_update_dup (curr_id, i);
	}

...

  lra_process_new_insns (curr_insn, before, after, "Inserting insn reload");  //★★オフセットを事前計算する RTL が追加される
  return change_p;
}

最後の lra_process_new_insns() はちょっとややこしくて、1つ目の引数(curr_insn)の前に 2つ目の引数(before)の RTL を足し、後ろに 3つ目の引数を(after)を足す関数です。図示したほうがわかりやすいですね。

  • 呼び出し前: (curr_insn)
  • 呼び出し後: (before) (curr_insn) (after)

関数呼び出し前後の RTL の中身を下記に示します。今回は after は NULL なので、curr_insn の後ろには何も足されません。

lra_process_new_insns() の前

(insn 21 7 11 2 (set (reg:SI 113)
        (plus:SI (reg/f:SI 97 frame)
            (const_int -376 [0xfffffffffffffe88]))) "b.c":8:2 3 {addsi3}
     (nil))

(insn 11 21 10 2 (set (reg:V64SI 109 [ v1 ])
        (asm_operands/v:V64SI ("vlw.v %0, %1") ("=&v") 0 [
                (mem/c:SI (reg:SI 113) [1 b+40 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:8)
            ]
             [] b.c:8)) "b.c":8:2 -1
     (nil))

★curr_insn は↓の RTL を指している

(insn 10 11 13 2 (set (mem/c:V64SI (reg:SI 114) [1 v1+0 S256 A2048])    ★*loc = new_reg; でオペランド変更済み
        (reg:V64SI 109 [ v1 ])) "b.c":8:2 134 {*movv64si_internal}
     (expr_list:REG_DEAD (reg:V64SI 109 [ v1 ])
        (nil)))

(insn 13 10 12 2 (set (reg:V64SI 110 [ v2 ])
        (asm_operands/v:V64SI ("vlw.v %0, %1") ("=&v") 0 [
                (mem/c:SI (plus:SI (reg/f:SI 97 frame)
                        (const_int -336 [0xfffffffffffffeb0])) [1 b+80 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:9)
            ]
             [] b.c:9)) "b.c":9:2 -1
     (nil))

...
lra_process_new_insns() の後

(insn 21 7 11 2 (set (reg:SI 113)
        (plus:SI (reg/f:SI 97 frame)
            (const_int -376 [0xfffffffffffffe88]))) "b.c":8:2 3 {addsi3}
     (nil))

(insn 11 21 22 2 (set (reg:V64SI 109 [ v1 ])
        (asm_operands/v:V64SI ("vlw.v %0, %1") ("=&v") 0 [
                (mem/c:SI (reg:SI 113) [1 b+40 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:8)
            ]
             [] b.c:8)) "b.c":8:2 -1
     (nil))

★before は↓の RTL を指している

(insn 22 11 10 2 (set (reg:SI 114)    ★この RTL が追加された(事前にアドレスを格納するレジスタにオフセットを足すため)
        (plus:SI (reg/f:SI 108)
            (const_int 256 [0x100]))) "b.c":8:2 3 {addsi3}
     (nil))

★curr_insn は↓の RTL を指している

(insn 10 22 13 2 (set (mem/c:V64SI (reg:SI 114) [1 v1+0 S256 A2048])
        (reg:V64SI 109 [ v1 ])) "b.c":8:2 134 {*movv64si_internal}
     (expr_list:REG_DEAD (reg:V64SI 109 [ v1 ])
        (nil)))

(insn 13 10 12 2 (set (reg:V64SI 110 [ v2 ])
        (asm_operands/v:V64SI ("vlw.v %0, %1") ("=&v") 0 [
                (mem/c:SI (plus:SI (reg/f:SI 97 frame)
                        (const_int -336 [0xfffffffffffffeb0])) [1 b+80 S4 A64])
            ]
             [
                (asm_input:SI ("A") b.c:9)
            ]
             [] b.c:9)) "b.c":9:2 -1
     (nil))

...

ベクトル命令の RTL の前にアドレスを計算する RTL が出力されました。うまくいってそうです。

うまくいっているけど、もう一息

話が長くなってきて忘れてしまいそうですが、やりたかったことは、ベクトル命令のオペランドにオフセット付きアドレスを指定しないことです。

先程まで見てきたように、常に win = false にすれば目的を達成しているように見えます。しかし残念ながら常にオフセット付きアドレスを拒絶することになり、スカラ命令にも影響が出てしまいます。変更前と変更後のアセンブラを比較します。

変更前後のアセンブラ比較

--- b_before.s  2020-05-28 21:17:24.607184754 +0900
+++ b_after.s   2020-05-28 21:14:31.375142095 +0900
@@ -21,8 +21,10 @@
        addi    s0,sp,1200
        .cfi_def_cfa 8, 0
        addi    a5,s0,-16

  #★★スカラ命令なのにアドレスオペランドのオフセット設定が分解されている(この分割は本来不要)

-       sw      a5,-1188(s0)
-       lw      a5,-1188(s0)
+       addi    a4,s0,-1188
+       sw      a5,0(a4)
+       addi    a5,s0,-1188
+       lw      a5,0(a5)
        addi    a5,a5,-1168
        addi    a5,a5,255
        srli    a5,a5,8
@@ -35,7 +37,8 @@
 
 # 0 "" 2
  #NO_APP

  #★★ベクトル命令のアドレスオペランドのオフセット設定が分解されるのは狙い通り

-       vsw.v   v0,256(a5)
+       addi    a4,a5,256
+       vsw.v   v0,0(a4)
        .loc 1 9 2
        addi    a4,s0,-336
  #APP

ベクトル命令は狙い通りですが、スカラ命令までアドレスオペランドのオフセット設定が分解されてしまいました。この分割は本来不要です。

これをどう抑えるかはまた次回。

[編集者: すずき]
[更新: 2020年 6月 1日 14:12]

コメント一覧

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



こんてんつ

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 サイトの情報