GCC 8.3 のバグを追った記録です。来月になったら絶対に忘れて、説明できなくなるので、できる限り詳細にメモしておきたいと思います。
再現は簡単で、下記のコードをコンパイルすると internal compile error になります。
float cargf(float _Complex z);
float atan2f(float y, float x);
void func(float _Complex cval, float val)
{
__builtin_cargf(cval);
__builtin_atan2f(val, 1.0f);
}
$ x86_64-unknown-elf-gcc -Wall -O2 -g a.c a.c: In function 'func': a.c:7:2: warning: statement with no effect [-Wunused-value] __builtin_cargf(cval); ^~~~~~~~~~~~~~~~~~~~~ during GIMPLE pass: forwprop a.c:9:1: internal compiler error: tree check: expected ssa_name, have var_decl in simplify_builtin_call, at tree-ssa-forwprop.c:1246 } ^ 0x1331604 tree_check_failed(tree_node const*, char const*, int, char const*, ...) ../../gcc/tree.c:9338 0x7ca7f2 tree_check(tree_node*, char const*, int, char const*, tree_code) ../../gcc/tree.h:3142 0x1135fb9 simplify_builtin_call ../../gcc/tree-ssa-forwprop.c:1246 0x113b55f execute ../../gcc/tree-ssa-forwprop.c:2527 Please submit a full bug report, with preprocessed source if appropriate. Please include the complete backtrace with any bug report. See <https://gcc.gnu.org/bugs/> for instructions.
再現にあたり重要なポイントは 2点です。
なぜこの 2点が重要か?については、追々説明します。この条件だけで原因が「ああ、あれか」と見当がつく人は超凄いです。GCC マスターか天才ですね。この記事は一切読む必要がないです。ちなみに私は解析に 1週間近く掛かりました。辛かったです……。
このエラーはディストリビューションが配布する GCC 8.3 のバイナリでは発生しません。x86_64 向けの GCC でも発生させるには、下記に示すように特殊なビルド条件にする必要があります。
(※)GNU のビルドシステムが使うシステム名の表し方です。machine-vendor-operatingsystem の順で表します。
PC 向けでは特殊なビルド条件ですが、ベアメタル向けのクロスコンパイラだと、割とこの条件に当てはまるものは多いです。
$ ../configure \ --target=x86_64-unknown-elf \ --prefix=/path/to/gcc/build/_install \ --disable-bootstrap \ --disable-libsanitizer \ --enable-checking=yes \ --enable-languages="c,c++" \ CFLAGS="-g -O0 -fno-inline" \ CXXFLAGS="-g -O0 -fno-inline" $ make -j8 all-gcc $ make install-gcc
ビルドコンフィグの一例を示しました。disable-bootstrap はデバッグ用ビルドオプション(CFLAGS, CXXFLAGS)を指定するために使っています(詳しくは 2021年 3月 30日の日記参照)。disable-libsanitizer は私の環境でビルドエラーになったので、仕方なくビルド対象から外しています。enable-languages は Fortran などの今回使わない言語を削ってビルド時間を短縮するためです。
デバッグする対象はおなじみ cc1 です。なぜ cc1 なのかは以前書いた(2019年 5月 17日の日記参照)とおりです。
#### gdb でデバッグするなら $ gdb /path/to/build/_install/libexec/gcc/x86_64-unknown-elf/8.3.0/cc1 (gdb) run -quiet a.c -mtune=generic -march=x86-64 \ -g -O2 -Wall -std=c99 -o zzzzzzzz.s #### gdbserver を使うなら $ gdbserver --multi localhost:1234 \ -quiet a.c -mtune=generic -march=x86-64 \ -g -O2 -Wall -std=c99 -o zzzzzzzz.s
問題の再現と、GCC のコードを追う準備ができました。
最初に書いておくと、vuse から追う解析は正解には至りませんでしたが、試行錯誤のあとも一応残しておきます。再現環境とデバッグの準備ができました。エラーが発生する箇所を調べます。
// gcc/gcc/tree-ssa-forwprop.c
static bool
simplify_builtin_call (gimple_stmt_iterator *gsi_p, tree callee2)
{
gimple *stmt1, *stmt2 = gsi_stmt (*gsi_p);
tree vuse = gimple_vuse (stmt2);
if (vuse == NULL)
return false;
stmt1 = SSA_NAME_DEF_STMT (vuse); //★★ここでエラー
// gcc/gcc/tree.h
/* Returns the statement which defines this SSA name. */
#define SSA_NAME_DEF_STMT(NODE) SSA_NAME_CHECK (NODE)->ssa_name.def_stmt
// gcc/gcc/tree-check.h
#define SSA_NAME_CHECK(t) TREE_CHECK (t, SSA_NAME)
// gcc/gcc/tree.h
/* When checking is enabled, errors will be generated if a tree node
is accessed incorrectly. The macros die with a fatal error. */
#if defined ENABLE_TREE_CHECKING && (GCC_VERSION >= 2007)
//★★enable-checking=yes だとこちらが有効になるので、エラーが発生する
#define TREE_CHECK(T, CODE) \
(tree_check ((T), __FILE__, __LINE__, __FUNCTION__, (CODE)))
...
#else /* not ENABLE_TREE_CHECKING, or not gcc */
...
//★★enable-checking=release だとこちらが有効になるので、エラーが発生しない
#define TREE_CHECK(T, CODE) (T)
// gcc/gcc/tree.h
//★★
// SSA_NAME_DEF_STMT (vuse)
// TREE_CHECK (vuse, SSA_NAME)
// tree_check (vuse, __FILE__, __LINE__, __FUNCTION__, SSA_NAME)
//
// vuse の TREE_CODE は VAR_DECL
inline tree
tree_check (tree __t, const char *__f, int __l, const char *__g, tree_code __c)
{
if (TREE_CODE (__t) != __c) //★★TREE_CODE が SSA_NAME ではないので、このチェックに引っかかる
tree_check_failed (__t, __f, __l, __g, __c, 0);
return __t;
}
正直、これを見ても「だから何??」ですよね。
GCC Internals を見てもいまいち要領を得ませんが、変数への参照を表しているようです。エラーの原因となっているので、調べるしかありません。どこから来るのでしょうか?
// gcc/gcc/tree-ssa-forwprop.c
static bool
simplify_builtin_call (gimple_stmt_iterator *gsi_p, tree callee2)
{
//★★stmt2 はイテレータ gsi_p が指している先頭の要素
gimple *stmt1, *stmt2 = gsi_stmt (*gsi_p);
//★★vuse は gimple stmt2 を gimple_statement_with_memory_ops にキャストしたときの vuse メンバ
tree vuse = gimple_vuse (stmt2);
if (vuse == NULL)
return false;
stmt1 = SSA_NAME_DEF_STMT (vuse); //★★ここでエラー
// gcc/gcc/gimple.h
/* Return the single VUSE operand of the statement G. */
static inline tree
gimple_vuse (const gimple *g)
{
const gimple_statement_with_memory_ops *mem_ops_stmt =
dyn_cast <const gimple_statement_with_memory_ops *> (g);
if (!mem_ops_stmt)
return NULL_TREE;
return mem_ops_stmt->vuse;
}
// gcc/gcc/gimple-iterator.h
/* Return the current stmt. */
static inline gimple *
gsi_stmt (gimple_stmt_iterator i)
{
return i.ptr;
}
この vuse メンバを設定するのはどこでしょうか?ソースコードから探すのは困難そうなので、watchpoint で探しましょう。
$ gdb /path/to/build/_install/libexec/gcc/x86_64-unknown-elf/8.3.0/cc1 (gdb) r -quiet a.c -mtune=generic -march=x86-64 -g -O2 -Wall -std=c99 -o zzzzzzzz.s ...エラーが出ることを確認する... (gdb) b tree-ssa-forwprop.c:1246 Breakpoint 1 at 0x11360b5: file ../../gcc/tree-ssa-forwprop.c, line 1246. (gdb) r Breakpoint 1, simplify_builtin_call (gsi_p=0x7fffffffd680, callee2=0x7ffff74af300) at ../../gcc/tree-ssa-forwprop.c:1246 1246 stmt1 = SSA_NAME_DEF_STMT (vuse); (gdb) p *stmt2 $2 = {code = GIMPLE_CALL, no_warning = 0, visited = 0, nontemporal_move = 0, ... ★★code = GIMPLE_CALL なので gcall にキャストしてもう一回ダンプ (gdb) p *(gcall *)stmt2 $3 = {<gimple_statement_with_memory_ops_base> = {<gimple_statement_with_ops_base> = {<gimple> = {code = GIMPLE_CALL, no_warning = 0, visited = 0, nontemporal_move = 0, plf = 0, modified = 0, has_volatile_ops = 0, pad = 0, subcode = 0, uid = 0, location = 2147483655, num_ops = 5, bb = 0x7ffff7475410, next = 0x7ffff7476118, prev = 0x7ffff7599b90}, use_ops = 0x7ffff75b14f8}, vdef = 0x7ffff7ffbf30, vuse = 0x7ffff7ffbf30}, ★★これ★★ call_used = {anything = 1, nonlocal = 0, ... ★★stmt2->vuse のアドレスを調べる (gdb) p &((gcall *)stmt2)->vuse $4 = (tree *) 0x7ffff75b30c8
エラーが発生するときの vuse は 0x7ffff7ffbf30 で、vuse を持っている変数 ((gcall *)stmt2)->vuse のアドレスは 0x7ffff75b30c8 です。デバッグ時、アドレスは毎回同じになることを利用して、先程調べたアドレスに watchpoint を設定し、何か値が書き込まれたら止めます。
★★stmt2->vuse を書き換える箇所を特定するため watchpoint を設定する (gdb) watch *(int *)0x7ffff75b30c8 (gdb) r ...memset 系で止まるところは無視... Old value = 0 New value = -134234320 gimple_set_vuse (g=0x7ffff75b3090, vuse=0x7ffff7ffbf30) at ../../gcc/gimple.h:2084 2084 }
それらしき関数 gimple_set_vuse() が見つかりました。vuse の値も 0x7ffff7ffbf30 で関数 simplify_builtin_call() で観測した値と一致しており、別の用事で書き換えられたわけではなさそうです。
さらに追っていくと、vuse は cfun->gimple_df->vop が元になっていることがわかり、cfun->gimple_df->vop は create_vop_var() によって生成されていることがわかるのですが、そこで行き詰まってしまいます。GCC はエラーメッセージからエラーが発生した箇所はすぐにわかります。しかしエラーの原因はわからないことがほとんどです。GCC のデバッグの辛いところですね。
別のアプローチが必要そうです。
実は GCC 9.1 では carg, atan2 を並べてもエラーが発生しませんので、バグがどこかで直っています。GCC 9.1 と 8.3 の動作の違いを調べることで、原因と直し方がわかるはずです。
GIMPLE を出力しながらコードを追っていくと、下記の関数で carg が atan2 に変換され、その後 internal compile error になるようです。
// gcc/gcc/builtins.c
/* Fold a call to builtin carg(a+bi) -> atan2(b,a). */
static tree
fold_builtin_carg (location_t loc, tree arg, tree type)
{
if (validate_arg (arg, COMPLEX_TYPE)
&& TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == REAL_TYPE)
{
tree atan2_fn = mathfn_built_in (type, BUILT_IN_ATAN2); //★★この関数が NULL 以外を返す条件がある
if (atan2_fn)
{
tree new_arg = builtin_save_expr (arg); //★★ここにくると carg → atan2 に変換される
tree r_arg = fold_build1_loc (loc, REALPART_EXPR, type, new_arg);
tree i_arg = fold_build1_loc (loc, IMAGPART_EXPR, type, new_arg);
return build_call_expr_loc (loc, atan2_fn, 2, i_arg, r_arg);
}
}
return NULL_TREE;
}
関数 mathfn_built_in() は builtin_info[uns_fncode].implicit_p がセットされていないと NULL を返す仕組みになっています。
// gcc/gcc/builtins.c
/* Like mathfn_built_in_1, but always use the implicit array. */
tree
mathfn_built_in (tree type, combined_fn fn)
{
return mathfn_built_in_1 (type, fn, /*implicit=*/ 1); //★★
}
/* Return mathematic function equivalent to FN but operating directly on TYPE,
if available. If IMPLICIT_P is true use the implicit builtin declaration,
otherwise use the explicit declaration. If we can't do the conversion,
return null. */
static tree
mathfn_built_in_1 (tree type, combined_fn fn, bool implicit_p)
{
built_in_function fcode2 = mathfn_built_in_2 (type, fn);
if (fcode2 == END_BUILTINS)
return NULL_TREE;
if (implicit_p && !builtin_decl_implicit_p (fcode2)) //★★implicit_p フラグがセットされないと変換されない
return NULL_TREE;
return builtin_decl_explicit (fcode2); //★★指定された数学関数の tree を返す(今回は atan2f)
}
// gcc/gcc/tree.h
/* Return whether the standard builtin function can be used implicitly. */
static inline bool
builtin_decl_implicit_p (enum built_in_function fncode)
{
size_t uns_fncode = (size_t)fncode;
gcc_checking_assert (BUILTIN_VALID_P (fncode));
return (builtin_info[uns_fncode].decl != NULL_TREE
&& builtin_info[uns_fncode].implicit_p); //★★implicit_p フラグがセットされないと変換されない
}
// gcc/gcc/builtins.c
#define CASE_MATHFN(MATHFN) \
CASE_CFN_##MATHFN: \
fcode = BUILT_IN_##MATHFN; fcodef = BUILT_IN_##MATHFN##F ; \
fcodel = BUILT_IN_##MATHFN##L ; break;
/* Return a function equivalent to FN but operating on floating-point
values of type TYPE, or END_BUILTINS if no such function exists.
This is purely an operation on function codes; it does not guarantee
that the target actually has an implementation of the function. */
static built_in_function
mathfn_built_in_2 (tree type, combined_fn fn)
{
tree mtype;
...
switch (fn)
{
...
CASE_MATHFN (ATAN)
CASE_MATHFN (ATAN2) //★★ここにヒットして break
CASE_MATHFN (ATANH)
CASE_MATHFN (CBRT)
...
default:
return END_BUILTINS;
}
mtype = TYPE_MAIN_VARIANT (type);
if (mtype == double_type_node)
return fcode;
else if (mtype == float_type_node)
return fcodef; //★★ここにくる、返り値は BUILTIN_ATAN2F
else if (mtype == long_double_type_node)
return fcodel;
...
else if (mtype == float128x_type_node)
return fcodef128x;
else
return END_BUILTINS;
}
しかし、cargf → atan2f 変換自体はおかしいことではないはずです。
GCC 9.1 と動作を比較してみます。
GCC 9.1 (OK) | GCC 8.3 (NG) |
---|---|
analyze_functions(true) で implicit_p: false → true | analyze_functions(true) で implicit_p: false → true |
pass_lower_cf で cargf → atan2f に置き換え | pass_forwprop で cargf → atan2f に置き換え |
pass_lower_cf で implicit_p: true → true | pass_forwprop で implicit_p: true → true |
pass_build_ssa で vuse が SSA_NAME に置き換わる | (VAR_DECL のまま) |
pass_forwprop で simplify_builtin_call() | pass_forwprop で simplify_builtin_call() → エラー!! |
パスの実行順序は早い順に pass_lower_cf (008t.lower), pass_build_ssa (019t.ssa), pass_forwprop (029t.forwprop1) になります。カッコ内は GCC 9.1 で dump-tree-all を指定したときのダンプファイルとの対応です。8.3 の場合は pass_forwprop (033t.forwprop) になります。
動作の違いは cargf() → atan2f() の変換が行われるパスです。GCC 9.1 は pass_lower_cf ですが、GCC 8.3 は pass_forwprop です。GCC 9.1 は序盤のパスで cargf() → atan2f() の変換が行われるため、pass_build_ssa で vuse が適切に書き換えられて救われるようです。
光明が見えてきました。
GCC 9.1 との比較で pass_forwprop より早いタイミングで cargf() → atan2f() の置き換えをすれば良さそうであることがわかりました。
さらに GCC 9.1 の動作を追うと pass_lower_cf で cargf() → atan2f() の置き換えをしている箇所が、下記の箇所であることがわかります。
// gcc/gcc/gimple-low.c
class pass_lower_cf : public gimple_opt_pass
{
public:
pass_lower_cf (gcc::context *ctxt)
: gimple_opt_pass (pass_data_lower_cf, ctxt)
{}
/* opt_pass methods: */
virtual unsigned int execute (function *) { return lower_function_body (); } //★★これ
}; // class pass_lower_cf
/* Lower the body of current_function_decl from High GIMPLE into Low
GIMPLE. */
static unsigned int
lower_function_body (void)
{
struct lower_data data;
gimple_seq body = gimple_body (current_function_decl);
gimple_seq lowered_body;
gimple_stmt_iterator i;
gimple *bind;
gimple *x;
...
bind = gimple_seq_first_stmt (body);
lowered_body = NULL;
gimple_seq_add_stmt (&lowered_body, bind);
i = gsi_start (lowered_body);
lower_gimple_bind (&i, &data); //★★これ
...
/* Lower a bind_expr TSI. DATA is passed through the recursion. */
static void
lower_gimple_bind (gimple_stmt_iterator *gsi, struct lower_data *data)
{
tree old_block = data->block;
gbind *stmt = as_a <gbind *> (gsi_stmt (*gsi));
tree new_block = gimple_bind_block (stmt);
...
lower_sequence (gimple_bind_body_ptr (stmt), data); //★★これ
...
static void
lower_stmt (gimple_stmt_iterator *gsi, struct lower_data *data)
{
gimple *stmt = gsi_stmt (*gsi);
gimple_set_block (stmt, data->block);
switch (gimple_code (stmt))
{
...
case GIMPLE_CALL:
{
tree decl = gimple_call_fndecl (stmt);
unsigned i;
for (i = 0; i < gimple_call_num_args (stmt); i++)
{
tree arg = gimple_call_arg (stmt, i);
if (EXPR_P (arg))
TREE_SET_BLOCK (arg, data->block);
}
if (decl
&& DECL_BUILT_IN_CLASS (decl) == BUILT_IN_NORMAL)
{
if (DECL_FUNCTION_CODE (decl) == BUILT_IN_SETJMP)
{
lower_builtin_setjmp (gsi);
data->cannot_fallthru = false;
return;
}
else if (DECL_FUNCTION_CODE (decl) == BUILT_IN_POSIX_MEMALIGN
&& flag_tree_bit_ccp
&& gimple_builtin_call_types_compatible_p (stmt, decl))
{
lower_builtin_posix_memalign (gsi);
return;
}
}
if (decl && (flags_from_decl_or_type (decl) & ECF_NORETURN))
{
data->cannot_fallthru = true;
gsi_next (gsi);
return;
}
//★★
//GCC 9.1 だと下記のような fold_stmt() を呼ぶ処理があるが、GCC 8.3 には存在しない
//GCC 8.3 に同様の処理を足すときは #include "gimple-fold.h" も足す必要がある
/* We delay folding of built calls from gimplification to
here so the IL is in consistent state for the diagnostic
machineries job. */
if (gimple_call_builtin_p (stmt))
fold_stmt (gsi);
}
break;
...
この変更を加えると internal compile error が出なくなります。
最初に、このエラーの発生条件の 1つとして Target triplet(※)の operatingsystem = elf になっていることを挙げました。
理由は operationgsystem = linux にすると implicit_p が最初から全てセットされた状態でコンパイル処理が始まるからです。GCC 8.3 でも pass_forwprop より早い段階で cargf() → atan2f() の置き換えが発生し、バグを隠してしまいます。
// gcc/gcc/builtins.def
/* Builtin that is specified by C99 and C90 reserve the name for future use.
We can still recognize the builtin in C90 mode but we can't produce it
implicitly. */
#undef DEF_C99_C90RES_BUILTIN
#define DEF_C99_C90RES_BUILTIN(ENUM, NAME, TYPE, ATTRS) \
DEF_BUILTIN (ENUM, "__builtin_" NAME, BUILT_IN_NORMAL, TYPE, TYPE, \
true, true, !flag_isoc99, ATTRS, targetm.libc_has_function (function_c99_misc), true)
...
/* Category: math builtins. */
DEF_LIB_BUILTIN (BUILT_IN_ACOS, "acos", BT_FN_DOUBLE_DOUBLE, ATTR_MATHFN_FPROUNDING_ERRNO)
DEF_C99_C90RES_BUILTIN (BUILT_IN_ACOSF, "acosf", BT_FN_FLOAT_FLOAT, ATTR_MATHFN_FPROUNDING_ERRNO)
...
//★★atan2f は fn_class = function_c99_misc を渡して libc_has_function() を呼ぶ★★
// gcc/gcc/config/linux.h
/* Determine what functions are present at the runtime;
this includes full c99 runtime and sincos. */
# undef TARGET_LIBC_HAS_FUNCTION
# define TARGET_LIBC_HAS_FUNCTION linux_libc_has_function
// gcc/gcc/config/linux.c
bool
linux_libc_has_function (enum function_class fn_class)
{
if (OPTION_GLIBC || OPTION_MUSL)
return true;
if (OPTION_BIONIC)
if (fn_class == function_c94
|| fn_class == function_c99_misc
|| fn_class == function_sincos)
return true; //★★operatingsystem = linux のときは acosf の implicit_p の初期値は true
return false;
}
// gcc/gcc/config/elfos.h
#undef TARGET_LIBC_HAS_FUNCTION
#define TARGET_LIBC_HAS_FUNCTION no_c99_libc_has_function
// gcc/gcc/targhooks.c
bool
no_c99_libc_has_function (enum function_class fn_class ATTRIBUTE_UNUSED)
{
return false; //★★operatingsystem = elf のときは implicit_p の初期値は false
}
最初は、この発生条件に気づかなくて GCC 8.3 のバグだと気づくのがだいぶ遅れました……。
関数 cargf() や atan2f() はビルトイン関数のため、何も宣言しなくても関数の実体が存在します。しかし最適化を有効にするには、あえて関数宣言する必要があります。関数宣言しないと implicit_p フラグがセットされない仕組みになっているからです。
// gcc/gcc/cgraphunit.c
void
symbol_table::finalize_compilation_unit (void)
{
...
/* Gimplify and lower all functions, compute reachability and
remove unreachable nodes. */
analyze_functions (/*first_time=*/true); //★★これ
...
static void
analyze_functions (bool first_time)
{
/* Keep track of already processed nodes when called multiple times for
intermodule optimization. */
cgraph_node *first_handled = first_analyzed;
varpool_node *first_handled_var = first_analyzed_var;
hash_set<void *> reachable_call_targets;
symtab_node *node;
symtab_node *next;
int i;
ipa_ref *ref;
bool changed = true;
location_t saved_loc = input_location;
...
/* Analysis adds static variables that in turn adds references to new functions.
So we need to iterate the process until it stabilize. */
while (changed)
{
...
/* Lower representation, build callgraph edges and references for all trivially
needed symbols and all symbols referred by them. */
while (queued_nodes != &symtab_terminator)
{
changed = true;
node = queued_nodes;
queued_nodes = (symtab_node *)queued_nodes->aux;
cgraph_node *cnode = dyn_cast <cgraph_node *> (node);
...
if (!cnode->analyzed)
cnode->analyze (); //★★これ
...
/* Analyze the function scheduled to be output. */
void
cgraph_node::analyze (void)
{
if (native_rtl_p ())
{
analyzed = true;
return;
}
tree decl = this->decl;
location_t saved_loc = input_location;
input_location = DECL_SOURCE_LOCATION (decl);
...
if (alias)
resolve_alias (cgraph_node::get (alias_target), transparent_alias);
else if (dispatcher_function)
{
...
}
else
{
push_cfun (DECL_STRUCT_FUNCTION (decl));
assign_assembler_name_if_needed (decl);
/* Make sure to gimplify bodies only once. During analyzing a
function we lower it, which will require gimplified nested
functions, so we can end up here with an already gimplified
body. */
if (!gimple_has_body_p (decl))
gimplify_function_tree (decl); //★★これ
...
// gcc/gcc/gimplify.c
/* Entry point to the gimplification pass. FNDECL is the FUNCTION_DECL
node for the function we want to gimplify.
Return the sequence of GIMPLE statements corresponding to the body
of FNDECL. */
void
gimplify_function_tree (tree fndecl)
{
tree parm, ret;
gimple_seq seq;
gbind *bind;
...
bind = gimplify_body (fndecl, true); //★★これ
...
/* Gimplify the body of statements of FNDECL and return a GIMPLE_BIND node
containing the sequence of corresponding GIMPLE statements. If DO_PARMS
is true, also gimplify the parameters. */
gbind *
gimplify_body (tree fndecl, bool do_parms)
{
location_t saved_location = input_location;
gimple_seq parm_stmts, parm_cleanup = NULL, seq;
gimple *outer_stmt;
gbind *outer_bind;
struct cgraph_node *cgn;
...
/* Gimplify the function's body. */
seq = NULL;
gimplify_stmt (&DECL_SAVED_TREE (fndecl), &seq); //★★これ
...
/* Gimplify an expression which appears at statement context. The
corresponding GIMPLE statements are added to *SEQ_P. If *SEQ_P is
NULL, a new sequence is allocated.
Return true if we actually added a statement to the queue. */
bool
gimplify_stmt (tree *stmt_p, gimple_seq *seq_p)
{
gimple_seq_node last;
last = gimple_seq_last (*seq_p);
gimplify_expr (stmt_p, seq_p, NULL, is_gimple_stmt, fb_none); //★★これ
return last != gimple_seq_last (*seq_p);
}
enum gimplify_status
gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p,
bool (*gimple_test_f) (tree), fallback_t fallback)
{
...
/* Loop over the specific gimplifiers until the toplevel node
remains the same. */
do
{
...
/* Make sure that all the cases set 'ret' appropriately. */
ret = GS_UNHANDLED;
switch (TREE_CODE (*expr_p))
{
...
case BIND_EXPR:
ret = gimplify_bind_expr (expr_p, pre_p); //★★これ
break;
...
//★★以降は
// gimplify_bind_expr
// gimplify_stmt
//
// gimplify_expr
// gimplify_statement_list
// gimplify_stmt
//
// gimplify_expr
// gimplify_call_expr
//
// gimplify_expr
// gimplify_addr_expr
//
// こんな感じ
/* Rewrite the ADDR_EXPR node pointed to by EXPR_P
unary_expr
: ...
| '&' varname
...
PRE_P points to the list where side effects that must happen before
*EXPR_P should be stored.
POST_P points to the list where side effects that must happen after
*EXPR_P should be stored. */
static enum gimplify_status
gimplify_addr_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
{
tree expr = *expr_p;
tree op0 = TREE_OPERAND (expr, 0);
enum gimplify_status ret;
location_t loc = EXPR_LOCATION (*expr_p);
switch (TREE_CODE (op0))
{
...
default:
/* If we see a call to a declared builtin or see its address
being taken (we can unify those cases here) then we can mark
the builtin for implicit generation by GCC. */
if (TREE_CODE (op0) == FUNCTION_DECL
&& DECL_BUILT_IN_CLASS (op0) == BUILT_IN_NORMAL
&& builtin_decl_declared_p (DECL_FUNCTION_CODE (op0)))
set_builtin_decl_implicit_p (DECL_FUNCTION_CODE (op0), true); //★★これ
...
//★★この if 文が成立するには cargf や atan2f 関数を宣言する必要がある。
//
// TREE_CODE (op0) == FUNCTION_DECL
// DECL_BUILT_IN_CLASS (op0) == BUILT_IN_NORMAL
// builtin_decl_declared_p (DECL_FUNCTION_CODE (op0)): cargf や atan2f を関数宣言しないと true にならない
//
// ソースコードから、下記の宣言を削除すると、
//
// float cargf(float _Complex z);
// float atan2f(float y, float x);
//
// TREE_CODE (op0) == FUNCTION_DECL : 1
// DECL_BUILT_IN_CLASS (op0) == BUILT_IN_NORMAL : 1
// builtin_decl_declared_p (DECL_FUNCTION_CODE (op0): 0
// gcc/gcc/tree.h
/* Set the implicit flag for a builtin function. */
static inline void
set_builtin_decl_implicit_p (enum built_in_function fncode, bool implicit_p)
{
size_t uns_fncode = (size_t)fncode;
gcc_checking_assert (BUILTIN_VALID_P (fncode)
&& builtin_info[uns_fncode].decl != NULL_TREE);
builtin_info[uns_fncode].implicit_p = implicit_p; //★★implicit_p が書き換わる
}
ソースコードから cargf() や atan2f() の宣言を削除すると、implicit_p がセットされなくなって cargf() を atan2f() に置き換える最適化が発動しなくなります。cargf() が存在しないような環境(C89 など)のためですかね……?
机の奥行きが 60cm のためか、ディスプレイの足がキーボードとぶつかって若干邪魔なのと、前からディスプレイアームに興味があったので、ディスプレイアームを買いました。
買ったのは ergotron LX デスクマウントアーム(品番 45-241-026、製品サイトへのリンク)です。Amazon で 11,000円くらいでした。1つだけディスプレイがつけられるタイプです。我が家はデュアルディスプレイなので、2つアームを買いました。
1つのアームに 2つのディスプレイを付けられるタイプ(品番 45-245-026、製品サイトへのリンク)もありますが、なぜかアーム 2つ分より高かったことと、1か所に荷重が掛かりすぎて机が壊れたら嫌だなと思って購入は控えました。
アーム+ディスプレイをクランプでテーブルの天板に止めるだけの構造です。天板の強度が不安だったので、サンワサプライの補強プレート CR-LAPLT1 を「裏側」につけています。
こんな感じです。デスクマウントアームに限らず、この手のクランプで固定するタイプの製品ならば作りは同じだと思いますが、表側は幅広の足でも、裏側は足が小さく、クランプを全力で締めると天板が凹んだり穴が開いてしまいます。
邪魔にならないように机の端ギリギリに付けたところ、アームが机の真ん中まで届かず、ディスプレイとディスプレイの間が妙に空いてしまいました。見た目がイマイチですね。まあいいか……。
重い頑丈そうなアームだけあって、安定感は素晴らしいです。
どの方向にもスイーっと動くのかと思っていたのですが、方向によりますね。軸回りに回転させるときは軽いですが、奥行方向、上下に動かすときはかなり力が要ります。逆に言えば手がぶつかった程度では動かないです。私はあまりディスプレイを動かさないのでこれくらい固いほうが良いです。
説明書によると突っ張り具合は調節可能らしいので、頻繁に動かしたい、固すぎて疲れる方は調整すると良いかもしれません。
ドコモがついに邪悪な 2年縛りを諦めてシンプルなシングルプランを作ったと聞いて、喜び勇んでプラン変更をしました。プラン変更自体はオンラインですぐに終わりました。
ところがプラン変更から 5分後くらいに携帯が圏外になりました。切り替わりの途中でおかしくなったかと思い、次の日まで待ったり、再起動などしてみるも、一向に圏外のまま変わりません。おや?これは、何かやらかしてしまった?
長いんで結論だけ先に言うと、私が使っている Zenfone 3 (ZS550KL) と Zenfone 4 (ZE554KL) では ahamo は使用できないことがわかりました。あと、最終的にスマホは Pixel 4a に買い換えました。
Zenfone 3 は外装ボロボロでバッテリーも寿命で買い替え時ですが、Zenfone 4 は新しくてまだキレイなのに、使えなくなるのはちょっと悲しいです。
まず、手持ちの WiFi ルーター(Huawei GL10P、SIM ロック外した版)に挿して APN を設定すると、データ通信が可能でした。そのため SIM カードの故障や、我が家が 4G の圏外、ではないです。WiFi ルーターに挿していた b-mobile の MVNO SIM カードを Zenfone 3 に挿すと使えるので、Zenfone 3 の故障でもなさそうです。
Zenfone 3 は何らかの理由で ahamo が使えない可能性が高いです。より新しい Zenfone 4 ならば ahamo が使えるかもしれません。困ったことに SIM カードの形が違う(Zenfone 3, GL10P は micro SIM、Zenfone 4 は nano SIM)ので、ハサミで SIM カードを nano の形に切って Zenfone 4 に挿しました。が、やはり圏外のままです。考えられる可能性として、
これらの可能性を否定するには ahamo 対応機種を購入して試すしかありません。しかしながら、ドコモの対応機種リスト(対応端末一覧 | ahamo)を見ても特に欲しい機種がなく、唯一気になった Google Pixel 3 は既に終売で手に入りませんでした。
中古のスマホは使いたくないし、確実に動くけどあまり欲しくない機種を買うか、数万円をドブに捨てる覚悟でさらに別の機種を試すか、悩ましいですね。
どの機種を買うか悩んだ末に Google Pixel 4a を買いました。Google ストアで、45,000円くらいです。SoC は Snapdragon 730G、メモリ 6GB、バッテリー容量は 3140mAh らしいです。
Pixel 4a は ahamo 対応機種リストには載っていませんが、Pixel 3a でドコモの VoLTE に対応して、Pixel 4a で未対応に戻す、なんて面倒なことはやらんだろと考えて Pixel 4a に賭けました。
注文してから 2週間位して Google Pixel 4a が届きました。とてもシンプルな箱です。
ドキドキしながら SIM カードを挿し込んでみると……無事認識しました。電話も掛けられますし、データ通信もできます。いやぁ、良かった良かった。これで一安心です。
Pixel 4a が届くまでの間、反省の意味も込めて、何でこんなことになったのか調べてみました。
まず、従来のドコモ回線と ahamo の大きな違いは「3G が使えない」ことです。音声通信、データ通信ともに 4G を使う必要があります。
最近のスマホは 4G に対応しているものの、音声通話のみ 3G で行う(CSFB, Circuit Switched Fallback)機種と、音声通話、データ通信ともに 4G で行う(VoLTE, Voice over LTE)機種があります。この辺りの仕組みは IIJ 堂前さんの記事 MVNO と VoLTE の関係 : MVNO の深イイ話 - ITmedia Mobile が大変参考になります。
Zenfone 3 と Zenfone 4 は au とソフトバンク回線に対しては VoLTE を使いますが、ドコモ回線に対しては CSFB を使う機種のようです。当然、そんなことはどこにも書いていませんが、3G が使えないドコモ回線(つまり ahamo)の SIM カードを挿すと、圏外になってしまう現象を見る限りの推察です。
VoLTE がキャリアごとに対応が必要だという点は知りませんでした。Zenfone = VoLTE 対応、程度の浅い知識で ahamo に変えると、今回のような悲しい一件に繋がります。完全に私の調査不足ですね。
Zenfone 4 が使えなかったのは高い勉強代となりましたが、Google Pixel 4a で ahamo が使えたし、月額も安くなったし、まあ、悪いことばかりでもないか……。
我が家の本棚は広い方ではないのに、ほとんど参照しない書類が幅を取っていて、本棚を圧迫しています。先日本を買ったところ、ついに本を置くところがなくなったので、思い切って紙の書類は電子化して捨てて、本棚を空けることにしました。
電子化と言えばドキュメントスキャナーということで Canon ImageFORMULA DR-P215II を買いました。ヨドバシで 24,000円くらいでした。USB バスパワーで動作し、軽量コンパクトにも関わらず、20枚も用紙トレイに置けるイケてるモデルです。
このモデルだと高いな〜と思われる方には、安価な DR-P208II もあります。コンパクトさは一緒ですが、一度に用紙トレイにおける枚数がちょっと少なくなります。
蓋を閉じた状態では大きい羊羹のような風貌ですが、蓋を開くと一気にメカっぽい見た目になります。
用紙は上から投入、前面から排出されます。両面同時にスキャンするので、裏向きに入れなおす必要はありません。便利。出てきた紙を保持しておく仕組みはありません。棚の上とかで使うと床に紙が落ちて散乱しますから、広い机で使うと良さそうです。ほんと軽いんで簡単にどこでも持って行けます。
製品には CD が付属していますが、特に何もインストールせずともスキャンできます。USB Type-A - micro-B ケーブルで接続し、蓋を開けると自動的に電源が入ります。
上記のように USB マスストレージとしても認識されます。ストレージ内に Capture On Touch Lite という名前のスキャン用のソフトウェアが入っています。スキャンする解像度は 150〜600dpi の間で選択できます。600dpi が上限なのは、スキャナーのハードウェア的な性能は 600dpi だからでしょう。
付属のスキャナーソフト Capture On Touch Lite のメイン画面
今回は原稿となる書類を捨てるため、二度と再スキャンできません。高画質で残したほうが何かと役立つでしょう。上から 2番目の解像度 400dpi を選びました。
解像度 400dpi フルカラースキャン速度は、だいぶまったりで、正直なところ遅いな……と思いました。しかもデータ転送が間に合わないのか、スキャン中にちょいちょい止まります。同じ解像度 400dpi でも白黒スキャンであれば、フルカラーの倍速出るので快適です。
スキャナーの画質は思っていたより良く、両面一気に取り込んでくれて便利だし、細かい字も読めます。色も綺麗です。スキャナの構造上、用紙が斜めに吸い込まれてスキャンされる場合がありますが、そこは Capture On Touch Lite が良かれに補正するらしく、画像が傾いて困ることはあまり起きませんでした。
それより気になったのは、用紙を 2枚同時に吸い込むことです。10年ものの古い書類をスキャンしたせいかもしれませんが、20枚スキャンすると 1回は発生する印象を受けました。ひどいときは 3〜4枚同時に吸い込みます。
用紙を 20枚セットすれば全自動でスキャンできると思っていたので、想定とやや違ったものの、フラットベッドタイプのスキャナーで 1枚 1枚チマチマとスキャンしていた時と比べれば、はるかに便利で速くて快適です。買って良かったですね。
Switch にてファイアーエムブレム 風花雪月を買いました。ファイアーエムブレムをプレイするのは、おそらくスーパーファミコンの「聖戦の系譜」以来です。聖戦の系譜と言えば、主人公であるシグルド様の陣営がメテオでほぼ全滅し、第二部が始まるという衝撃のストーリーでした……懐かしいですね。それはさておき。
交互に殴り合う戦闘システム、親密度といったファイアーエムブレムの基本システムは維持されています。ここを変えたら違うゲームになっちゃうから当然ですかねえ。
聖戦の系譜では親密度は第一部(親世代)のペアリングに影響し、第二部(子世代)の攻略難易度が大きく変わる割と重要な要素でした。一部キャラクターとの会話も変わりますがオマケ程度の存在でした。風花雪月でも親密度はキャラクターの強さに影響するので大事ですが、どちらかというと「各キャラクターとの会話」に重きが置かれたようです。聖戦の系譜には存在しなかったアドベンチャーパートと多彩な会話シーンによって……そうです、ぶっちゃけギャルゲーになりました。例を挙げれば、
ちなみに親密度は従来どおりの上げ方(キャラクターが隣接した状態で戦闘する)でも上がりますので、アドベンチャーパートなんてダルいんじゃ、戦略 SRPG をやりたいんじゃー!って人はアドベンチャーパートは最低限こなして、戦闘だけガンガンやってても進むはずです。今作の半分近くを占めるキャラクター会話を捨てると、パラメーター強化にも関係してますし、ちょっともったいない気もしますが……。
グラフィクスはアニメーションも所々に入りますが、基本的には 3D キャラクターたちが良く動きます。総じてキレイです、よくできてるなあ。トゥーンレンダリングで、色の階調をあえて潰してセル画っぽくしているのかなあ?さすがにセル画が動いている!とまでは行きませんけど、トゥーンレンダリングで気になりがちな妙な太い線は気にならないです。
今作はクラスチェンジしてもレベルが維持されたままです。パラメータは「HP、力、魔力、技、速さ、幸運、守備、魔防、魅力」の 9つで、上限値も高そう(従来は 30、今作は 50 以上はありそう)です。
初期値はいずれも 1桁、高くても 15 もありません。よって初期値の合計は 100 前後です。全ステータスカンストするには合計 500 くらい上げる必要がありそうです。要求するものは多いのに、レベルアップ時のステータス上昇は合計 +2〜+5 程度と渋めです。ファイアーエムブレムのレベル上限は 30 だか 40 なので、この調子で上限レベルに達したところで、合計 250 が良いところ。1つもステータスカンストできそうにありません。
ステータスカンストどころか、下手すると全員雑魚になってしまって途中で敵が倒せなくなって進行不能にならないか?レベルアップ時ステータス厳選ガチャが必須だった?いや、現代のゲームの標準難易度がそんな鬼畜設定のはずがないだろ……??などと不思議に思っていました。
さんざんやった挙句に進行不能になるのは困るので、攻略サイトを見たところ「レベル99 まで上がる」って書いてありました。ええっ 99?まさかのレベル上限です。逆にそんなに要ります?
ノーマル難易度なら無限に挑めるレベル上げ用マップがありますからレベル 99 でも 100 でも達成できましょうが、他の難易度ではどうやってもレベル 99 にはなりませんよね?
メモ: 技術系?の話は Facebook から転記しておくことにした。大幅に追記。
管理者: 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.)