コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2021年4月5日 >>> 2021年4月5日
link もっと後

2021年4月5日

GCCを調べる - GCC 8.3のfoldingバグ - 解決編

目次: GCC

GCC 9.1との比較でpass_forwpropより早いタイミングでcargf() → atan2f() の置き換えをすれば良さそうであることがわかりました。

さらにGCC 9.1の動作を追うとpass_lower_cfでcargf() → atan2f() の置き換えをしている箇所が、下記の箇所であることがわかります。

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() の置き換えが発生し、バグを隠してしまいます。

elfとlinuxのときのimplicit_pの違い

// 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フラグがセットされない仕組みになっているからです。

analyze_functions(true) から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など)のためですかね……?

編集者:すずき(2023/09/24 11:55)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2021年4月5日 >>> 2021年4月5日
link もっと後

管理用メニュー

link 記事を新規作成

<2021>
<<<04>>>
----123
45678910
11121314151617
18192021222324
252627282930-

最近のコメント5件

  • link 24年4月22日
    hdkさん (04/24 08:36)
    「うちのHHFZ4310は15年突破しまし...」
  • link 24年4月22日
    すずきさん (04/24 00:37)
    「ちゃんと数えてないですけど蛍光管が10年...」
  • link 24年4月22日
    hdkさん (04/23 20:52)
    「おお... うちのHHFZ4310より後...」
  • link 20年6月19日
    すずきさん (04/06 22:54)
    「ディレクトリを予め作成しておけば良いです...」
  • link 20年6月19日
    斎藤さん (04/06 16:25)
    「「Preferencesというメニューか...」

最近の記事3件

  • link 24年4月25日
    すずき (04/26 16:49)
    「[AVIFの変換] AVIFが読めないアプリケーションがたまにあるので、AVIF(AV1 Image File Format)...」
  • link 24年2月7日
    すずき (04/24 02:52)
    「[複数の音声ファイルのラウドネスを統一したい] PCやデジタル音楽プレーヤーで音楽を聞いていると、曲によって音量の大小が激しく...」
  • link 24年4月22日
    すずき (04/23 20:13)
    「[仕事部屋の照明が壊れた] いきなり仕事部屋のシーリングライトが消えました。蛍光管の寿命にしては去年(2022年10月19日の...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 04/26 16:49