前回(2020年 5月 31日の日記参照)見たとおり、メモリというかオフセット付きアドレスを全部禁止するのは明らかにやりすぎで、他の命令に悪影響を及ぼしていました。スカラ命令はオフセット付きアドレスを許可し、ベクトル命令だけオフセット付きアドレスを禁止したいところです。
オペランドを許可する、しないを判断するコードを改造すればできるでしょうか?コードを見てみます。
// gcc/common.md
(define_memory_constraint "TARGET_MEM_CONSTRAINT"
"Matches any valid memory."
(and (match_code "mem")
(match_test "memory_address_addr_space_p (GET_MODE (op), XEXP (op, 0),
MEM_ADDR_SPACE (op))")))
// gcc/recog.c
//★★各引数の値
//mode = E_V64SImode
//addr = (plus:SI (reg/f:SI 108)
// (const_int 256 [0x100]))
//as = 0
int
memory_address_addr_space_p (machine_mode mode ATTRIBUTE_UNUSED,
rtx addr, addr_space_t as)
{
#ifdef GO_IF_LEGITIMATE_ADDRESS
...
#else
return targetm.addr_space.legitimate_address_p (mode, addr, 0, as);
#endif
}
// gcc/targhook.c
//★★各引数の値
//mode = E_V64SImode
//mem = (plus:SI (reg/f:SI 108)
// (const_int 256 [0x100]))
//strict = 0
//as = 0
bool
default_addr_space_legitimate_address_p (machine_mode mode, rtx mem,
bool strict,
addr_space_t as ATTRIBUTE_UNUSED)
{
return targetm.legitimate_address_p (mode, mem, strict);
}
// gcc/config/riscv/riscv.c
#undef TARGET_LEGITIMATE_ADDRESS_P
#define TARGET_LEGITIMATE_ADDRESS_P riscv_legitimate_address_p
// gcc/config/riscv/riscv.c
//★★各引数の値
//mode = E_V64SImode
//mem = (plus:SI (reg/f:SI 108)
// (const_int 256 [0x100]))
//strict_p = 0
static bool
riscv_legitimate_address_p (machine_mode mode, rtx x, bool strict_p)
{
struct riscv_address_info addr;
return riscv_classify_address (&addr, x, mode, strict_p);
}
// gcc/config/riscv/riscv.c
//★★各引数の値
//x = (plus:SI (reg/f:SI 108)
// (const_int 256 [0x100]))
//mode = E_V64SImode
//strict_p = 0
static bool
riscv_classify_address (struct riscv_address_info *info, rtx x,
machine_mode mode, bool strict_p)
{
switch (GET_CODE (x))
{
...
case PLUS:
info->type = ADDRESS_REG;
info->reg = XEXP (x, 0);
info->offset = XEXP (x, 1);
//★★各変数の値
//info->reg = x->u.fld[0].rt_rtx = (reg/f:SI 108)
//info->offset = x->u.fld[1].rt_rtx = (const_int 256 [0x100])
return (riscv_valid_base_register_p (info->reg, mode, strict_p)
&& riscv_valid_offset_p (info->offset, mode));
...
}
}
関数 memory_address_addr_space_p() の addr を見ると、メモリアドレスを表す RTL しか渡されません。この情報だけではスカラ命令のオペランドか、ベクトル命令のオペランドか、判断するのは困難です。
ベクトル命令は machine mode が V64SI であることを利用するとうまくいくかもしれません。関数 riscv_classify_address() を変更し、mode == V64SI だったら PLUS など REG 以外を使った RTL に対し false を返せば良さそうです。
// gcc/config/riscv/riscv.c
//★★各引数の値
//x = (plus:SI (reg/f:SI 108)
// (const_int 256 [0x100]))
//mode = E_V64SImode
//strict_p = 0
static bool
riscv_classify_address (struct riscv_address_info *info, rtx x,
machine_mode mode, bool strict_p)
{
switch (GET_CODE (x))
{
...
case PLUS:
if (mode == E_V64SImode) //★★ machine mode が V64SI ならオフセット付きアドレスは許可しない
return false;
info->type = ADDRESS_REG;
info->reg = XEXP (x, 0);
info->offset = XEXP (x, 1);
return (riscv_valid_base_register_p (info->reg, mode, strict_p)
&& riscv_valid_offset_p (info->offset, mode));
...
}
}
$ diff -u b_before.s b_mod.s
--- b_before.s 2020-05-28 21:17:24.607184754 +0900
+++ b_mod.s 2020-05-30 22:37:02.370663628 +0900
@@ -35,7 +35,8 @@
# 0 "" 2
#NO_APP
- vsw.v v0,256(a5)
+ addi a4,a5,256
+ vsw.v v0,0(a4)
.loc 1 9 2
addi a4,s0,-336
#APP
結果だけ見ると良さそうですが、将来的にオフセットアドレスが使えるベクトル命令が出てきたときに、判別不能になり困ります。その場しのぎ感が否めません。もっと良い方法はあるでしょうか?
実はもっと簡単な方法で対処できます。変更すべき箇所については、この取り組みの発端「constraint "m" をチェックしていそうな箇所から調査を開始した」ことを思い出していただければ想像が付くと思います。constraint "m" を使っている箇所は define_insn です。
;; gcc/config/riscv/riscv.md
(define_attr "vecmode" "unknown,V64SI"
(const_string "unknown"))
(define_insn "*movv64si_internal"
[(set (match_operand:V64SI 0 "nonimmediate_operand" "=v,v,m")
(match_operand:V64SI 1 "move_operand" " v,m,v"))] //★★constraint "m" を使っている場所
"(register_operand (operands[0], V64SImode)
|| reg_or_0_operand (operands[1], V64SImode))" return riscv_output_move (operands[0], operands[1]);
[(set_attr "move_type" "move,load,store")
(set_attr "vecmode" "V64SI")])
この "m" を変更して、オフセット付きアドレスオペランドだけを拒否したいですが、そんな都合の良い constraint はあるでしょうか?実は RISC-V には既にあります。constraint "A" です。
;; gcc/config/riscv/constraints.md
(define_memory_constraint "A"
"An address that is held in a general-purpose register."
(and (match_code "mem")
(match_test "GET_CODE(XEXP(op,0)) == REG")))
コードを見ると、constraint "A" が見ている条件は、種別がメモリであり、オペランドが REG(オフセットありだと PLUS などになる)であることです。先程の改造と同じ発想ですね。m を A に変更します。
;; gcc/config/riscv/riscv.md
(define_insn "*movv64si_internal"
[(set (match_operand:V64SI 0 "nonimmediate_operand" "=v,v,A")
(match_operand:V64SI 1 "move_operand" " v,A,v"))] //★★constraint "A" に変更
...
$ diff -u b_before.s b_final.s
--- b_before.s 2020-05-28 21:17:24.607184754 +0900
+++ b_final.s 2020-05-28 21:20:20.219175573 +0900
@@ -35,7 +35,8 @@
# 0 "" 2
#NO_APP
- vsw.v v0,256(a5)
+ addi a4,a5,256
+ vsw.v v0,0(a4)
.loc 1 9 2
addi a4,s0,-336
#APP
出力結果のアセンブリも良い感じですし、ビルドの際にアセンブラも文句を言わなくなりました。
長きに渡りましたが、やっとベクトル型を使った GCC 拡張インラインアセンブラが書けるようになりました。良かった良かった。
一覧が欲しくなってきたので作りました。
GCC について。
GCC を含めたツールチェーンについて。
今後、日記が増えたら追加します。
以前(2020年 5月 24日の日記参照)、変数の代入操作を expand で展開する際、代入を分割するか分割しないか、を決める条件の 1つとして、optab_handler() という関数が出てきました。optab_handler() の動作を決める genopinit というツールの動作を調べます。
バイナリは build_gcc/build/genopinit に生成されます(以降 build_gcc は GCC のビルドディレクトリを指すものとします)。実行する際の引数は下記の通りです。
# build_gcc/Makefile
s-opinit: $(MD_DEPS) build/genopinit$(build_exeext) insn-conditions.md
$(RUN_GEN) build/genopinit$(build_exeext) $(md_file) \
insn-conditions.md -htmp-opinit.h -ctmp-opinit.c
# ビルドログ
build/genopinit ./gcc/gcc/common.md ./gcc/gcc/config/riscv/riscv.md \
insn-conditions.md -htmp-opinit.h -ctmp-opinit.c
上記のビルドログでは一時ファイルに出力していますが、最終的には build_gcc/insn-opinit.h, build_gcc/insn-opinit.c の 2つのファイルを生成します。
前回は optab_handler() にブレークポイントを設定するため、enum optab_tag の値を使いました。この値は genopinit が生成しています。
// gcc/optabs-query.h
/* Return the insn used to implement mode MODE of OP, or CODE_FOR_nothing
if the target does not have such an insn. */
inline enum insn_code
optab_handler (optab op, machine_mode mode)
{
unsigned scode = (op << 16) | mode; //★★以前は op = mov_optab, mode = E_V64SImode でブレークを掛けた
gcc_assert (op > LAST_CONV_OPTAB);
return raw_optab_handler (scode); //★★次回以降、調べます
}
// build_gcc/insn-opinit.h
enum optab_tag {
unknown_optab,
sext_optab,
...
mov_optab, //★★この値のこと
...
};
...
typedef enum optab_tag optab;
これらの値は gensupport.c の optab_def から生成されています。実際に optab_def の中身を定義するのは optabs.def ファイルです。この *.def ファイルは、C 言語で素直に書くと重複、冗長になる情報を簡潔に表すための、いわゆる DSL(Domain Specific Language)だと思われます。
DSL っぽいものを使うとき、下記のような邪悪な include の使い方をします。GCC 凶悪デザインパターンの 1つですね。良い子は真似してはいけません。
// gcc/gensupport.c
#define OPTAB_DC(o, p, c) { #o, p, NS, ZS, NS, o, c, c, 4 },
#define OPTAB_D(o, p) { #o, p, NS, ZS, NS, o, UNKNOWN, UNKNOWN, 4 },
...
/* An array of all optabs. Note that the same optab can appear more
than once, with a different pattern. */
optab_def optabs[] = {
{ "unknown_optab", NULL, NS, ZS, NS, unknown_optab, UNKNOWN, UNKNOWN, 0 },
#include "optabs.def" //★★上記のように optabs.def 内で使われるマクロを、都合の良い定義に変えてから include
};
// gcc/optabs.def
OPTAB_DC(mov_optab, "mov$a", SET) //★★マクロの定義は include される場所によって違うので、展開後の結果は場所による
OPTAB_DC(movstrict_optab, "movstrict$a", STRICT_LOW_PART)
OPTAB_D (movmisalign_optab, "movmisalign$a")
...
// gcc/genopinit.c
int
main (int argc, const char **argv)
{
...
/* Emit the optab enumeration for the header file. */
fprintf (h_file, "enum optab_tag {\n");
for (i = j = 0; i < n; ++i)
{
optabs[i].op = i;
fprintf (h_file, " %s,\n", optabs[i].name); //★★optabs の名前を出力
if (optabs[i].kind != j)
last_kind[j++] = i - 1;
}
fprintf (h_file, " FIRST_CONV_OPTAB = %s,\n", optabs[last_kind[0]+1].name);
...
せっかく DSL っぽいものがあるのに、genopinit のようにコード自動生成ツールも混ぜて使うのはどうしてなんでしょう?GCC は理解不能です。
以前(2020年 5月 24日の日記参照)、変数の代入操作を expand で展開する際、代入を分割するか分割しないか、を決める条件の 1つとして、optab_handler() という関数が出てきました。この関数の動作に関わる genopinit というツールの動作を調べます。
前回(2020年 6月 3日の日記参照)は分岐条件に関わる enum optab_tag の生成について調べました。今回はもう少し複雑な pats[].scode の生成について調べます。
突然 pats[].scode と言われても何だかわからないと思います。私もこんなひどい名前の変数、全く覚えられません。復習も兼ねて optab_handler() を見直します。
// gcc/optabs-query.h
/* Return the insn used to implement mode MODE of OP, or CODE_FOR_nothing
if the target does not have such an insn. */
inline enum insn_code
optab_handler (optab op, machine_mode mode)
{
unsigned scode = (op << 16) | mode; //★★scode の意味はここにある通り
gcc_assert (op > LAST_CONV_OPTAB);
return raw_optab_handler (scode); //★★これ
}
// build_gcc/insn-opinit.c
enum insn_code
raw_optab_handler (unsigned scode)
{
int i = lookup_handler (scode); //★★これ
return (i >= 0 && this_fn_optabs->pat_enable[i]
? pats[i].icode : CODE_FOR_nothing);
}
static int
lookup_handler (unsigned scode)
{
int l = 0, h = ARRAY_SIZE (pats), m;
while (h > l)
{
m = (h + l) / 2;
if (scode == pats[m].scode) //★★これ
return m;
else if (scode < pats[m].scode) //★★これ
h = m;
else
l = m + 1;
}
return -1;
}
//★★pats[] の定義
struct optab_pat {
unsigned scode;
enum insn_code icode;
};
static const struct optab_pat pats[NUM_OPTAB_PATTERNS] = {
{ 0x010405, CODE_FOR_extendqihi2 },
{ 0x010406, CODE_FOR_extendqisi2 },
{ 0x010407, CODE_FOR_extendqidi2 },
{ 0x010505, CODE_FOR_extendhihi2 },
...
{ 0x021c1b, CODE_FOR_truncdfsf2 }, //★★この変な数字(scode)0x021c1b は誰が作るのか?
...
コードから scode の意味、上位 16ビットが optab で、下位 16ビットが machine mode、は理解できると思います。pats には scode が無数に並んでいますが、この数字を誰が作るかというと genopinit です。
前に見た optabs の定義は色々ありますが、必ず 2つ以上の引数を取り、1番目が name(mov_optab, trunc_optab など)、2番目が pattern となっており、この 2つは全ての定義に存在します。
// gcc/optabs.def
OPTAB_CL(trunc_optab, "trunc$b$a2", TRUNCATE, "trunc", gen_trunc_conv_libfunc)
'-- name `-- pattern
このうち name は enum optab_tag の変数名として使っていました。その他にも code_to_optab_[] という配列の定義にも使いますが、今はどうでもいいです。前後に文字が付くくらいで、基本的に名前がそのまま使われます。
一方 2番目の pattern(例えば "mov$a", "trunc$b$a2" など)はちょっと変わっています。$a や $b という不思議な文字が入ります。
前回説明したとおり genopinit の入力は gcc/common.md, gcc/config/riscv/riscv.md, build_gcc/insn-conditions.md の 3つの *.md ファイルです(md は machine descriptor の略)。Lisp っぽい記法で、define_insn や define_expand を追加したファイルです。
基本的な genopinit の動作は、
パターンマッチのコードはこんな感じです。
// gcc/gensupport.h
/* Information about an .md define_* rtx. */
class md_rtx_info {
public:
/* The rtx itself. */
rtx def;
/* The location of the first line of the rtx. */
file_location loc;
/* The unique number attached to the rtx. Currently all define_insns,
define_expands, define_splits, define_peepholes and define_peephole2s
share the same insn_code index space. */
int index;
};
// gcc/genopinit.c
int
main (int argc, const char **argv)
{
...
if (!init_rtx_reader_args_cb (argc, argv, handle_arg)) //★★ *.md を解析
return (FATAL_EXIT_CODE);
...
/* Read the machine description. */
md_rtx_info info;
while (read_md_rtx (&info)) //★★ RTX を 1つずつ取り出す
switch (GET_CODE (info.def))
{
case DEFINE_INSN: //★★ define_insn, define_expand だけ注目
case DEFINE_EXPAND:
gen_insn (&info); //★★これ
break;
static void
gen_insn (md_rtx_info *info)
{
optab_pattern p;
if (find_optab (&p, XSTR (info->def, 0))) //★★これ
patterns.safe_push (p);
}
// gcc/gensupport.c
bool
find_optab (optab_pattern *p, const char *name)
{
...
/* See if NAME matches one of the patterns we have for the optabs
we know about. */
for (unsigned int pindex = 0; pindex < ARRAY_SIZE (optabs); pindex++) //★★全ての pattern を試す
{
p->m1 = p->m2 = 0;
if (match_pattern (p, name, optabs[pindex].pattern)) //★★これ
{
p->name = name;
p->op = optabs[pindex].op;
p->sort_num = (p->op << 16) | (p->m2 << 8) | p->m1; //★★scode を作る
return true;
}
// gcc/gensupport.h
/* Information about an instruction name that matches an optab pattern. */
struct optab_pattern
{
/* The name of the instruction. */
const char *name;
/* The matching optab. */
unsigned int op;
/* The optab modes. M2 is only significant for conversion optabs;
it is zero otherwise. */
unsigned int m1, m2;
/* An index that provides a lexicographical sort of (OP, M2, M1).
Used by genopinit.c. */
unsigned int sort_num;
};
申し訳ないですが *.md ファイルの解析関数 init_rtx_reader_args_cb(), read_md_rtx() 辺りは調べる予定がありません。どなたか調べてくれたら嬉しいです。
パターンマッチは name に対して行われ、全ての pattern とマッチするか試します。マッチしなければ match_pattern() は false を返しますから、次の pattern を試します。マッチしたら、結果は optab_pattern *p に格納され、match_patter() が作り出す p->m1, p->m2 という謎の数から scode が生成されます。
なぜ genopinit では sort_num という変数名にしたんでしょうね?scode にすればもう少しわかりやすいのに。
パターンマッチのルールと、m1, m2 が何者か?については、文章で説明できる気がしないので、例として name = "truncdfsf2" を見ながら説明したいと思います。マッチする pattern は "trunc$b$a2" です。他の pattern はマッチしません。
// gcc/config/riscv/riscv.md
//★★ name の例
(define_insn "truncdfsf2" ★★名前
[(set (match_operand:SF 0 "register_operand" "=f")
(float_truncate:SF
(match_operand:DF 1 "register_operand" " f")))]
"TARGET_DOUBLE_FLOAT"
"fcvt.s.dt%0,%1"
[(set_attr "type" "fcvt")
(set_attr "mode" "SF")])
// gcc/optab.def
//★★ マッチする pattern の定義部分(2番目の引数)
OPTAB_CL(trunc_optab, "trunc$b$a2", TRUNCATE, "trunc", gen_trunc_conv_libfunc)
// gcc/gensupport.c
//★★
//p は結果格納用の変数
//name = "truncdfsf2"
//pat = "trunc$b$a2"
static bool
match_pattern (optab_pattern *p, const char *name, const char *pat)
{
...
★★match_pattern() が true を返した後、find_optab() が return true で終了する直前でダンプ (gdb) p optabs[pindex] $13 = { name = 0x4574b5 "trunc_optab", pattern = 0x4574c1 "trunc$b$a2", base = 0x4574cc ""trunc"", suffix = 0x457478 "'\0'", libcall = 0x4574d4 "gen_trunc_conv_libfunc", op = 2, fcode = TRUNCATE, rcode = UNKNOWN, kind = 1 } (gdb) p/x *p $15 = { name = 0x4dd270, op = 0x2, m1 = 0x1b, m2 = 0x1c, sort_num = 0x21c1b } p は結果格納用の optab_pattern *p
この定義の pattern は 2番目の引数 "trunc$b$a2" です。全部繋がっていてわかりにくいですが、下記の 4つの要素から構成されます。
このうち $a や $b は machine mode の名前にマッチします。具体的には下記のようになります。
name と pat が name = "truncdfsf2" pat = "trunc$b$a2" の場合、 $b -> df $a -> sf にマッチします。$a, $b のマッチを調べるときは mode_name を先頭から検索(大文字小文字の違いは無視)します。 全モードの名前は mode_name[] という配列に入っています。 $b は mode_name[28] = "DF" と一致する。 $a は mode_name[27] = "SF" と一致する。 match_pattern() は一致したモードの番号を m1, m2 に格納します。 つまり optab_pattern の m1, m2 の意味はそれぞれ m1: $a がマッチしたモードのインデックス、今回だと 27 = 0x1b (E_SFmode) m2: $b がマッチしたモードのインデックス、今回だと 28 = 0x1c (E_DFmode) 結果 scode はこうなります。 p->sort_num = (p->op << 16) | (p->m2 << 8) | p->m1; = (2 << 16) | (28 << 8) | 27; = 0x21c1b // build_gcc/insn-opinit.c static const struct optab_pat pats[NUM_OPTAB_PATTERNS] = { ... { 0x021c1b, CODE_FOR_truncdfsf2 }, //★★この値が生成された
謎の pats[].scode はこんな仕組みで生成されていたのでした。ややこしいですね。
FSF (Free Software Foundation) の Copyright Assignment にサインしてみました。とりあえず GCC と binutils です。ツールチェーン仲間としては glibc もやっておけば良かったかも??まあいいか。
手続きは特に難しくなく、
これで完了です。
FSF からの返事はそれぞれ数日〜1週間程度、という感じでした。GNU のソフトウェアにパッチを送りたいときは、個人でこの契約を交わす(今回はこっち)か、会社の業務で作成している場合は、会社でこの契約を交わす(こっちのやり方は知らない)必要があります。
FSF がなぜこんな面倒なことをしているのかについては、なぜ FSF は貢献者に著作権の譲渡をお願いしているのか - GNU プロジェクトに書いてあります。過去に訴えられたり何か嫌なことがあったんでしょうね。
テンプレートは Copyright Papers (Information for Maintainers of GNU Software) を見ると GNU のメンテナーに要求してくれと書いてあります。gnulib のリポジトリにも入っていて、私はこのファイルを送ったら OK でした(gnulib の git リポジトリにある request-assign.future)
FSF の Copyright Assignment にはいろいろ書いてありますが、
読んでいると割と怖い内容ですが、会社で書いたコードを投稿しなければ問題はないです。
契約の範囲はソフトウェア毎(GCC, binutils, glibc, などなど)に契約する必要があります。契約の種類は「1回限り(request-assign.changes)」と「今後ずっと(request-assign.future)」があります。ソフトウェアを改善し続ける場合、毎回契約するのは面倒なので「今後ずっと」を選択すると思います。
契約の種類によって FSF に送るメールのテンプレートが異なります。詳細は Copyright Papers (Information for Maintainers of GNU Software) に書いてあります。和訳ないのかなあ、これ。
目次: Kindle - まとめリンク
割と昔からですが Kindle Fire がストアで買った本を認識しない(ダウンロードしない、一覧に出てこない)病気が頻発し、買ったはずの本が消えて、非常に困っています。
Kindle Fire がロストした本は、Kindle for PC だとあっさり捕捉できますから(「新しい商品」順に並べると確認しやすい)、Kindle Fire のバグだと思われます。
根本対策は不明ですが、対症療法はいくつかあります。お手軽な順に、
Kindle ストアも割と曲者で、複数のダウンロードボタンがある変な仕様(2018年 11月 17日の日記参照)です。1回押してダメでも諦めず、他の種類のダウンロードボタンも押してください。どれかが成功すればラッキーです。
最終手段ファクトリリセットには 1度だけお世話になりました。しかし再設定、再ダウンロードにかなり時間を浪費して辛いので、できればもう二度とやりたくありません。
Kindle Fire はちょっと変な挙動が多いです。仕様かバグか良くわからないものもあります。
Kindle のメインであるはずの本の機能さえ、この有様なので、本以外(アプリ、ミュージック、ムービーなど)の機能は一体どうなっていることやら??
メモ: 技術系の話は Facebook から転記しておくことにした。大幅に追記。
はるか昔(2007年 6月 7日の日記参照)に購入したピアノ音源 PMI Boesendorfer 290 を現在使っている PC にインストールしました。
当時の日本代理店はクリプトンでしたが、2008年に違う会社に変わったようです。
それはさておき、ソフトは古くても(13年前!)音源自体はインストールでき、音も正常に鳴ります。Windows の後方互換性は本当に素晴らしいなと思います。しかしアクティベーションだけがどうしてもできなくて困っています。
Boesendorfer 290 はインストール時にシリアル番号の入力が必要で、インストール後はアクティベーションが必要です。アクティベーションは各 PC 固有の番号と紐付いていて、別の PC にインストールしたときは再アクティベーションが必要となります。面倒くさいですね。
アクティベーションをしない状態で使い続けると、インストール後 5日間に動かなくなってしまいます。
購入当時はアクティベート画面の「REGISTER NOW」を押せば、オンラインでアクティベーションできましたが、現在このボタンを押すとエラーになります。
アクティベーション用のサイトの URL が変わったのか、ブラウザの接続警告が出ますし、転送先のサイトでは「そんなページは知らない」って言われます。これはひどい。
オンラインがダメならオフラインアクティベーションを試そうと思い「FILL OUT FORM」を押してみましたが、やっぱりダメでした。
エラーメッセージを見ても Internet browser の何がダメかわかりません。当然、ユーザーとしても対処しようがありません。このソフトを開発した人たちも、まさか 13年後にアクティベーションする奴がいるとは思っていなかったでしょう。
これ以上どうにもならないので、サポートに連絡することにしました。Boesendorfer 290 の販売は Native Instruments 社、開発は EastWest 社とありますが、どこに連絡すれば良いかわかりません。とりあえず販売 Native Instruments 社のサポートに連絡してみようと思います。
以前(2020年 6月 12日の日記参照)にインストール&アクティベートで失敗した Boesendorfer 290 ですが、Native Instruments のサポートの方との QA により、インストール方法がわかりました。
カギは Native Access というツールと、Kontakt Player というアプリケーションです。
Native Access はインストーラ&情報管理ツールです。アップデートもお知らせしてくれるようです。動きに癖があり、やや使いにくいです。
Kontakt Player は音源データから音色を合成するプレイヤーです。なんと無料、凄い。私はプレイヤーとしてしか使っていませんが、本来はもっと高機能なのかな?
私の理解が正しければ、Boesendorfer 290 にはピアノ音源データと、音色合成プレイヤーの 2つがセットになっていますが、音色合成プレイヤーはもはやアクティベーション不可能で利用できません。代わりに Kontakt 6 Player を使うのが正解です。
最初に Native Access をインストールします。インストーラを起動するだけです。このアプリはたまにアップデート確認画面から進まなくなることがあります。私の場合、Native Access を再起動するか、Windows を再起動したら直りました。
Kontakt 6 Player をインストールしようとすると、こんな画面になると思います。
これが Native Access の良くないところですね……、ディレクトリの設定に失敗しているとこのエラーになりますが、エラーメッセージを見ても、何が悪いのか全くわからないです。
Preferences というメニューからディレクトリの設定ができます。パスを指定するだけではダメで、ディレクトリが存在していないとさっきのエラーが出ます。パスの最後のディレクトリがなかったら、勝手に作ってくれれば良いのに。これも Native Access のイマイチなところです……。
次に Kontakt 6 Player をインストールします。
ダウンロードにはかなり時間が掛かりますが、待っていれば終わります。これは Native Access の良いところですね。とても楽です。
最後に Kontakt 6 Player に Boesendorfer 290 がインストールされている位置を教える必要があります。CD から Boesendorfer 290 をインストールした後に、下記の Browse を押します。
スクリーンショットでは Boesendorfer が INSTALLED PRODUCTS のカテゴリに居ますが、初回の設定だと NOT INSTALLED のカテゴリに居るはずです。1回インストールすると二度と NOT INSTALLED に戻らないみたいです。これも Native Access のイマイチなところ……。
このパス指定がまた曲者で、どのディレクトリを指定したら良いのかさっぱりわかりません。
正解は Boesendorfer をインストールしたディレクトリの直下にある「Boesendorfer 290 Library」というディレクトリです。Boesendorfer_part1.nks や Boesendorfer_part2.nks というファイルが入っているはずです。
おそらく *.nks が Kontakt 用の音色データで、Kontakt Player は音色データのあるディレクトリを知りたいんだろう、くらいの想像はつきますけど、正直に言ってわかりにくいです。
設定がうまくいけば Kontakt Player の画面に Boesendorfer 290 が出現するはずです。
Kontakt Player に Boesendorfer 290 が出れば成功
無事、動作したので良かったですけど、初心者殺しのポイント(Preferences, *.nks のパス)が多くて疲れました……。
半年くらい前から SAS(睡眠時無呼吸症候群)の対症療法として CPAP(経鼻的持続陽圧呼吸器)を使っています。しかし、こいつがどうも息苦しくて合ってない気がしてなりません。
呼吸が止まると SpO2(血中酸素飽和度)が下がります。医療機器以外で SpO2 を測る手段はありませんから、先日、思い切ってメモリ機能付きのパルスオキシメーター(= SpO2 計測器)Ubi-x LUKLA 2800m を購入しました。お値段 6万円です。さすが医療機器、高いなー。
添付のグラフは SpO2(青、左目盛り)と、脈拍(オレンジ、右目盛り)です。正常時の SpO2 は 98〜99%ですが、深夜 4時 3分に突如 88%まで下がっています。
SpO2 88%がどれほど危険なのかわからなかったので、わざと息を止めて SpO2 を下げる実験をしたところ「酸欠で目がチカチカして暗くなってきて、このまま死ぬんじゃねー?」と思うくらいでやっと SpO2 92%でした。88%とは一体……??
CPAP なしだとどうなるかも測りました。CPAP ありのときよりも頻繁に SpO2 が低下しています。
寝苦しくて辛いけど CPAP には意味があるということなんでしょうかねえ?
まだ 1度ずつしか測っていないので、CPAP の効力については何とも言えません。今後 CPAP あり・なし、仰向き、横向きなど条件を変えて何度か測ってみようと思います。
メモ: 技術系の話は Facebook から転記しておくことにした。大幅に追記。
目次: Linux - まとめリンク
最近、というほどでもないのですが、Linux Kernel のデバイスツリーのドキュメントは YAML で書く方が主流らしいです。
ドキュメントの書き方が良くわからず、合っているのか間違っているのかわからなくて困っていたんですが、ドキュメント Writing DeviceTree Bindings in json-schema を見ていたところ、YAML チェッカーの存在を知りました。
$ make dt_binding_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/xxxx.yaml
こんな感じで使います。DT_SCHEMA_FILES を指定せずに実行すると全ての YAML をチェックしようとしますが、16並列でもかなり時間が掛かりますから、自分が変更したファイルだけチェックしたほうが効率的でしょうね。
STATIONflow のしょうもない小技。
将来的に、どこに駅の入り口と電車の乗り場が出てくるか?を見たい場合は「マップ作成」で見たいマップを「複製」「マップ編集」すれば、全ての入り口、乗り場を見ることができます。
STATIONflow の面白さは、どこに増設されるかわからない入り口や乗り場に対応することではあるのですが、記録を狙ったりするときや、事前に完璧な駅建築計画を立てたい人は、覗いてみると役立つかもしれません。
STATIONflow の基本は理解したつもりなので、実績解除に挑んでますが、難しくて取れないものがいくつかあります。
今のところ一番難しいと思うのは「エレベスト」です。条件は「駅ランク 20 以上で階段とエスカレーターを設置せずに A+ 評価」です。
ありがちな条件ですが、STATIONflow のエレベーターはとてもヘボいため達成は困難です。
一番問題なのは 3つ目の条件です。通常、待ち行列から離脱した客は階段やエスカレーターを利用しますが、移動手段がエレベーターしかない場合、不可解な動きをします。
(※)彷徨っている間はエレベーターが到着しても乗りません。一番訳が分からない動きです。しかもこの彷徨う時間が長い。
このようなおかしな行動をし続けた挙句、勝手に怒り始めて、駅の評価を下げてきます。理不尽極まりないですね。
ランク 20 到達後も駅と人が増え続けるため、どんどん客が捌ききれなくなり条件が悪くなる一方ですから、「エレベスト」実績を達成する最大のチャンスは「ランク 20 になる瞬間」でしょう。
が、私の腕では評価 A が限界でした。
もっと頑張ればできるのかもしれないですが、何回も同じマップをランク 20 までやり直すのは時間掛かって辛いし面白くないのでもうやりません……。
STATIONflow のしょうもない小技 その 2 です。
実績の解除が非常に難しい「エレベスト」や「効率の鬼」のような難しい実績が、超簡単に取れる方法です。
例えば、エレベストの条件は「駅ランク 20 以上で階段とエスカレーターを設置せずに A+ 評価」です。前回(2020年 6月 27日の日記参照)お伝えした通り、エレベーターと通勤客の変な挙動が合わさって、まともにやるとかなり難しいです。
しかし STATIONflow の実績判定は甘々で「00:00 になった瞬間」しか見ません。従って、
これだけで達成できます。正直、こんな低レベルな小細工が通じると思わなかったので、逆にびっくりしました……。
類似した実績「階段抜き」「ノンエスカレーター」「エレベスト」「効率の鬼」ならば同じ手が通用するはずです。
作成者の想定とは違うだろうという意味で「邪道」な感じはしますが、バグを突いた挙動でもなさそうだし、早解きしたい方は利用してみても良いでしょう。
ノート PC でゲームをしていると、筐体が焼けそうなほど熱くなり、キーボードまで熱くなってキーを打つのが辛いです。
発熱の原因はグラフィックスチップですが、どうも CPU も無罪ではないらしく、TurboBoost を無効にするとややマシになることがわかりました。
私のマシンでは「電源オプション」 - 「プロセッサの電源管理」 - 「最大のプロセッサの状態」にて、87%以下にすると TurboBoost が OFF になりました。
下記は 87%設定(1.49GHz)と 88%設定(3.38GHz)にしたときの CPU 動作周波数の変化です。
87%に設定したときの CPU 動作周波数は 1.49GHz
88%に設定したときの CPU 動作周波数は 3.38GHz
当然ながら 1.49GHz と 3.38GHz では性能に天と地ほどの差があって、1.49GHz だと STATIONflow の画面はめちゃくちゃカクつきます。
しかしシミュレーションゲームでは、待っているだけの時もありますし、常に爆熱で動いてくれる必要はありません。TurboBoost を任意に OFF にできるのは非常に便利です。
CPU のクロック周波数の上限は何段階かあるようなので、変化点を調べました。
こんな感じでした。Core i5-8250U はベース周波数 1.6GHz、ブースト周波数 3.4GHz なのにベース周波数である 1.6GHz に張り付く設定は存在しません。謎です。
タスクマネージャーに「基本速度 1.8GHz」と表示されているのも謎です。どこから 1.8GHz 出てきた……??
以前(2020年 5月 28日の日記参照)STATIONflow で速度 3 にすると駅の評価が下がることをお伝えしましたが、若干間違っていて「画面の処理落ちで評価が下がる」方が実態に近そうです。
速度 2 で A+ 評価の駅を使って実験したところ、ノート PC のクロック周波数を低くするだけで、駅の評価が A に下がりました。
駅の評価と見せかけて、実はマシンの評価も入ってるんでしょうか?余計なお世話なので、勘弁してくれよ。
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2021.
Powered by PHP 5.2.17.
using GD bundled (2.0.34 compatible)(png support.)