日記が増えすぎて、一覧が欲しくなってきたので作りました。
今後、日記が増えたら追加します。
前回(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 拡張インラインアセンブラが書けるようになりました。良かった良かった。
前回(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
ベクトル命令は狙い通りですが、スカラ命令までアドレスオペランドのオフセット設定が分解されてしまいました。この分割は本来不要です。
これをどう抑えるかはまた次回。
前回(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 を変更したことによる影響は、また次回。
メモリからのロード、ストアに問題があるので、おそらくメモリ周りの constraint 指定が間違っていて、本来受け付けてはいけないオペランドまで受け付けていると思われますが、証拠を掴む方法が全くわかりません。
今回追加した define_expand, define_insn のうち define_expand は constraint なし、define_insn は m という標準の constraint を使っています。手がかりになりそうな constraint m から追ってみます。メモリの constraint は下記に定義があります。
// 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 の satisfies_constraint_m() という関数にコピペされていることがわかります。このルールは constraint "m" だけでなく他も同じです。関数 satisfies_constraint_(文字) が自動的に生成され、条件チェックされます。
この関数でブレークしたいですが static inline になっておりブレークが掛からないので、適当に for_break_tmp() 関数を一つ追加しておき、この関数で止めます。
// 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 で条件ブレークすると良いでしょう。
(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日の日記参照)のときに見ました、懐かしいですね。それはさておき下記のコードから呼ばれています。
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 にしています。
この辺りを変えれば、オフセット付きアドレスに変化が起きそうです。続きはまた今度。
STATIONflow バグってますね……。ゲーム進行速度を最大にすると勝手に評価が下がってしまいます。これはあんまりだ。
ゲーム進行速度を最大の 3 にすると、評価がかなり下がります。速度 1 or 2 なら A+(1枚目)ですが、速度 3 だと B(2枚目)です。他は何も変えず、速度だけ変えた結果です。
STATIONflow は評価と収入が直結しており、評価が 2段階下がると大赤字です。画像の右下が収入ですが、A+ 316000 から B 158000 に下がってますよね?
速度 3 のときの客の評価値を見ると、利用施設の待ち時間に対する評価が軒並み悪化しています。速度 3 だけ待ち時間の計算をミスってるんじゃないでしょうか?
あまりにも評価が乱高下するから、何が起きたのか結構悩んでしまいました。無駄に悩んだ時間を返せ。
ゲーム進行的に 1つの区切りと思われる、ランク 20 を超えました。駅が広くなるとちょっと困った現象が発生します。
現象としては終電が出た後に、極端に評価値が下がります。仕組みはこんな感じ。
この問題は常に発生していて、序盤は駅が狭いのと人が少ないので問題ありませんが、終盤は一気に 1,000〜2,000人規模で目的地の切り替えが発生して、最終の評価値に影響するほど下がることがあります。
終電の時間は固定で、プレーヤーの努力でどうすることもできません。理不尽だ……。
前回(2020年 5月 25日の日記参照)にて 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 を複数書きます。するとまたエラーが出ます。
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]));
}
$ 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)'
コンパイラはエラーを出していませんが、アセンブラがエラーを出しています。どうも変なオペランドが出力されているようです。うーん、惜しい。アセンブラを見ると、
...
.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年 5月 22日の日記、2020年 5月 23日の日記、2020年 5月 24日の日記参照)にて define_expand を追加し、Internal compile error: maximum number of generated reload insns per insn achieved (90) は出なくなりましたが、別のエラーが出ました。
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() という関数名から、そのような事情は掴めないですよね、普通。
|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 から持ってきているようです。
;; 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() も変わるでしょう。近しいものをコピーして作ります。
;; 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 だけ変更しました。これが合っているのかわかりませんが、ダメなら後で直しましょう。
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 ではない値が返されるようになりました。
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年 5月 23日の日記参照)の続きです。エラーの原因となる代入操作 RTL は emit_move_multi_word() で出力されており、条件の分岐点である emit_move_insn_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 します。
#### 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 のコードです。ツールの使い方がわからないのでビルドログを見ます。
# 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() を呼びます。
// 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 のときに何が起きるのか見ます。
$ 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
;; 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 では止まってほしくないので、こういうときは条件付きブレークが便利です。
(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 }
;; 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 を真似して追加します。
;; 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() が呼び出されます。念のためにコードを再掲します。
// 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() 関数が突然出てきますが、これは自動生成された関数です。
(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' }
// 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 に変わっていますし、問題なさそうに見えます。
(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 の追加だけではダメで、変更後はこんなエラーが出ます。
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 | > | ||||
<< | < | 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 | - | - | - | - |
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2016.
Powered by PHP 5.2.17.
using GD bundled (2.0.34 compatible)(png support.)