目次: プロバイダ
ドコモがついに邪悪な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が使えたし、月額も安くなったし、まあ、悪いことばかりでもないか……。
この記事にコメントする
目次: PC
机の奥行きが60cmのためか、ディスプレイの足がキーボードとぶつかって若干邪魔なのと、前からディスプレイアームに興味があったので、ディスプレイアームを買いました。
買ったのはergotron LXデスクマウントアーム(品番45-241-026、製品サイトへのリンク)です。Amazonで11,000円くらいでした。1つだけディスプレイがつけられるタイプです。我が家はデュアルディスプレイなので、2つアームを買いました。
1つのアームに2つのディスプレイを付けられるタイプ(品番45-245-026、製品サイトへのリンク)もありますが、なぜかアーム2つ分より高かったことと、1か所に荷重が掛かりすぎて机が壊れたら嫌だなと思って購入は控えました。
アーム+ディスプレイをクランプでテーブルの天板に止めるだけの構造です。天板の強度が不安だったので、サンワサプライの補強プレートCR-LAPLT1を「裏側」につけています。
こんな感じです。デスクマウントアームに限らず、この手のクランプで固定するタイプの製品ならば作りは同じだと思いますが、表側は幅広の足でも、裏側は足が小さく、クランプを全力で締めると天板が凹んだり穴が開いてしまいます。
邪魔にならないように机の端ギリギリに付けたところ、アームが机の真ん中まで届かず、ディスプレイとディスプレイの間が妙に空いてしまいました。見た目がイマイチですね。まあいいか……。
重い頑丈そうなアームだけあって、安定感は素晴らしいです。
どの方向にもスイーっと動くのかと思っていたのですが、方向によりますね。軸回りに回転させるときは軽いですが、奥行方向、上下に動かすときはかなり力が要ります。逆に言えば手がぶつかった程度では動かないです。私はあまりディスプレイを動かさないのでこれくらい固いほうが良いです。
説明書によると突っ張り具合は調節可能らしいので、頻繁に動かしたい、固すぎて疲れる方は調整すると良いかもしれません。
この記事にコメントする
目次: GCC
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など)のためですかね……?
この記事にコメントする
目次: 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
最初に書いておくと、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
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のコードを追う準備ができました。
この記事にコメントする
目次: GCC
前回(2021年3月29日の日記参照)はconfigureオプションに --disable-bootstrapを指定してブートストラップモードを無効にしてビルドしました。ブートストラップモードが有効なときについても、メモしておこうと思います。
ブートストラップモードはホストのコンパイラでSTAGE1コンパイラをビルドし、STAGE1コンパイラを使ってSTAGE2とSTAGE3コンパイラをビルドして、ビルド結果に食い違いがないことを比較するモードです。ライブラリのビルドなどに使われる(最終的にインストールされる)のはSTAGE3のコンパイラのようです。3回GCCをビルドするので、ビルド時間は非ブートストラップモードの3倍近い時間がかかります。
ブートストラップモードのときはconfigureにCFLAGS, CXXFLAGSを指定する方法は使えません。代わりに GCCのマニュアルに記載がある通りmake BOOT_CFLAGS="-O0 -g -fno-inline" bootstrapとすれば良いです。
こちらがおそらく正規の手順で、configureにCFLAGS, CXXFLAGSを指定する方法は邪道なんでしょうけど、ブートストラップモードはビルドが遅くて辛いんだよなー……。
$ mkdir build $ cd build $ ../configure \ --prefix=`pwd`/_install \ --enable-languages=c,c++ $ make -j8 BOOT_CFLAGS="-O0 -g -fno-inline" bootstrap $ make install
ただしBOOT_CFLAGSの指定はSTAGE1には効きません。STAGE1だけは常に手堅い安定したオプションでビルドされます。
# gcc/Makefile.in
...
# Flags to pass to stage2 and later makes. They are defined
# here so that they can be overridden by Makefile fragments.
BOOT_CFLAGS= -g -O2
BOOT_LDFLAGS=
BOOT_ADAFLAGS= -gnatpg
...
# Defaults for all stages; some are overridden below.
STAGE_CFLAGS = $(BOOT_CFLAGS) ★★STAGE_CFLAGS = BOOT_CFLAGS★★
STAGE_TFLAGS = $(TFLAGS)
STAGE_CONFIGURE_FLAGS=@stage2_werror_flag@
# Defaults for stage 1; some are overridden below.
STAGE1_CFLAGS = $(STAGE_CFLAGS) ★★STAGE1_CFLAGS = STAGE_CFLAGS★★
STAGE1_CXXFLAGS = $(CXXFLAGS)
@if target-libstdc++-v3-bootstrap
# Override the above if we're bootstrapping C++.
STAGE1_CXXFLAGS = $(STAGE1_CFLAGS)
@endif target-libstdc++-v3-bootstrap
...
# By default, C and C++ are the only stage1 languages, because they are the
# only ones we require to build with the bootstrap compiler, and also the
# only ones useful for building stage2.
STAGE1_CFLAGS = @stage1_cflags@ ★★STAGE1_CFLAGSだけ無理やり上書きされる★★
STAGE1_CHECKING = @stage1_checking@
STAGE1_LANGUAGES = @stage1_languages@
当然ですがSTAGE2とSTAGE3には設定が反映されます。STAGE1コンパイラを手動で使って何かビルドする人はほぼいないと思うので、特に問題ないでしょう。
この記事にコメントする
| < | 2021 | > | ||||
| << | < | 04 | > | >> | ||
| 日 | 月 | 火 | 水 | 木 | 金 | 土 |
| - | - | - | - | 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 | - |
wiki
Linux JM
Java API
2002年
2003年
2004年
2005年
2006年
2007年
2008年
2009年
2010年
2011年
2012年
2013年
2014年
2015年
2016年
2017年
2018年
2019年
2020年
2021年
2022年
2023年
2024年
2025年
過去日記について
アクセス統計
サーバ一覧
サイトの情報合計:
本日: