目次: ゲーム
ゲーム進行的に1つの区切りと思われる、ランク20を超えました。駅が広くなるとちょっと困った現象が発生します。
現象としては終電が出た後に、極端に評価値が下がります。仕組みはこんな感じ。
この問題は常に発生していて、序盤は駅が狭いのと人が少ないので問題ありませんが、終盤は一気に1,000〜2,000人規模で目的地の切り替えが発生して、最終の評価値に影響するほど下がることがあります。
終電の時間は固定で、プレーヤーの努力でどうすることもできません。理不尽だ……。
目次: GCC
前回(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命令はオフセット付きアドレスをオペランドに取れません。コンパイラはオフセット付きアドレスをオペランドに出力しないように抑制する必要があります。続きはまた今度。
目次: GCC
引き続き、ベクトル型を使用する際の下記の問題に取り組みます。
前回(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*)
今度こそうまく行くかと思いきや、また別のエラーが発生しました。道のりは長そうです。
目次: GCC
前回(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
続きはまた次回。
目次: GCC
前回(2020年5月22日の日記参照)の続きです。エラーの原因となる代入操作RTLはどこから出てきているのでしょう。RTLを遡っていくとexpandから出力されていることがわかります。expandはRTLの最初のパスで、GIMPLEからRTLに変換するためのパス(パス番号236)です。最初から間違っているということですね。
;;★★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() という関数で出力することを利用します。
ブレークで実行が止まったら、バックトレースを取れば呼び出しまでの経路がわかります。
(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() が怪しそうです。次回以降、この関数を中心に調べます。
目次: GCC
前回(2020年5月16日の日記、2020年5月17日の日記参照)の取り組みでインラインアセンブラでベクトル型を指定できるようになりましたが、下記の問題が残っていました。
いよいよコンパイラがInternal compile errorを出す問題に取り組みます。ベクトル型を使ったインラインアセンブラを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を比べます。
;;★★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))
...
;;★★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のとき
(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でInternal compile errorが発生しないのはラッキーに過ぎなかったようです。次回は問題となるRTLがどこから来るのか調べます。
かつてのMozillaのころからずっと使っていたSeaMonkeyですが、最近のサイトだと知らない子扱いされて悲しいのと、Cookieの削除がしづらくてたまらないので、ブラウザをFirefoxに変えました。
SeaMonkey今までありがとう。大変お世話になりました。
ブラウザに限らず、あまりアプリケーションのカスタマイズはしない方ですが、今のWindows版Firefoxは1点だけ嫌なところがあります。Ctrl+Tabを押したときにタブの一覧が出る挙動です。
Windowsのウインドウ切り替えと似ていますが、ブラウザのタブは元から横一直線に並んでいるので、リストを出す必要はないです。隣のタブに迅速に切り替えてほしい。
調べてみるとbrowser.ctrlTab.recentlyUsedOrderをfalseにすると Ctrl+Tabを押した瞬間に隣のタブに移る挙動に戻せることを知りました。とても嬉しい。これだよこれ!
目次: ゲーム
Steamで新たなゲームを買いました。2020年4月ローンチのSTATIONflow(開発: DMM GAMES)です。その名の通り地下鉄の駅を作るゲームです。
新宿や渋谷のようなモンスター駅の作り、使いづらさに憤慨したことはありませんか?「使いづらい!俺様に任せればスマートに作ってやる」と思う方はぜひチャレンジしてください。
私もそう思ってたのですが、ゲームをやってみて「思い上がりでした、ごめんなさい、JRさん尊敬してます。」反省しました。油断するとすぐに、客を何度も上り下りさせて、べらぼうに長距離歩かせて、挙句迷子になるクソ駅が爆誕します。
(気に入ったところ)
(気に入らないところ)
ゲームはまだ始めたばかりなので、主に操作性の面での感想です。
プレイ時間が10時間未満で、序盤しか体験してませんが、STATIONflowのシステム紹介もしておきます。システムはそんなに難しくないです。
マップには地下鉄の改札口と、電車の到着ホームが固定で置かれます。お客さんの行動は基本的に3つです。
他の交通系ゲームと大きく違う点は、客は「最適経路を知らない」ことです。客は「構内案内」を見て行動します。目的地への経路が存在しても、構内案内を間違えると客は目的地に行けなくて迷います。視界の広さがお客さんによって違い、遠いところに構内案内を置くと、客によっては構内案内を見失って迷います。
遠回りで変なシステムに見えますが、よく考えると、すべての客が駅構内の経路を知り尽くしていて、常に最短経路で移動するって変じゃないですか?そんなエスパーみたいな客いませんよね。
正直いって構内案内のメンテナンスはかなり面倒ですが、ゲームとして理不尽にならないレベル(※)で現実に寄せた、素敵なアイデアだと思います。
(※)実世界だと、構内案内をあえて無視して迷う、目の前の構内案内に気づかない、話の通じないクレーマー、など不条理ばかりですが、そんなの再現してもクソゲーにしかなりません。現実に寄せれば良いってものじゃない。
乗降客数が増えて、お客さんの不満が少ないとランクアップし、ホームと改札口が勝手に増えます。大抵は予想していない変なところにホームが勝手に増えるので、駅のレイアウトと合わなくなります。つじつま合わせが必要です。ここは腕の見せどころです。
ランクアップに伴い、トイレやレストランなどの構内施設の種類が増えます。エスカレーターしか乗らない高齢客、エレベーターしか乗らない車いす客、などお客さんのバリエーションも増えます。序盤は簡単で、ランクアップによって難易度が段々上がっていく設計に見えます。
ホームと改札の位置はマップにより固定のようです。最後まで進めて最初からやり直せば、綺麗な駅が作れるかもしれません(まだそこまで見てないので断言はできませんけど)。
メモ: 技術系の話はFacebookから転記しておくことにした。加筆&修正した。
Twitterで「収入ほぼゼロ」「来館者9割減」 危機に陥っている「航空科学博物館」がクラウドファンディングで支援募集 - ねとらぼ、の記事を目にしたので、微力ながら5,000円を支援しました。後で入場券が2枚送られてくるそうな。
100万円のコースはさすがに高すぎなのか支援者がまだ現れていませんが、30万円のコースは3人ほど支援者がいました。愛されていますね。
公営の博物館(公益財団法人 航空科学博物館)は、国や自治体が支援するのが筋だろう、という意見も目にしましたし、言い分はわかるんですが、うだうだ言っている間に潰れてしまっては元も子もないです。クラウドファンディングで助けを求めるのは良い手段だと思います。
目次: GCC
前回(2020年3月27日の日記参照)の調査により、targetm.vector_mode_supported_p() がfalseを返すことが原因だとわかりました。この関数ポインタが指す先は、アーキテクチャターゲット(ARMとかi386とかriscvとか)ごとに違います。RISC-Vの場合は下記のようにすれば良いです。
// 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で実装が切り替わるのか、についても調べたいところですね。それはさておいて、上記の実装を追加すると無事ベクトル型が使えるようになります。
(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の問題はまた今度です。
目次: GCC
以前(2020年3月27日の日記、2020年3月28日の日記、2020年3月29日の日記参照)ベクトルレジスタを扱えるようにした際に、下記の問題が残っていました。
前回(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]));
}
ベクトル型を使ってインラインアセンブラを書くとエラーが出ます。
$ 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になっていました。
(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辺りのコードを探ってみます。
// 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になってしまうようです。
ノートPC(ThinkPad E480)の冷却ファンが回り続けていてうるさいです。特に何かしている訳でもないのに、良い勢いでファンが回ってます。
ここ最近、ノートPCを酷使(ゲーム、在宅勤務など)したため、ファンに埃が詰まって、冷却能力が下がったか?と予想して、頑張ってThinkPadの裏蓋を開けました。しかし思ったほど汚れていません。
どうしてファンが回りっぱなしなんでしょう?純粋に冷却能力が足りないだけなんだろうか??
ThinkPad E480の裏蓋はめちゃくちゃ開けにくいです。ネジ9か所と爪で止まっているので、ネジを緩めた後、マイナスドライバーでこじ開けるしかありません。
爪を外すとき、バキっ!ベキっ!というすごい嫌な音がします。案の定、ヒンジ付近(ノートPCとノートPCディスプレイが接続されている方)の爪が4か所ほど折れました。
せっかく裏蓋まで開けたにも関わらず、何も収穫がありませんでした。裏蓋を止める爪が4か所壊れただけです。相変わらずファンはうるせーし、嬉しくない結末です。目次: GCC
以前(2020年3月27日の日記、2020年3月28日の日記、2020年3月29日の日記参照)ベクトルレジスタを扱えるようにした際に、下記の問題が残っていました。
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つしか定義されていません。
FLOAT_MODE (TF, 16, ieee_quad_format);
このファイルにベクトル型を表すマシンモードを追加します。
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では珍しい部類の、わかりやすさ&親切さです。自動生成コードには気を使っているんでしょうか?他のところもこれくらい親切だと嬉しいんですけどねえ。
今年のヤマザキ春のパン祭りは、2枚もゲットできました。
去年は品川勤務かつコンビニ昼飯がメインだったので、ヤマザキパンを買う機会がほぼなく、お皿をもらえるほど点がたまりませんでした。今年はCOVID-19による在宅勤務で、近所のスーパーでパンを買う機会が大幅に増えたため、2枚も手に入ったわけです。在宅勤務の意外な効果です。
コンビニに行かない人にとっては意外かもしれませんが、コンビニはヤマザキパンをほとんど置いていません。自社のプライベートブランドのパンばかりです。10年位前は他社のパンも置いていたんですが、今やコンビニは9割方がプライベートブランドのパンです。
コンビニのプライベートブランドのパンは、大手メーカー(ヤマザキパン、敷島製パン、フジパンなど)が作っていますが、コストの都合か何だか知りませんが、味に劣る気がします。好みの問題なのかな……?
パン祭りのお皿、裏側に何か書いてるなーと思って写真を撮ってみました。
ちょっと見づらいので文字に起こすと、
ARTICLE YAMAZAKI
MADE IN FRANCE
ZENMEN
BUTSURIKYOUKA
GARASU
「全面物理強化ガラス」ってローマ字で書いてありますね。フランス製なのに何でローマ字?フランス語で書かれても読めないから?
見ていて素朴な疑問が沸きました。あえて「全面」と強調する理由はなんでしょうね?皿のように厚みのない製品で「片面」物理強化ガラスにすることは可能?可能だったとしてもやる意味がある?
Pythonの文字列置換は "string".replace() ですが、正規表現ライブラリreだと、なぜかre.sub() です。同じ機能なのに、APIの名前も、引数の指定順序も違います。どうしてこうなった。
改定の度に魔界化するC/C++ に比べると、Pythonは明瞭に思えます。とはいえPythonも何だかんだ長い歴史ですし、祓いきれない闇があるんでしょうねえ。
メモ: 技術系の話はFacebookから転記しておくことにした。
目次: Kindle
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 Fire HDのアプリはたまにアップデートされて動きが変わります。今年の頭くらいだったか?覚えてないですけど、また動作が変わりました。
新たなバグは再現率100%です。再現方法も簡単です。
この順に操作したとき、本来は本の一覧が出なければなりませんが、グループ化された本が再表示されてしまいます。明らかにバグってます。
このバグは、ユーザー側の操作で回避可能です。
ユーザーの操作に影響が出るバグですし、テスターに触らせたら数分で見つけそうなのにね?KindleってUIのテストしてないのかなあ??
目次: RISC-V
マクロの名前に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では修正していません。
リポジトリを見ていてちょっと気になったのはSiFiveの人以外、変更がほとんどないことです。著名プロジェクトでは珍しいです。もしかするとGitHubでPull Requestを受け付けてない(※1)可能性があります。
変更を提案するのはここじゃないとか、そもそも変更は受け付けてませんとか、何でも良いので反応があると嬉しいですね、週明けまで待ちましょうかね……。
(※1)本家および開発の場がGitHub以外に存在していて、GitHubをミラーにしているプロジェクトの場合、GitHub上で何か言っても無視されることがあるようです。
Splatoon 2のガチマッチ(Cランク)の難易度が下がった気がします。
1〜2か月前は、20kill対0killで負けたり、1分でノックアウトされたり、超フルボッコで負けまくるのが当たり前で、すっかりやる気がなくなっていましたが、今日久しぶりにやったら一度も負けず、あっさりC+ ランクになりました。
アクションゲームの腕は1か月やそこらで急に上達しないので、私が上達したというよりも、絶対勝てないおかしいレベルのプレーヤーとマッチする割合が減った、そんな感じです。
Stay Homeとかゴールデンウイークとかで、Splatoon 2のプレーヤー人口が盛り返して、初心者クラス(C-〜C+ ランクあたり)に合う人が増えたんじゃないかと推測しています。
最初からこのくらいの難易度だったら、ガチマッチ嫌いにならずに済んだんですけど、すっかりガチマッチ嫌いになってしまった(レギュラーマッチは好き)ので、もう遅いんだよな〜……。
目次: C言語とlibc
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言語の仕様(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) も展開されています。
今、使ってるノートPC(ThinkPad E480カスタムオーダー)には、Intelのグラフィクスと、Radeon RX 550が搭載されています。
Radeon RX 550はモバイル用としては結構強力で、主にゲームの時に助かっているのですが、Radeonを使ってゲームをしばらくやっていると、本体が触れないくらい熱くなり、しまいに熱暴走でクラッシュしてしまいます。
せっかくカスタムオーダーで追加したのに微妙な奴だな〜。
このGPUはもうひとつ変なところがあって、デバイスマネージャでRadeon RX 550を「無効」にしてPCを再起動すると、冷却ファンが唸り続けます。
CPU利用率は極めて低いままにも関わらず、冷却ファンが全力稼働しているので、ファンの制御がおかしくなっているように思います。GPUの負荷を勘違いしているのだろうか?
プロセスを片っ端から終了させても収まらなかったところをみると、恐らくドライバが変なところにハマっている?のでしょうか。
デバイスを有効にするか、有効にしたあとに再度無効にすれば、CPUファンは静かになりますが、なんだか挙動不審ですよね……。
目次: Kindle
Amazonでハンダごて一式を購入しました。
まず、ハンダが来ました。
次に、ハンダこて台、ハンダ吸い取り線が来ました。
肝心の、ハンダごてが来ません……(後日、ハンダごても届きました)。
Amazonは一括で注文できますが、発送元の違いで一括で発送されないことは良くあります。たまに不思議な順番で送られてきてちょっと面白いですよね。
メモ: 技術系の話はFacebookから転記しておくことにした。
目次: ゲーム
昨日(2020年4月29日の日記参照)に引き続きTransport Fever 2の基本である、生産量について紹介します。画像をできるだけ使うようにしました。ゲームの雰囲気を感じてもらえたら嬉しいです。
生産量の概念は、私が最初に躓いたというか、一番訳の分からなかったところです。うまく説明できると良いんですけど。
Transport Fever 2のフリープレイで出現する生産者、消費者は5段階に分類できます。カテゴリの正式名がないため、私が適当に名付けています。間違っていたらごめんなさい。
図の見方を一応説明しておくと、食料加工プラントの場合、農場から「穀物」を2つ運ぶと、1つの「食料」が生産される、という風に見ます。生産がダントツで面倒くさいのは「商品」ですね。
経路がシンプルな「穀物」→「食料」にも罠があります。施設の生産キャパシティは基本的に最大400ですが、なぜか農場だけ200 しかありません。レベル最大の食料加工プラントで食料を400生産するには、穀物が800必要になりますから、農場4つから穀物を運ばなければなりません。
ちなみに…、「製油所」が2つ出てくるのは誤植ではありません。「製品」工場が「商品」を作るのも、「工場」と「プラント」が混ざっているのも誤植ではありません。Transport Fever 2の日本語はポンコツなので、突っ込み出すとキリがないです。
中間と最終生産を担う工場にはレベルがあり、生産上限がレベルで決まります。レベルは各施設をクリックすると出てくる概要に表示されています。
ただまあ、工場のレベルを上げるぞ!!と思って上げることはあまりなくて「たくさん原料を運び込んで」「生産したものを全部消費」しているうちに、勝手に上がっていることが多いです。
生産と消費がTransport Fever 2で一番わかりにくい概念だと思います。生産施設は常に「必要とされた分」しか作りません。私は最初、この概念が全く分かりませんでした。
先日作った経路で説明しましょう。森から製材所への運送経路を1つだけ作りました。生産(森)と消費(製材所)の関係はこうなります。
基本的に消費側(製材所)の要求数 = 生産側(森)の生産数(2番目の「輸送」の数字)になります。正確に言えば、下記のロジックで決まります。
消費側は繋がっている生産側「全て」に要求するというのが、わかりにくいんですけど、結構大事です。
もし製材所レベル1に、森A、森Bを2つ繋いだとすると、森2つに対し「合計400の木材が要求」され、森の生産力が余ります。すると、森Aは100、森Bは300のように分散して生産し始めます。
このダイアログの情報は難しい部類ですが、日本語が完全に本当にポンコツです、全く説明になっていません。特に「輸送」を違う意味で2つ並べた人は何を考えているんでしょう?どう見てもおかしいでしょう??
これはわかりにくいというより、紛らわしい数字が表示されることが多いので、説明しておきます。トラック駅をクリックして、路線をクリックすると、路線の情報が表示されます。
ほとんど見ればわかる系の情報なのですが「割合」は注意が必要です。数字は路線の年間輸送力を意味しています。これもポンコツ翻訳のひとつですね……。
この路線は400の木材を森から製材所に運ぶために作ったことを思い出せば、400前後の輸送力にしておくと無駄がないことが分かると思います。
路線のコースを変えたり、トラックの種類や数を変更すると、すぐに年間輸送力の数字が変化します。例えばこの路線だと、トラックを1台から12台にすると、400くらいの値が表示されました。
しかしながら、この数字は実際の輸送力とかけ離れている場合があります。ある程度待つと、実情に近い値に補正されます。この補正がかなり劇的で、路線の変更直後は400だったのに、しばらく経つと480など大幅に増えたり、逆に激減したりすることが多々あり、気づかないうちに輸送力が不足したり、無駄になったりします。
ですから路線の輸送力を変更したときは、ちょっと時間をおいてから再チェックすることをお勧めします。
私の個人的な感覚では、トラックは最初の数字が低めに出る傾向(=後で増える)、鉄道と船舶は最初の数字が高めに出る(=後で減る)傾向があるように感じます。
< | 2020 | > | ||||
<< | < | 05 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | 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 |
31 | - | - | - | - | - | - |
合計:
本日: