目次: ゲーム
STATIONflowの実績をコンプリートしました。「ラッキーセブン」と「東京」は自動化マクロを組まないと取れませんでした。
本来、このゲームにハマって色々なマップをずっと遊んでいたらいつのまにか取れていた、というタイプの実績ですが、申し訳ないことに、私はそこまでの情熱がなかったです。
ゲームやったらわかりますけど、この2つの実績だけ条件設定が異常すぎます。
実績の条件(総利用者数700万人、1,390万人)がいかに異常かがわかると思います。
自動化の方法は簡単で、PowerShellでEnterキーを3秒に1回送るマクロを組んで、会社行っている間や夜間に放置するだけです。2週間くらいで取れました。
STATIONflowは放置しても悪いイベントが起きない(=駅が壊れない)優しい仕様になっているため、自動化+放置が可能でしたが、他のシミュレーションゲームだと偶発的に悪いイベントが起きるため、この方法は使えません。
まあ、明らかに製作者の想定した取り方ではないし、こんな方法で実績取っても嬉しくないし、無理に実績取るのは今後はやめておきます。
目次: ゲーム
MAD Tower Tycoonでゾーン方式のエレベータの作り方を悩んで色々やっていたら、いつのまにかレベル100、実績コンプリートしていました。ゲーム内時間は表示されないので詳しくはわかりませんが、おそらく700日くらい?
The Towerと比べて色々思うところはありますが、総合的にみれば面白いと思います。
序盤だけ金欠になりやすいですが、難易度はかなり低めですし、ビル建築シミュレーション初めての方にオススメしたいゲームです。
MAD Tower Tycoonは住人を追尾する機能があり、ビルの住人が困っていないか把握するのに便利です。基本的には目的地に行って、家に帰る(住人の場合)、もしくはビルから出る(ゲストの場合)だけですけど、大きなビルを作ると変な行動が目立ちます。
前回(2020年7月10日の日記参照)も書きましたが、MAD Tower Tycoonはゾーン方式のエレベーターを作る方法がわかりません。今はメンテナンス施設の射程(上下6Fに効果がある)の関係で、7階+8階(※)おきに乗り換え階を作っています。
どうもこの方式だと40階くらいで限界っぽいです。乗り換えに時間が掛かりすぎて、目的地に行くだけで半日費やしている気の毒な住民がいます。彼らはなぜか不満は言いませんが、見ていると不憫です……。
救いとしてはMAD Tower Tycoonはビルの横幅がめちゃくちゃ広く取れるので、45階もあれば、レベル100、五つ星ビルが余裕で作れることです。だけど、やっぱりThe Towerにあやかるなら、100階建て目指したいですよね?
(※)効率重視ならば7F, 14F乗り換えが最適ですが、スカイロビーを作成できるのは15Fからなので、あえて14Fを空きフロアにして、15F乗り換えにしています。
MAD Tower Tycoonは、The Towerとかなり似ているがゆえに、つい比較してしまいます。
良いところ
悪いところ
ゾーン方式のエレベーターが作れたら、高層ビルに効率的に人を運べるようになって、もっと面白くなるはずなのに。もったいないよ〜。
目次: ゲームに統合。
目次: OpenCL
OpenCLは複数のベンダーのデバイスを同時に扱うことができます。ICD(Installable Client Driver)というそうです。ICDはGPUなどのデバイスを制御し、アプリケーションとICDの間にICDローダーが存在します。
Debian TestingではICDローダーとしてocl-icd-2.2.12が使われています。
$ apt-cache search ocl-icd-libopencl1 ocl-icd-libopencl1 - Generic OpenCL ICD Loader
ローダーのソースコードはGitHub(リンク)にあります。
先程、図示したもの以外にもICDはいくつか実装があります。現時点のDebian Testingでは下記が提供されていました。
IntelはICDのソースコードを完全にオープンにしています。NVIDIAは公開していません。AMDもないのかな?
動作を追ってみたいと思います。アプリケーションにはclinfoを使います。ocl-icdは環境変数OCL_ICD_DEBUG=15に設定すると、動作時に詳細なログを出力します。デバッガで追うのと併用するとわかりやすいです。
ローダーが走査するディレクトリは /etc/OpenCL/vendorsがハードコードされていますが、環境変数OPENCL_VENDOR_PATHで変更できます。
ICDのロードの中心となる処理は _find_and_check_platforms() です。
プラットフォームは説明が難しいですが、OpenCL APIの実体+任意のドライバ固有のデータとでも言いましょうか。変数の型はcl_platform_id * 型です。cl_platform_idは少なくとも先頭のメンバはstruct _cl_icd_dispatch *dispatchでなければなりません。dispatchの後ろには他の情報が入っていても問題ないようです。
// ocl-icd/ocl_icd_loader.c
static inline void _find_and_check_platforms(cl_uint num_icds) {
cl_uint i;
...
cl_platform_id *platforms = (cl_platform_id *) malloc( sizeof(cl_platform_id) * num_platforms);
error = (*plt_fn_ptr)(num_platforms, platforms, NULL);
...
for(j=0; j<num_platforms; j++) {
debug(D_LOG, "Checking platform %i", j);
struct platform_icd *p=&_picds[_num_picds];
char *param_value=NULL;
p->extension_suffix=NULL;
p->vicd=&_icds[i];
p->pid=platforms[j]; //★★pid = platform IDのことらしい
/* If clGetPlatformInfo is not exported and we are here, it
* means that OCL_ICD_ASSUME_ICD_EXTENSION. Si we try to take it
* from the dispatch * table. If that fails too, we have to
* bail.
*/
if (plt_info_ptr == NULL) {
plt_info_ptr = p->pid->dispatch->clGetPlatformInfo; //★★dispatchメンバが存在することを前提としている
// ocl-icd/khronos-headers/CL/cl.h
typedef struct _cl_platform_id * cl_platform_id;
// ocl-icd/(build-dir)/ocl_icd_loader_gen.h
struct _cl_platform_id { struct _cl_icd_dispatch *dispatch; }; //★★dispatch以外は特に規定がなさそう
// ocl-icd/(build-dir)/ocl_icd.h
struct _cl_icd_dispatch {
#ifdef CL_VERSION_1_0
CL_API_ENTRY cl_int (CL_API_CALL*clGetPlatformIDs)(
cl_uint /* num_entries */,
cl_platform_id * /* platforms */,
cl_uint * /* num_platforms */
) CL_API_SUFFIX__VERSION_1_0;
#else
CL_API_ENTRY cl_int (CL_API_CALL* clUnknown0)(void);
#endif
#ifdef CL_VERSION_1_0
CL_API_ENTRY cl_int (CL_API_CALL*
clGetPlatformInfo)(
cl_platform_id /* platform */,
cl_platform_info /* param_name */,
size_t /* param_value_size */,
void * /* param_value */,
size_t * /* param_value_size_ret */
) CL_API_SUFFIX__VERSION_1_0;
#else
CL_API_ENTRY cl_int (CL_API_CALL* clUnknown1)(void);
#endif
...
//★★こんな調子で関数ポインタの定義が延々と続く
先頭のdispatchはたくさんの関数ポインタが並んだ巨大な構造体です。アプリケーションから見るとOpenCLのAPIはocl-icdが提供しているように見えますが、ocl-icdのAPI実装はdispatchの関数ポインタを呼ぶラッパー関数であり、関数ポインタと、OpenCL API実装の本体を提供するのは各ICDの役割です。
目次: GCC
GCCには自動ベクトル化(tree-vectorize)機能があります。ループ処理を自動的にSIMD命令に置き換えるために使われているようです。現状のGCCが可変長のベクトル長に対応しているかどうかはわかりません。未対応ならば可変長のベクトル長に対応する実装が必要になりますが、非常に難しそうです。
可変長のベクトルの扱いはひとまず横に置くとして、RISC-Vのベクトルを「とても長い固定長のSIMD」とみなして自動ベクトル化を動かします。
// gcc/config/riscv/riscv.c
/* Implement TARGET_VECTORIZE_AUTOVECTORIZE_VECTOR_MODES. */
static unsigned int
riscv_autovectorize_vector_modes (vector_modes *modes, bool)
{
if (TARGET_VECTOR)
{
modes->safe_push (V64SImode);
modes->safe_push (V32SImode);
}
return 0;
}
...
#undef TARGET_VECTORIZE_AUTOVECTORIZE_VECTOR_MODES
#define TARGET_VECTORIZE_AUTOVECTORIZE_VECTOR_MODES riscv_autovectorize_vector_modes
自動ベクトル化を有効にする方法は簡単で、これだけです。
void *cpy(void *dst, const void *src, int n)
{
int *d = dst;
const int *s = src;
int i;
for (i = 0; i < n / sizeof(*d); i++) {
d[i] = s[i];
}
return dst;
}
この関数がベクトル化あり、なしでどのように変わるか見ます。
//★★自動ベクトル化、あり
d[i] = s[i];
100b8: 1202e007 vlw.v v0,(t0)
100bc: 10038393 addi t2,t2,256
100c0: f0038793 addi a5,t2,-256
100c4: 10028293 addi t0,t0,256
100c8: 0207e027 vsw.v v0,(a5)
for (i = 0; i < n / sizeof(*d); i++) {
100cc: fee296e3 bne t0,a4,100b8 <cpy+0x44>
100d0: 00661293 slli t0,a2,0x6
100d4: 02568963 beq a3,t0,10106 <cpy+0x92>
100d8: 959a add a1,a1,t1
100da: 932a add t1,t1,a0
d[i] = s[i];
100dc: 0005a383 lw t2,0(a1)
//★★自動ベクトル化、なし
d[i] = s[i];
10080: 0005a303 lw t1,0(a1)
for (i = 0; i < n / sizeof(*d); i++) {
10084: 0591 addi a1,a1,4
10086: 0291 addi t0,t0,4
d[i] = s[i];
10088: fe62ae23 sw t1,-4(t0)
for (i = 0; i < n / sizeof(*d); i++) {
1008c: fe759ae3 bne a1,t2,10080 <cpy+0xc>
}
return dst;
}
10090: 8082 ret
ソースコードではベクトル型を使っていませんが、自動ベクトル化により256バイト(=64要素)ずつ処理され、vlw.v, vsw.v命令が使われるようになったことがわかります。
GitHubが北極にコードを保存する取り組み(私のソースコードが北極送りに? "GitHub" アカウントに謎のラベルが付与されたとの報告が多数 - やじうまの杜 - 窓の杜)をしているそうです。面白いこと考えますよね。
GitHub Arctic Code Vault Contributor
気づいたら自分のGitHubアカウントにもArctic Code Vault Contributorと出ていました。有名なOSSには関わってないし縁がないと思っていましたが、どうやらLinuxにコントリビュートしていたので出てきたっぽいです。いわれてみるとLinuxもGitHubにミラーされてました。
現代人をもってしても1000年前の文字の解読(例: ヒエログリフ、前3200年〜4世紀、19世紀頃に解読された)はとても苦労したこと、解読されていない古代文字がまだまだあることを考えれば、現代文字や文明を遺しても未来人が理解不能で終わってしまう可能性もあるわけです。
少なくとも未来文明は現代文明より遥か上の水準に発展していないと、仮に現代文字や文明を発見できても、真に理解するのは難しいでしょう。
こんなことを考えると、実はCode Vaultの取り組みは「地球の未来は今より進んでいるはず」という信頼や願望が前提になっていることが見て取れます。人類か、それ以外の生命体か、誰の手に渡るかわかりませんが、SF的なロマンがありますよね。
目次: GCC
ベクトルのロード、ストアだけでは自動ベクトル化できるコードが少なすぎるので、他の演算も定義したいと思います。
;; gcc/config/riscv/riscv.md
(define_attr "vecmode" "unknown,V32SI,V64SI"
(const_string "unknown"))
...
;; Iterator for hardware supported vector modes.
(define_mode_iterator ANYV [(V32SI "TARGET_VECTOR")
(V64SI "TARGET_VECTOR")])
...
;;★★加算
(define_insn "add<mode>3"
[(set (match_operand:ANYV 0 "register_operand" "=v")
(plus:ANYV (match_operand:ANYV 1 "register_operand" " v")
(match_operand:ANYV 2 "arith_operand" " v")))]
"TARGET_VECTOR"
"vadd.vvt%0,%1,%2"
[(set_attr "type" "arith")
(set_attr "vecmode" "<MODE>")])
;;★★減算
(define_insn "sub<mode>3"
[(set (match_operand:ANYV 0 "register_operand" "=v")
(minus:ANYV (match_operand:ANYV 1 "register_operand" " v")
(match_operand:ANYV 2 "arith_operand" " v")))]
"TARGET_VECTOR"
"vsub.vvt%0,%1,%2"
[(set_attr "type" "arith")
(set_attr "vecmode" "<MODE>")])
;;★★乗算
(define_insn "mul<mode>3"
[(set (match_operand:ANYV 0 "register_operand" "=v")
(mult:ANYV (match_operand:ANYV 1 "register_operand" " v")
(match_operand:ANYV 2 "arith_operand" " v")))]
"TARGET_VECTOR"
"vmul.vvt%0,%1,%2"
[(set_attr "type" "arith")
(set_attr "vecmode" "<MODE>")])
;;★★除算
;; This code iterator allows unsigned and signed division to be generated
;; from the same template.
(define_code_iterator any_div [div udiv mod umod])
(define_insn "<optab><mode>3"
[(set (match_operand:ANYV 0 "register_operand" "=v")
(any_div:ANYV (match_operand:ANYV 1 "register_operand" " v")
(match_operand:ANYV 2 "arith_operand" " v")))]
"TARGET_VECTOR"
"v<insn>.vvt%0,%1,%2"
[(set_attr "type" "arith")
(set_attr "vecmode" "<MODE>")])
;;★★論理演算
;; This code iterator allows the three bitwise instructions to be generated
;; from the same template.
(define_code_iterator any_bitwise [and ior xor])
...
(define_insn "<optab><mode>3"
[(set (match_operand:ANYV 0 "register_operand" "=v")
(any_bitwise:ANYV (match_operand:ANYV 1 "register_operand" "%v")
(match_operand:ANYV 2 "arith_operand" " v")))]
"TAREGET_VECTOR"
"v<insn>.vvt%0,%1,%2"
[(set_attr "type" "logical")
(set_attr "vecmode" "<MODE>")])
四則演算、論理演算を使う下記のプログラムを書きます。自動ベクトル化で四則演算のループをベクトル化しても良いですが、ベクトル拡張記法(Vector Extensions (Using the GNU Compiler Collection (GCC)))を使ったほうが狙った演算が出しやすく、テストするときに楽です。
typedef int __v64si __attribute__((__vector_size__(256)));
void test()
{
__v64si v10, v11, v12, v13;0;
__asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v10) : "A"(b[10]));
__asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v11) : "A"(b[20]));
__asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v12) : "A"(b[30]));
__asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v13) : "A"(b[40]));
v10 = v11 + v12;
v11 &= v12 - v13;
v12 |= v13 * v10;
v13 ^= v10 / v11;
__asm__ volatile ("vsw.v %1, %0\n" : "=A"(b[40]) : "v"(v10));
__asm__ volatile ("vsw.v %1, %0\n" : "=A"(b[50]) : "v"(v11));
__asm__ volatile ("vsw.v %1, %0\n" : "=A"(b[60]) : "v"(v12));
__asm__ volatile ("vsw.v %1, %0\n" : "=A"(b[70]) : "v"(v13));
}
ビルド方法は何でも良いですが、最適化レベルをOgにするとアセンブラが見やすいと思います。
$ riscv32-unknown-elf-gcc b.c -nostdlib -g -Og -march=rv32gcv -mabi=ilp32f $ riscv32-unknown-elf-objdump -dS a.out ... __asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v13) : "A"(b[40])); 10092: 0a028793 addi a5,t0,160 10096: 1207e207 vlw.v v4,(a5) v10 = v11 + v12; 1009a: 022081d7 vadd.vv v3,v2,v1 v11 &= v12 - v13; 1009e: 0a120057 vsub.vv v0,v1,v4 100a2: 26010057 vand.vv v0,v0,v2 v12 |= v13 * v10; 100a6: 9641a157 vmul.vv v2,v4,v3 100aa: 2a208157 vor.vv v2,v2,v1 v13 ^= v10 / v11; 100ae: 863020d7 vdiv.vv v1,v3,v0 100b2: 2e1200d7 vxor.vv v1,v1,v4 __asm__ volatile ("vsw.v %1, %0\n" : "=A"(b[40]) : "v"(v10)); 100b6: 0207e1a7 vsw.v v3,(a5) ...
うまくいっているようです。良かった良かった。
目次: GCC
前回(2020年7月19日の日記参照)は四則演算と論理演算(and, or, xor)を定義しました。今回はNot演算を定義します。他の論理演算と異なり、Not演算は2オペランドしか取りませんから、define_insnを別に書く必要があります。
;; gcc/config/riscv/riscv.md
(define_insn "one_cmpl<mode>2"
[(set (match_operand:ANYV 0 "register_operand" "=v")
(not:ANYV (match_operand:ANYV 1 "register_operand" "%v")))]
"TARGET_VECTOR"
"vnot.vt%0,%1"
[(set_attr "type" "logical")
(set_attr "vecmode" "<MODE>")])
前回同様に、ベクトル拡張記法(Vector Extensions (Using the GNU Compiler Collection (GCC)))を使ってビット毎Notを使うプログラムを書きます。
typedef int __v64si __attribute__((__vector_size__(256)));
void test()
{
__v64si v10, v11;
static int b[1024 * 1024] = {0};
__asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v10) : "A"(b[10]));
__asm__ volatile ("vlw.v %0, %1\n" : "=&v"(v11) : "A"(b[20]));
v10 = ~v11;
__asm__ volatile ("vsw.v %1, %0\n" : "=A"(b[40]) : "v"(v10));
}
コンパイルするとエラーが発生します。何かお気に召さないようです。
$ riscv32-unknown-elf-gcc b.c -nostdlib -Og -march=rv32gcv -mabi=ilp32f during RTL pass: reload dump file: b.c.282r.reload b.c: In function 'test': b.c:14:1: internal compiler error: in setup_operand_alternative, at lra.c:814 14 | } | ^ 0xea5c36 setup_operand_alternative gcc/gcc/lra.c:814 0xea70c2 lra_set_insn_recog_data(rtx_insn*) gcc/gcc/lra.c:1073 0xea30f9 lra_get_insn_recog_data gcc/gcc/lra-int.h:488 0xeab0b9 remove_scratches_1 gcc/gcc/lra.c:2058 0xeab4c4 remove_scratches gcc/gcc/lra.c:2094 0xeac629 lra(_IO_FILE*) gcc/gcc/lra.c:2396 0xe1a41f do_reload gcc/gcc/ira.c:5523 0xe1ae5c execute gcc/gcc/ira.c:5709
コードを見ると最後のオペランドに % を付けるべきではないそうです。確かにdefine_insnを見ると % が要らないのに付いています。
// gcc/lra.c
/* Setup info about operands in alternatives of LRA DATA of insn. */
static void
setup_operand_alternative (lra_insn_recog_data_t data,
const operand_alternative *op_alt)
{
int i, j, nop, nalt;
int icode = data->icode;
struct lra_static_insn_data *static_data = data->insn_static_data;
static_data->commutative = -1;
nop = static_data->n_operands;
nalt = static_data->n_alternatives;
static_data->operand_alternative = op_alt;
for (i = 0; i < nop; i++)
{
static_data->operand[i].early_clobber_alts = 0;
static_data->operand[i].is_address = false;
if (static_data->operand[i].constraint[0] == '%') //★★% が付いていればcommutative
{
/* We currently only support one commutative pair of operands. */
if (static_data->commutative < 0)
static_data->commutative = i;
else
lra_assert (icode < 0); /* Asm */
/* The last operand should not be marked commutative. */
lra_assert (i != nop - 1); //★★このアサートに引っかかる
}
}
...
素直に応じるとエラーは消えます。
(define_insn "one_cmpl<mode>2"
[(set (match_operand:ANYV 0 "register_operand" "=v")
(not:ANYV (match_operand:ANYV 1 "register_operand" " v")))] ★★% を消す
"TARGET_VECTOR"
"vnot.vt%0,%1"
[(set_attr "type" "logical")
(set_attr "vecmode" "<MODE>")])
四則演算、論理演算(3オペランド系)と、Not演算(2オペランド系)が揃いました。残りの頻出する演算はビットシフト系かな?
最後のオペランドをcommutativeにしてはいけない理由は、GCCのConstraintsの説明を見るとわかります。
`%' Declares the instruction to be commutative for this operand and the following operand. This means that the compiler may interchange the two operands if that is the cheapest way to make all operands fit the constraints. GCC can only handle one commutative pair in an asm; if you use more, the compiler may fail. Note that you need not use the modifier if the two alternatives are strictly identical; this would only waste time in the reload pass. The modifier is not operational after register allocation, so the result of define_peephole2 and define_splits performed after reload cannot rely on `%' to make the intended insn match.
難しいことを言っていますが、commutativeは % を付けたオペランドと「次」のオペランドが可換だと宣言することだそうです。最後のオペランドには「次」のオペランドがありませんから、% を付けてはいけません。なるほど。
RISC-Vの場合は論理演算のdefine_insnの2番目のオペランドに % が使われています。
;; gcc/config/riscv/riscv.md
;; This code iterator allows the three bitwise instructions to be generated
;; from the same template.
(define_code_iterator any_bitwise [and ior xor])
...
(define_insn "<optab><mode>3"
[(set (match_operand:X 0 "register_operand" "=r,r")
(any_bitwise:X (match_operand:X 1 "register_operand" "%r,r")
(match_operand:X 2 "arith_operand" " r,I")))]
""
"<insn>%i2t%0,%1,%2"
[(set_attr "type" "logical")
(set_attr "mode" "<MODE>")])
例えばand r1, r2, r3とand r1, r3, r2は結果が同じですから、2番目と3番目のオペランドは入れ替え可能です。3つの論理演算(any_bitwiseはand, or, xorのこと)はいずれも同様に入れ替え可能ですので、このような定義になっています。
< | 2020 | > | ||||
<< | < | 07 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | 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 | - |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.15.
using GD bundled (2.1.0 compatible)(png support.)