はるか昔(2007年6月7日の日記参照)に購入したピアノ音源PMI Boesendorfer 290を現在使っているPCにインストールしました。
当時の日本代理店はクリプトンでしたが、2008年に違う会社に変わったようです。
それはさておき、ソフトは古くても(13年前!)音源自体はインストールでき、音も正常に鳴ります。Windowsの後方互換性は本当に素晴らしいなと思います。しかしアクティベーションだけがどうしてもできなくて困っています。
Boesendorfer 290はインストール時にシリアル番号の入力が必要で、インストール後はアクティベーションが必要です。アクティベーションは各PC固有の番号と紐付いていて、別のPCにインストールしたときは再アクティベーションが必要となります。面倒くさいですね。
アクティベーションをしない状態で使い続けると、インストール後5日間に動かなくなってしまいます。
購入当時はアクティベート画面の「REGISTER NOW」を押せば、オンラインでアクティベーションできましたが、現在このボタンを押すとエラーになります。
アクティベーション用のサイトのURLが変わったのか、ブラウザの接続警告が出ますし、転送先のサイトでは「そんなページは知らない」って言われます。これはひどい。
オンラインがダメならオフラインアクティベーションを試そうと思い「FILL OUT FORM」を押してみましたが、やっぱりダメでした。
エラーメッセージを見てもInternet browserの何がダメかわかりません。当然、ユーザーとしても対処しようがありません。このソフトを開発した人たちも、まさか13年後にアクティベーションする奴がいるとは思っていなかったでしょう。
これ以上どうにもならないので、サポートに連絡することにしました。Boesendorfer 290の販売はNative Instruments社、開発はEastWest社とありますが、どこに連絡すれば良いかわかりません。とりあえず販売Native Instruments社のサポートに連絡してみようと思います。
目次: Kindle
割と昔からですがKindle Fireがストアで買った本を認識しない(ダウンロードしない、一覧に出てこない)病気が頻発し、買ったはずの本が消えて、非常に困っています。
Kindle Fireがロストした本は、Kindle for PCだとあっさり捕捉できますから(「新しい商品」順に並べると確認しやすい)、Kindle Fireのバグだと思われます。
根本対策は不明ですが、対症療法はいくつかあります。お手軽な順に、
Kindleストアも割と曲者で、複数のダウンロードボタンがある変な仕様(2018年11月17日の日記参照)です。1回押してダメでも諦めず、他の種類のダウンロードボタンも押してください。どれかが成功すればラッキーです。
最終手段ファクトリリセットには1度だけお世話になりました。しかし再設定、再ダウンロードにかなり時間を浪費して辛いので、できればもう二度とやりたくありません。
Kindle Fireはちょっと変な挙動が多いです。仕様かバグか良くわからないものもあります。
Kindleのメインであるはずの本の機能さえ、この有様なので、本以外(アプリ、ミュージック、ムービーなど)の機能は一体どうなっていることやら??
メモ: 技術系の話はFacebookから転記しておくことにした。大幅に追記。
目次: GCC
FSF (Free Software Foundation) のCopyright Assignmentにサインしてみました。とりあえずGCCとbinutilsです。ツールチェーン仲間としてはglibcもやっておけば良かったかも??まあいいか。
手続きは特に難しくなく、
これで完了です。
FSFからの返事はそれぞれ数日〜1週間程度、という感じでした。GNUのソフトウェアにパッチを送りたいときは、個人でこの契約を交わす(今回はこっち)か、会社の業務で作成している場合は、会社でこの契約を交わす(こっちのやり方は知らない)必要があります。
FSFがなぜこんな面倒なことをしているのかについては、なぜFSFは貢献者に著作権の譲渡をお願いしているのか - GNUプロジェクトに書いてあります。過去に訴えられたり何か嫌なことがあったんでしょうね。
テンプレートは Copyright Papers (Information for Maintainers of GNU Software) を見るとGNUのメンテナーに要求してくれと書いてあります。gnulibのリポジトリにも入っていて、私はこのファイルを送ったらOKでした(gnulibのgitリポジトリにあるrequest-assign.future)
FSFのCopyright Assignmentにはいろいろ書いてありますが、
読んでいると割と怖い内容ですが、会社で書いたコードを投稿しなければ問題はないです。
契約の範囲はソフトウェア毎(GCC, binutils, glibc, などなど)に契約する必要があります。契約の種類は「1回限り(request-assign.changes)」と「今後ずっと(request-assign.future)」があります。ソフトウェアを改善し続ける場合、毎回契約するのは面倒なので「今後ずっと」を選択すると思います。
契約の種類によってFSFに送るメールのテンプレートが異なります。詳細は Copyright Papers (Information for Maintainers of GNU Software) に書いてあります。和訳ないのかなあ、これ。
目次: GCC
以前(2020年5月24日の日記参照)、変数の代入操作をexpandで展開する際、代入を分割するか分割しないか、を決める条件の1つとして、optab_handler() という関数が出てきました。この関数の動作に関わるgenopinitというツールの動作を調べます。
前回(2020年6月3日の日記参照)は分岐条件に関わるenum optab_tagの生成について調べました。今回はもう少し複雑なpats[].scodeの生成について調べます。
突然pats[].scodeと言われても何だかわからないと思います。私もこんなひどい名前の変数、全く覚えられません。復習も兼ねてoptab_handler() を見直します。
// 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; //★★scodeの意味はここにある通り
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); //★★これ
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 },
...
{ 0x021c1b, CODE_FOR_truncdfsf2 }, //★★この変な数字(scode)0x021c1bは誰が作るのか?
...
コードからscodeの意味、上位16ビットがoptabで、下位16ビットがmachine mode、は理解できると思います。patsにはscodeが無数に並んでいますが、この数字を誰が作るかというとgenopinitです。
前に見たoptabsの定義は色々ありますが、必ず2つ以上の引数を取り、1番目がname(mov_optab, trunc_optabなど)、2番目がpatternとなっており、この2つは全ての定義に存在します。
// gcc/optabs.def
OPTAB_CL(trunc_optab, "trunc$b$a2", TRUNCATE, "trunc", gen_trunc_conv_libfunc)
'-- name `-- pattern
このうちnameはenum optab_tagの変数名として使っていました。その他にもcode_to_optab_[] という配列の定義にも使いますが、今はどうでもいいです。前後に文字が付くくらいで、基本的に名前がそのまま使われます。
一方2番目のpattern(例えば "mov$a", "trunc$b$a2" など)はちょっと変わっています。$aや $bという不思議な文字が入ります。
前回説明したとおりgenopinitの入力はgcc/common.md, gcc/config/riscv/riscv.md, build_gcc/insn-conditions.mdの3つの *.mdファイルです(mdはmachine descriptorの略)。Lispっぽい記法で、define_insnやdefine_expandを追加したファイルです。
基本的なgenopinitの動作は、
パターンマッチのコードはこんな感じです。
// gcc/gensupport.h
/* Information about an .md define_* rtx. */
class md_rtx_info {
public:
/* The rtx itself. */
rtx def;
/* The location of the first line of the rtx. */
file_location loc;
/* The unique number attached to the rtx. Currently all define_insns,
define_expands, define_splits, define_peepholes and define_peephole2s
share the same insn_code index space. */
int index;
};
// gcc/genopinit.c
int
main (int argc, const char **argv)
{
...
if (!init_rtx_reader_args_cb (argc, argv, handle_arg)) //★★ *.mdを解析
return (FATAL_EXIT_CODE);
...
/* Read the machine description. */
md_rtx_info info;
while (read_md_rtx (&info)) //★★RTXを1つずつ取り出す
switch (GET_CODE (info.def))
{
case DEFINE_INSN: //★★define_insn, define_expandだけ注目
case DEFINE_EXPAND:
gen_insn (&info); //★★これ
break;
static void
gen_insn (md_rtx_info *info)
{
optab_pattern p;
if (find_optab (&p, XSTR (info->def, 0))) //★★これ
patterns.safe_push (p);
}
// gcc/gensupport.c
bool
find_optab (optab_pattern *p, const char *name)
{
...
/* See if NAME matches one of the patterns we have for the optabs
we know about. */
for (unsigned int pindex = 0; pindex < ARRAY_SIZE (optabs); pindex++) //★★全てのpatternを試す
{
p->m1 = p->m2 = 0;
if (match_pattern (p, name, optabs[pindex].pattern)) //★★これ
{
p->name = name;
p->op = optabs[pindex].op;
p->sort_num = (p->op << 16) | (p->m2 << 8) | p->m1; //★★scodeを作る
return true;
}
// gcc/gensupport.h
/* Information about an instruction name that matches an optab pattern. */
struct optab_pattern
{
/* The name of the instruction. */
const char *name;
/* The matching optab. */
unsigned int op;
/* The optab modes. M2 is only significant for conversion optabs;
it is zero otherwise. */
unsigned int m1, m2;
/* An index that provides a lexicographical sort of (OP, M2, M1).
Used by genopinit.c. */
unsigned int sort_num;
};
申し訳ないですが *.mdファイルの解析関数init_rtx_reader_args_cb(), read_md_rtx() 辺りは調べる予定がありません。どなたか調べてくれたら嬉しいです。
パターンマッチはnameに対して行われ、全てのpatternとマッチするか試します。マッチしなければmatch_pattern() はfalseを返しますから、次のpatternを試します。マッチしたら、結果はoptab_pattern *pに格納され、match_patter() が作り出すp->m1, p->m2という謎の数からscodeが生成されます。
なぜgenopinitではsort_numという変数名にしたんでしょうね?scodeにすればもう少しわかりやすいのに。
パターンマッチのルールと、m1, m2が何者か?については、文章で説明できる気がしないので、例としてname = "truncdfsf2" を見ながら説明したいと思います。マッチするpatternは "trunc$b$a2" です。他のpatternはマッチしません。
// gcc/config/riscv/riscv.md
//★★nameの例
(define_insn "truncdfsf2" ★★名前
[(set (match_operand:SF 0 "register_operand" "=f")
(float_truncate:SF
(match_operand:DF 1 "register_operand" " f")))]
"TARGET_DOUBLE_FLOAT"
"fcvt.s.dt%0,%1"
[(set_attr "type" "fcvt")
(set_attr "mode" "SF")])
// gcc/optab.def
//★★ マッチするpatternの定義部分(2番目の引数)
OPTAB_CL(trunc_optab, "trunc$b$a2", TRUNCATE, "trunc", gen_trunc_conv_libfunc)
// gcc/gensupport.c
//★★
//pは結果格納用の変数
//name = "truncdfsf2"
//pat = "trunc$b$a2"
static bool
match_pattern (optab_pattern *p, const char *name, const char *pat)
{
...
★★match_pattern() がtrueを返した後、find_optab() がreturn trueで終了する直前でダンプ (gdb) p optabs[pindex] $13 = { name = 0x4574b5 "trunc_optab", pattern = 0x4574c1 "trunc$b$a2", base = 0x4574cc ""trunc"", suffix = 0x457478 "'\0'", libcall = 0x4574d4 "gen_trunc_conv_libfunc", op = 2, fcode = TRUNCATE, rcode = UNKNOWN, kind = 1 } (gdb) p/x *p $15 = { name = 0x4dd270, op = 0x2, m1 = 0x1b, m2 = 0x1c, sort_num = 0x21c1b } pは結果格納用のoptab_pattern *p
この定義のpatternは2番目の引数 "trunc$b$a2" です。全部繋がっていてわかりにくいですが、下記の4つの要素から構成されます。
このうち $aや $bはmachine modeの名前にマッチします。具体的には下記のようになります。
nameとpatが name = "truncdfsf2" pat = "trunc$b$a2" の場合、 $b -> df $a -> sf にマッチします。$a, $bのマッチを調べるときはmode_nameを先頭から検索(大文字小文字の違いは無視)します。 全モードの名前はmode_name[] という配列に入っています。 $bはmode_name[28] = "DF" と一致する。 $aはmode_name[27] = "SF" と一致する。 match_pattern() は一致したモードの番号をm1, m2に格納します。 つまりoptab_patternのm1, m2の意味はそれぞれ m1: $aがマッチしたモードのインデックス、今回だと27 = 0x1b (E_SFmode) m2: $bがマッチしたモードのインデックス、今回だと28 = 0x1c (E_DFmode) 結果scodeはこうなります。 p->sort_num = (p->op << 16) | (p->m2 << 8) | p->m1; = (2 << 16) | (28 << 8) | 27; = 0x21c1b // build_gcc/insn-opinit.c static const struct optab_pat pats[NUM_OPTAB_PATTERNS] = { ... { 0x021c1b, CODE_FOR_truncdfsf2 }, //★★この値が生成された
謎のpats[].scodeはこんな仕組みで生成されていたのでした。ややこしいですね。
目次: GCC
以前(2020年5月24日の日記参照)、変数の代入操作をexpandで展開する際、代入を分割するか分割しないか、を決める条件の1つとして、optab_handler() という関数が出てきました。optab_handler() の動作を決めるgenopinitというツールの動作を調べます。
バイナリはbuild_gcc/build/genopinitに生成されます(以降build_gccはGCCのビルドディレクトリを指すものとします)。実行する際の引数は下記の通りです。
# 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
上記のビルドログでは一時ファイルに出力していますが、最終的にはbuild_gcc/insn-opinit.h, build_gcc/insn-opinit.cの2つのファイルを生成します。
前回はoptab_handler() にブレークポイントを設定するため、enum optab_tagの値を使いました。この値はgenopinitが生成しています。
// 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.h
enum optab_tag {
unknown_optab,
sext_optab,
...
mov_optab, //★★この値のこと
...
};
...
typedef enum optab_tag optab;
これらの値はgensupport.cのoptab_defから生成されています。実際にoptab_defの中身を定義するのはoptabs.defファイルです。この *.defファイルは、C言語で素直に書くと重複、冗長になる情報を簡潔に表すための、いわゆるDSL(Domain Specific Language)だと思われます。
DSLっぽいものを使うとき、下記のような邪悪なincludeの使い方をします。GCC凶悪デザインパターンの1つですね。良い子は真似してはいけません。
// gcc/gensupport.c
#define OPTAB_DC(o, p, c) { #o, p, NS, ZS, NS, o, c, c, 4 },
#define OPTAB_D(o, p) { #o, p, NS, ZS, NS, o, UNKNOWN, UNKNOWN, 4 },
...
/* An array of all optabs. Note that the same optab can appear more
than once, with a different pattern. */
optab_def optabs[] = {
{ "unknown_optab", NULL, NS, ZS, NS, unknown_optab, UNKNOWN, UNKNOWN, 0 },
#include "optabs.def" //★★上記のようにoptabs.def内で使われるマクロを、都合の良い定義に変えてからinclude
};
// gcc/optabs.def
OPTAB_DC(mov_optab, "mov$a", SET) //★★マクロの定義はincludeされる場所によって違うので、展開後の結果は場所による
OPTAB_DC(movstrict_optab, "movstrict$a", STRICT_LOW_PART)
OPTAB_D (movmisalign_optab, "movmisalign$a")
...
// gcc/genopinit.c
int
main (int argc, const char **argv)
{
...
/* Emit the optab enumeration for the header file. */
fprintf (h_file, "enum optab_tag {\n");
for (i = j = 0; i < n; ++i)
{
optabs[i].op = i;
fprintf (h_file, " %s,\n", optabs[i].name); //★★optabsの名前を出力
if (optabs[i].kind != j)
last_kind[j++] = i - 1;
}
fprintf (h_file, " FIRST_CONV_OPTAB = %s,\n", optabs[last_kind[0]+1].name);
...
せっかくDSLっぽいものがあるのに、genopinitのようにコード自動生成ツールも混ぜて使うのはどうしてなんでしょう?GCCは理解不能です。
目次: GCC
GCCについて。
GCCを含めたツールチェーンについて。
目次: GCC
前回(2020年5月31日の日記参照)見たとおり、メモリというかオフセット付きアドレスを全部禁止するのは明らかにやりすぎで、他の命令に悪影響を及ぼしていました。スカラ命令はオフセット付きアドレスを許可し、ベクトル命令だけオフセット付きアドレスを禁止したいところです。
オペランドを許可する、しないを判断するコードを改造すればできるでしょうか?コードを見てみます。
// gcc/common.md
(define_memory_constraint "TARGET_MEM_CONSTRAINT"
"Matches any valid memory."
(and (match_code "mem")
(match_test "memory_address_addr_space_p (GET_MODE (op), XEXP (op, 0),
MEM_ADDR_SPACE (op))")))
// gcc/recog.c
//★★各引数の値
//mode = E_V64SImode
//addr = (plus:SI (reg/f:SI 108)
// (const_int 256 [0x100]))
//as = 0
int
memory_address_addr_space_p (machine_mode mode ATTRIBUTE_UNUSED,
rtx addr, addr_space_t as)
{
#ifdef GO_IF_LEGITIMATE_ADDRESS
...
#else
return targetm.addr_space.legitimate_address_p (mode, addr, 0, as);
#endif
}
// gcc/targhook.c
//★★各引数の値
//mode = E_V64SImode
//mem = (plus:SI (reg/f:SI 108)
// (const_int 256 [0x100]))
//strict = 0
//as = 0
bool
default_addr_space_legitimate_address_p (machine_mode mode, rtx mem,
bool strict,
addr_space_t as ATTRIBUTE_UNUSED)
{
return targetm.legitimate_address_p (mode, mem, strict);
}
// gcc/config/riscv/riscv.c
#undef TARGET_LEGITIMATE_ADDRESS_P
#define TARGET_LEGITIMATE_ADDRESS_P riscv_legitimate_address_p
// gcc/config/riscv/riscv.c
//★★各引数の値
//mode = E_V64SImode
//mem = (plus:SI (reg/f:SI 108)
// (const_int 256 [0x100]))
//strict_p = 0
static bool
riscv_legitimate_address_p (machine_mode mode, rtx x, bool strict_p)
{
struct riscv_address_info addr;
return riscv_classify_address (&addr, x, mode, strict_p);
}
// gcc/config/riscv/riscv.c
//★★各引数の値
//x = (plus:SI (reg/f:SI 108)
// (const_int 256 [0x100]))
//mode = E_V64SImode
//strict_p = 0
static bool
riscv_classify_address (struct riscv_address_info *info, rtx x,
machine_mode mode, bool strict_p)
{
switch (GET_CODE (x))
{
...
case PLUS:
info->type = ADDRESS_REG;
info->reg = XEXP (x, 0);
info->offset = XEXP (x, 1);
//★★各変数の値
//info->reg = x->u.fld[0].rt_rtx = (reg/f:SI 108)
//info->offset = x->u.fld[1].rt_rtx = (const_int 256 [0x100])
return (riscv_valid_base_register_p (info->reg, mode, strict_p)
&& riscv_valid_offset_p (info->offset, mode));
...
}
}
関数memory_address_addr_space_p() のaddrを見ると、メモリアドレスを表すRTLしか渡されません。この情報だけではスカラ命令のオペランドか、ベクトル命令のオペランドか、判断するのは困難です。
ベクトル命令はmachine modeがV64SIであることを利用するとうまくいくかもしれません。関数riscv_classify_address() を変更し、mode == V64SIだったらPLUSなどREG以外を使ったRTLに対しfalseを返せば良さそうです。
// gcc/config/riscv/riscv.c
//★★各引数の値
//x = (plus:SI (reg/f:SI 108)
// (const_int 256 [0x100]))
//mode = E_V64SImode
//strict_p = 0
static bool
riscv_classify_address (struct riscv_address_info *info, rtx x,
machine_mode mode, bool strict_p)
{
switch (GET_CODE (x))
{
...
case PLUS:
if (mode == E_V64SImode) //★★machine modeがV64SIならオフセット付きアドレスは許可しない
return false;
info->type = ADDRESS_REG;
info->reg = XEXP (x, 0);
info->offset = XEXP (x, 1);
return (riscv_valid_base_register_p (info->reg, mode, strict_p)
&& riscv_valid_offset_p (info->offset, mode));
...
}
}
$ diff -u b_before.s b_mod.s
--- b_before.s 2020-05-28 21:17:24.607184754 +0900
+++ b_mod.s 2020-05-30 22:37:02.370663628 +0900
@@ -35,7 +35,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
結果だけ見ると良さそうですが、将来的にオフセットアドレスが使えるベクトル命令が出てきたときに、判別不能になり困ります。その場しのぎ感が否めません。もっと良い方法はあるでしょうか?
実はもっと簡単な方法で対処できます。変更すべき箇所については、この取り組みの発端「constraint "m" をチェックしていそうな箇所から調査を開始した」ことを思い出していただければ想像が付くと思います。constraint "m" を使っている箇所は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"))] //★★constraint "m" を使っている場所
"(register_operand (operands[0], V64SImode)
|| reg_or_0_operand (operands[1], V64SImode))" return riscv_output_move (operands[0], operands[1]);
[(set_attr "move_type" "move,load,store")
(set_attr "vecmode" "V64SI")])
この "m" を変更して、オフセット付きアドレスオペランドだけを拒否したいですが、そんな都合の良いconstraintはあるでしょうか?実はRISC-Vには既にあります。constraint "A" です。
;; gcc/config/riscv/constraints.md
(define_memory_constraint "A"
"An address that is held in a general-purpose register."
(and (match_code "mem")
(match_test "GET_CODE(XEXP(op,0)) == REG")))
コードを見ると、constraint "A" が見ている条件は、種別がメモリであり、オペランドがREG(オフセットありだとPLUSなどになる)であることです。先程の改造と同じ発想ですね。mをAに変更します。
;; gcc/config/riscv/riscv.md
(define_insn "*movv64si_internal"
[(set (match_operand:V64SI 0 "nonimmediate_operand" "=v,v,A")
(match_operand:V64SI 1 "move_operand" " v,A,v"))] //★★constraint "A" に変更
...
$ diff -u b_before.s b_final.s
--- b_before.s 2020-05-28 21:17:24.607184754 +0900
+++ b_final.s 2020-05-28 21:20:20.219175573 +0900
@@ -35,7 +35,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
出力結果のアセンブリも良い感じですし、ビルドの際にアセンブラも文句を言わなくなりました。
長きに渡りましたが、やっとベクトル型を使ったGCC拡張インラインアセンブラが書けるようになりました。良かった良かった。
目次: GCC
前回(2020年5月30日の日記参照)見たとおり、関数process_alt_operands() がグローバル変数goal_alt_winを変更しました。
フラグの変更が呼び出し元のcurr_insn_transform() にどう影響するか調べます。RTLが変化するところに、適宜コメントを入れました。
// 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)を足す関数です。図示したほうがわかりやすいですね。
関数呼び出し前後のRTLの中身を下記に示します。今回はafterはNULLなので、curr_insnの後ろには何も足されません。
(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))
...
(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
ベクトル命令は狙い通りですが、スカラ命令までアドレスオペランドのオフセット設定が分解されてしまいました。この分割は本来不要です。
これをどう抑えるかはまた次回。
目次: GCC
前回(2020年5月29日の日記参照)メモリというかオフセット付きアドレスのconstraintをチェックしていそうな箇所を見つけました。
ベクトル命令の場合は、オフセット付きアドレスを拒否してほしいです。試しにwin = true; の部分を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に関係する部分です。
関数process_alt_operands() の返り値はbool型で、falseは他のオペランドの選択肢はない、trueは他の選択肢があるという意味だそうです。今回はtrueを返します。だから何?と思いますが、あとでこの値がちょっとだけ出ます。
関数の最後に設定するgoal_alt_winというグローバル変数が、process_alt_operands() 関数の「外側」の制御に影響を及ぼします。これはひどい。良い子の皆さんはこういうコードを書いてはいけません。
今回注目しているinsnのオペランドは2つあり、nop = 0がメモリのオペランド、nop = 1がレジスタのオペランドです。デバッガで追いかけるとthis_ なんちゃらの値はそれぞれ下記のようになっていました。
---------- 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配列(グローバル変数)に代入されます。
処理の骨組みだけ示すと下記のようになります。
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 = {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 | > | ||||
<< | < | 06 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | - | - | - | - |
合計:
本日: