* cgraph.c (cgraph_create_indirect_edge): Update call of
authorhubicka <hubicka@138bc75d-0d04-0410-961f-82ee72b054a4>
Sat, 5 Jul 2014 17:22:44 +0000 (17:22 +0000)
committerhubicka <hubicka@138bc75d-0d04-0410-961f-82ee72b054a4>
Sat, 5 Jul 2014 17:22:44 +0000 (17:22 +0000)
get_polymorphic_call_info.
* ipa-utils.h (get_polymorphic_call_info): Add parameter CALL.
(possible_polymorphic_call_targets): Add parameter call.
(decl_maybe_in_construction_p): New predicate.
(get_polymorphic_call_info): Add parameter call;
use decl_maybe_in_construction_p.
* gimple-fold.c (fold_gimple_assign): Update use of
possible_polymorphic_call_targets.
(gimple_fold_call): Likewise.
* ipa-prop.c: Inlcude calls.h
(ipa_binfo_from_known_type_jfunc): Check that known type is record.
(param_type_may_change_p): New predicate.
(detect_type_change_from_memory_writes): Break out from ...
(detect_type_change): ... this one; use
param_type_may_change_p.
(detect_type_change_ssa): Use param_type_may_change_p.
(compute_known_type_jump_func): Use decl_maybe_in_construction_p.

* g++.dg/ipa/devirt-26.C: Update testcase.
* g++.dg/ipa/imm-devirt-1.C: Update testcase.
* g++.dg/ipa/imm-devirt-2.C: Update testcase.

git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@212304 138bc75d-0d04-0410-961f-82ee72b054a4

gcc/ChangeLog
gcc/cgraph.c
gcc/gimple-fold.c
gcc/ipa-devirt.c
gcc/ipa-prop.c
gcc/ipa-utils.h
gcc/testsuite/ChangeLog
gcc/testsuite/g++.dg/ipa/devirt-26.C
gcc/testsuite/g++.dg/ipa/imm-devirt-1.C
gcc/testsuite/g++.dg/ipa/imm-devirt-2.C

index d300f61..5cab9dd 100644 (file)
@@ -1,3 +1,24 @@
+2014-07-05  Jan Hubicka   <hubicka@ucw.cz>
+
+       * cgraph.c (cgraph_create_indirect_edge): Update call of
+       get_polymorphic_call_info.
+       * ipa-utils.h (get_polymorphic_call_info): Add parameter CALL.
+       (possible_polymorphic_call_targets): Add parameter call.
+       (decl_maybe_in_construction_p): New predicate.
+       (get_polymorphic_call_info): Add parameter call;
+       use decl_maybe_in_construction_p.
+       * gimple-fold.c (fold_gimple_assign): Update use of
+       possible_polymorphic_call_targets.
+       (gimple_fold_call): Likewise.
+       * ipa-prop.c: Inlcude calls.h
+       (ipa_binfo_from_known_type_jfunc): Check that known type is record.
+       (param_type_may_change_p): New predicate.
+       (detect_type_change_from_memory_writes): Break out from ...
+       (detect_type_change): ... this one; use 
+       param_type_may_change_p.
+       (detect_type_change_ssa): Use param_type_may_change_p.
+       (compute_known_type_jump_func): Use decl_maybe_in_construction_p.
+
 2014-07-05  Charles Baylis  <charles.baylis@linaro.org>
 
        PR target/49423
index 41dcaf9..4cc8c9b 100644 (file)
@@ -967,7 +967,7 @@ cgraph_create_indirect_edge (struct cgraph_node *caller, gimple call_stmt,
       get_polymorphic_call_info (caller->decl,
                                 target,
                                 &otr_type, &otr_token,
-                                &context);
+                                &context, call_stmt);
 
       /* Only record types can have virtual calls.  */
       gcc_assert (TREE_CODE (otr_type) == RECORD_TYPE);
index 3dcb576..1a90319 100644 (file)
@@ -376,7 +376,7 @@ fold_gimple_assign (gimple_stmt_iterator *si)
              {
                bool final;
                vec <cgraph_node *>targets
-                 = possible_polymorphic_call_targets (val, &final);
+                 = possible_polymorphic_call_targets (val, stmt, &final);
                if (final && targets.length () <= 1 && dbg_cnt (devirt))
                  {
                    tree fndecl;
@@ -1125,7 +1125,7 @@ gimple_fold_call (gimple_stmt_iterator *gsi, bool inplace)
        {
          bool final;
          vec <cgraph_node *>targets
-           = possible_polymorphic_call_targets (callee, &final);
+           = possible_polymorphic_call_targets (callee, stmt, &final);
          if (final && targets.length () <= 1 && dbg_cnt (devirt))
            {
              tree lhs = gimple_call_lhs (stmt);
index f7418f1..da8dfcf 100644 (file)
@@ -1438,6 +1438,99 @@ vtable_pointer_value_to_binfo (const_tree t)
                                         offset, vtable);
 }
 
+/* We know that the instance is stored in variable or parameter
+   (not dynamically allocated) and we want to disprove the fact
+   that it may be in construction at invocation of CALL.
+
+   For the variable to be in construction we actually need to
+   be in constructor of corresponding global variable or
+   the inline stack of CALL must contain the constructor.
+   Check this condition.  This check works safely only before
+   IPA passes, because inline stacks may become out of date
+   later.  */
+
+bool
+decl_maybe_in_construction_p (tree base, tree outer_type,
+                             gimple call, tree function)
+{
+  outer_type = TYPE_MAIN_VARIANT (outer_type);
+  gcc_assert (DECL_P (base));
+
+  /* After inlining the code unification optimizations may invalidate
+     inline stacks.  Also we need to give up on global variables after
+     IPA, because addresses of these may have been propagated to their
+     constructors.  */
+  if (DECL_STRUCT_FUNCTION (function)->after_inlining)
+    return true;
+
+  /* Pure functions can not do any changes on the dynamic type;
+     that require writting to memory.  */
+  if (!auto_var_in_fn_p (base, function)
+      && flags_from_decl_or_type (function) & (ECF_PURE | ECF_CONST))
+    return false;
+
+  for (tree block = gimple_block (call); block && TREE_CODE (block) == BLOCK;
+       block = BLOCK_SUPERCONTEXT (block))
+    if (BLOCK_ABSTRACT_ORIGIN (block)
+       && TREE_CODE (BLOCK_ABSTRACT_ORIGIN (block)) == FUNCTION_DECL)
+      {
+       tree fn = BLOCK_ABSTRACT_ORIGIN (block);
+
+       if (TREE_CODE (TREE_TYPE (fn)) != METHOD_TYPE
+           || (!DECL_CXX_CONSTRUCTOR_P (fn)
+               || !DECL_CXX_DESTRUCTOR_P (fn)))
+         {
+           /* Watch for clones where we constant propagated the first
+              argument (pointer to the instance).  */
+           fn = DECL_ABSTRACT_ORIGIN (fn);
+           if (!fn
+               || !is_global_var (base)
+               || TREE_CODE (TREE_TYPE (fn)) != METHOD_TYPE
+               || (!DECL_CXX_CONSTRUCTOR_P (fn)
+                   || !DECL_CXX_DESTRUCTOR_P (fn)))
+             continue;
+         }
+       if (flags_from_decl_or_type (fn) & (ECF_PURE | ECF_CONST))
+         continue;
+
+       /* FIXME: this can go away once we have ODR types equivalency on
+          LTO level.  */
+       if (in_lto_p && !polymorphic_type_binfo_p (TYPE_BINFO (outer_type)))
+         return true;
+       tree type = TYPE_MAIN_VARIANT (method_class_type (TREE_TYPE (fn)));
+       if (types_same_for_odr (type, outer_type))
+         return true;
+      }
+
+  if (TREE_CODE (base) == VAR_DECL
+      && is_global_var (base))
+    {
+      if (TREE_CODE (TREE_TYPE (function)) != METHOD_TYPE
+         || (!DECL_CXX_CONSTRUCTOR_P (function)
+             || !DECL_CXX_DESTRUCTOR_P (function)))
+       {
+         if (!DECL_ABSTRACT_ORIGIN (function))
+           return false;
+         /* Watch for clones where we constant propagated the first
+            argument (pointer to the instance).  */
+         function = DECL_ABSTRACT_ORIGIN (function);
+         if (!function
+             || TREE_CODE (TREE_TYPE (function)) != METHOD_TYPE
+             || (!DECL_CXX_CONSTRUCTOR_P (function)
+                 || !DECL_CXX_DESTRUCTOR_P (function)))
+           return false;
+       }
+      /* FIXME: this can go away once we have ODR types equivalency on
+        LTO level.  */
+      if (in_lto_p && !polymorphic_type_binfo_p (TYPE_BINFO (outer_type)))
+       return true;
+      tree type = TYPE_MAIN_VARIANT (method_class_type (TREE_TYPE (function)));
+      if (types_same_for_odr (type, outer_type))
+       return true;
+    }
+  return false;
+}
+
 /* Proudce polymorphic call context for call method of instance
    that is located within BASE (that is assumed to be a decl) at OFFSET. */
 
@@ -1490,6 +1583,8 @@ get_polymorphic_call_info_from_invariant (ipa_polymorphic_call_context *context,
 
 /* Given REF call in FNDECL, determine class of the polymorphic
    call (OTR_TYPE), its token (OTR_TOKEN) and CONTEXT.
+   CALL is optional argument giving the actual statement (usually call) where
+   the context is used.
    Return pointer to object described by the context  */
 
 tree
@@ -1497,7 +1592,8 @@ get_polymorphic_call_info (tree fndecl,
                           tree ref,
                           tree *otr_type,
                           HOST_WIDE_INT *otr_token,
-                          ipa_polymorphic_call_context *context)
+                          ipa_polymorphic_call_context *context,
+                          gimple call)
 {
   tree base_pointer;
   *otr_type = obj_type_ref_class (ref);
@@ -1561,6 +1657,12 @@ get_polymorphic_call_info (tree fndecl,
                    }
                  get_polymorphic_call_info_for_decl (context, base,
                                                      context->offset + offset2);
+                 if (context->maybe_in_construction && call)
+                   context->maybe_in_construction
+                    = decl_maybe_in_construction_p (base,
+                                                    context->outer_type,
+                                                    call,
+                                                    current_function_decl);
                  return NULL;
                }
              else
index b569210..34e766d 100644 (file)
@@ -62,6 +62,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "dbgcnt.h"
 #include "domwalk.h"
 #include "builtins.h"
+#include "calls.h"
 
 /* Intermediate information that we get from alias analysis about a particular
    parameter in a particular basic_block.  When a parameter or the memory it
@@ -552,7 +553,11 @@ ipa_set_ancestor_jf (struct ipa_jump_func *jfunc, HOST_WIDE_INT offset,
 tree
 ipa_binfo_from_known_type_jfunc (struct ipa_jump_func *jfunc)
 {
+  if (!RECORD_OR_UNION_TYPE_P (jfunc->value.known_type.base_type))
+    return NULL_TREE;
+
   tree base_binfo = TYPE_BINFO (jfunc->value.known_type.base_type);
+
   if (!base_binfo)
     return NULL_TREE;
   return get_binfo_at_offset (base_binfo,
@@ -731,18 +736,84 @@ check_stmt_for_type_change (ao_ref *ao ATTRIBUTE_UNUSED, tree vdef, void *data)
     return false;
 }
 
+/* See if ARG is PARAM_DECl describing instance passed by pointer
+   or reference in FUNCTION.  Return false if the dynamic type may change
+   in between beggining of the function until CALL is invoked.
 
+   Generally functions are not allowed to change type of such instances,
+   but they call destructors.  We assume that methods can not destroy the THIS
+   pointer.  Also as a special cases, constructor and destructors may change
+   type of the THIS pointer.  */
+
+static bool
+param_type_may_change_p (tree function, tree arg, gimple call)
+{
+  /* Pure functions can not do any changes on the dynamic type;
+     that require writting to memory.  */
+  if (flags_from_decl_or_type (function) & (ECF_PURE | ECF_CONST))
+    return false;
+  /* We need to check if we are within inlined consturctor
+     or destructor (ideally we would have way to check that the
+     inline cdtor is actually working on ARG, but we don't have
+     easy tie on this, so punt on all non-pure cdtors.
+     We may also record the types of cdtors and once we know type
+     of the instance match them.
+
+     Also code unification optimizations may merge calls from
+     different blocks making return values unreliable.  So
+     do nothing during late optimization.  */
+  if (DECL_STRUCT_FUNCTION (function)->after_inlining)
+    return true;
+  if (TREE_CODE (arg) == SSA_NAME
+      && SSA_NAME_IS_DEFAULT_DEF (arg)
+      && TREE_CODE (SSA_NAME_VAR (arg)) == PARM_DECL)
+    {
+      /* Normal (non-THIS) argument.  */
+      if ((SSA_NAME_VAR (arg) != DECL_ARGUMENTS (function)
+          || TREE_CODE (TREE_TYPE (function)) != METHOD_TYPE)
+         /* THIS pointer of an method - here we we want to watch constructors
+            and destructors as those definitely may change the dynamic
+            type.  */
+         || (TREE_CODE (TREE_TYPE (function)) == METHOD_TYPE
+             && !DECL_CXX_CONSTRUCTOR_P (function)
+             && !DECL_CXX_DESTRUCTOR_P (function)
+             && (SSA_NAME_VAR (arg) == DECL_ARGUMENTS (function))))
+       {
+         /* Walk the inline stack and watch out for ctors/dtors.  */
+         for (tree block = gimple_block (call); block && TREE_CODE (block) == BLOCK;
+              block = BLOCK_SUPERCONTEXT (block))
+           if (BLOCK_ABSTRACT_ORIGIN (block)
+               && TREE_CODE (BLOCK_ABSTRACT_ORIGIN (block)) == FUNCTION_DECL)
+             {
+               tree fn = BLOCK_ABSTRACT_ORIGIN (block);
+
+               if (flags_from_decl_or_type (fn) & (ECF_PURE | ECF_CONST))
+                 continue;
+               if (TREE_CODE (TREE_TYPE (fn)) == METHOD_TYPE
+                   && (DECL_CXX_CONSTRUCTOR_P (fn)
+                       || DECL_CXX_DESTRUCTOR_P (fn)))
+                 return true;
+             }
+         return false;
+       }
+    }
+  return true;
+}
 
 /* Detect whether the dynamic type of ARG of COMP_TYPE has changed (before
    callsite CALL) by looking for assignments to its virtual table pointer.  If
    it is, return true and fill in the jump function JFUNC with relevant type
    information or set it to unknown.  ARG is the object itself (not a pointer
    to it, unless dereferenced).  BASE is the base of the memory access as
-   returned by get_ref_base_and_extent, as is the offset.  */
+   returned by get_ref_base_and_extent, as is the offset. 
+
+   This is helper function for detect_type_change and detect_type_change_ssa
+   that does the heavy work which is usually unnecesary.  */
 
 static bool
-detect_type_change (tree arg, tree base, tree comp_type, gimple call,
-                   struct ipa_jump_func *jfunc, HOST_WIDE_INT offset)
+detect_type_change_from_memory_writes (tree arg, tree base, tree comp_type,
+                                      gimple call, struct ipa_jump_func *jfunc,
+                                      HOST_WIDE_INT offset)
 {
   struct type_change_info tci;
   ao_ref ao;
@@ -753,25 +824,6 @@ detect_type_change (tree arg, tree base, tree comp_type, gimple call,
 
   comp_type = TYPE_MAIN_VARIANT (comp_type);
 
-  if (!flag_devirtualize)
-    return false;
-
-  /* C++ methods are not allowed to change THIS pointer unless they
-     are constructors or destructors.  */
-  if (TREE_CODE        (base) == MEM_REF
-      && TREE_CODE (TREE_OPERAND (base, 0)) == SSA_NAME
-      && SSA_NAME_IS_DEFAULT_DEF (TREE_OPERAND (base, 0))
-      && TREE_CODE (SSA_NAME_VAR (TREE_OPERAND (base, 0))) == PARM_DECL
-      && TREE_CODE (TREE_TYPE (current_function_decl)) == METHOD_TYPE
-      && !DECL_CXX_CONSTRUCTOR_P (current_function_decl)
-      && !DECL_CXX_DESTRUCTOR_P (current_function_decl)
-      && (SSA_NAME_VAR (TREE_OPERAND (base, 0))
-         == DECL_ARGUMENTS (current_function_decl)))
-    {
-      gcc_assert (comp_type);
-      return false;
-    }
-
   /* Const calls cannot call virtual methods through VMT and so type changes do
      not matter.  */
   if (!flag_devirtualize || !gimple_vuse (call)
@@ -809,6 +861,28 @@ detect_type_change (tree arg, tree base, tree comp_type, gimple call,
   return true;
 }
 
+/* Detect whether the dynamic type of ARG of COMP_TYPE may have changed.
+   If it is, return true and fill in the jump function JFUNC with relevant type
+   information or set it to unknown.  ARG is the object itself (not a pointer
+   to it, unless dereferenced).  BASE is the base of the memory access as
+   returned by get_ref_base_and_extent, as is the offset.  */
+
+static bool
+detect_type_change (tree arg, tree base, tree comp_type, gimple call,
+                   struct ipa_jump_func *jfunc, HOST_WIDE_INT offset)
+{
+  if (!flag_devirtualize)
+    return false;
+
+  if (TREE_CODE        (base) == MEM_REF
+      && !param_type_may_change_p (current_function_decl,
+                                  TREE_OPERAND (base, 0),
+                                  call))
+    return false;
+  return detect_type_change_from_memory_writes (arg, base, comp_type,
+                                               call, jfunc, offset);
+}
+
 /* Like detect_type_change but ARG is supposed to be a non-dereferenced pointer
    SSA name (its dereference will become the base and the offset is assumed to
    be zero).  */
@@ -822,10 +896,14 @@ detect_type_change_ssa (tree arg, tree comp_type,
       || !POINTER_TYPE_P (TREE_TYPE (arg)))
     return false;
 
+  if (!param_type_may_change_p (current_function_decl, arg, call))
+    return false;
+
   arg = build2 (MEM_REF, ptr_type_node, arg,
                build_int_cst (ptr_type_node, 0));
 
-  return detect_type_change (arg, arg, comp_type, call, jfunc, 0);
+  return detect_type_change_from_memory_writes (arg, arg, comp_type,
+                                               call, jfunc, 0);
 }
 
 /* Callback of walk_aliased_vdefs.  Flags that it has been invoked to the
@@ -1433,11 +1511,15 @@ compute_known_type_jump_func (tree op, struct ipa_jump_func *jfunc,
   if (!DECL_P (base)
       || max_size == -1
       || max_size != size
-      || !contains_polymorphic_type_p (TREE_TYPE (base))
-      || is_global_var (base))
+      || !contains_polymorphic_type_p (TREE_TYPE (base)))
     return;
 
-  if (detect_type_change (op, base, expected_type, call, jfunc, offset))
+  if (decl_maybe_in_construction_p (base, TREE_TYPE (base),
+                                   call, current_function_decl)
+      /* Even if the var seems to be in construction by inline call stack,
+        we may work out the actual type by walking memory writes.  */
+      && (!is_global_var (base)
+         && detect_type_change (op, base, expected_type, call, jfunc, offset)))
     return;
 
   ipa_set_jf_known_type (jfunc, offset, TREE_TYPE (base),
index 82aa23f..470f495 100644 (file)
@@ -87,9 +87,11 @@ bool possible_polymorphic_call_target_p (tree, HOST_WIDE_INT,
 tree method_class_type (const_tree);
 tree get_polymorphic_call_info (tree, tree, tree *,
                                HOST_WIDE_INT *,
-                               ipa_polymorphic_call_context *);
+                               ipa_polymorphic_call_context *,
+                               gimple call = NULL);
 bool get_polymorphic_call_info_from_invariant (ipa_polymorphic_call_context *,
                                               tree, tree, HOST_WIDE_INT);
+bool decl_maybe_in_construction_p (tree, tree, gimple, tree);
 tree vtable_pointer_value_to_binfo (const_tree);
 bool vtable_pointer_value_to_vtable (const_tree, tree *, unsigned HOST_WIDE_INT *);
 bool contains_polymorphic_type_p (const_tree);
@@ -125,7 +127,8 @@ possible_polymorphic_call_targets (struct cgraph_edge *e,
 /* Same as above but taking OBJ_TYPE_REF as an parameter.  */
 
 inline vec <cgraph_node *>
-possible_polymorphic_call_targets (tree call,
+possible_polymorphic_call_targets (tree ref,
+                                  gimple call,
                                   bool *final = NULL,
                                   void **cache_token = NULL)
 {
@@ -134,11 +137,11 @@ possible_polymorphic_call_targets (tree call,
   ipa_polymorphic_call_context context;
 
   get_polymorphic_call_info (current_function_decl,
-                            call,
-                            &otr_type, &otr_token, &context);
-  return possible_polymorphic_call_targets (obj_type_ref_class (call),
+                            ref,
+                            &otr_type, &otr_token, &context, call);
+  return possible_polymorphic_call_targets (obj_type_ref_class (ref),
                                            tree_to_uhwi
-                                             (OBJ_TYPE_REF_TOKEN (call)),
+                                             (OBJ_TYPE_REF_TOKEN (ref)),
                                            context,
                                            final, cache_token);
 }
index 93f9d35..c24f301 100644 (file)
@@ -1,3 +1,9 @@
+2014-07-05  Jan Hubicka   <hubicka@ucw.cz>
+
+       * g++.dg/ipa/devirt-26.C: Update testcase.
+       * g++.dg/ipa/imm-devirt-1.C: Update testcase.
+       * g++.dg/ipa/imm-devirt-2.C: Update testcase.
+
 2014-07-04  Tobias Burnus  <burnus@net-b.de>
 
        * gfortran.dg/coarray/coindexed_3.f90: New.
index 469a140..1787fee 100644 (file)
@@ -1,5 +1,5 @@
 /* { dg-do compile } */
-/* { dg-options "-O3 -fdump-ipa-devirt-details"  } */
+/* { dg-options "-O3 -fdump-tree-ccp1"  } */
 struct A
  {
    int a;
@@ -23,7 +23,6 @@ int test(void)
   return d->foo()+b->foo();
 }
 /* The call to b->foo() is perfectly devirtualizable because C can not be in construction
-   when &c was used, but we can not analyze that so far.  Test that we at least speculate
-   that type is in the construction.  */
-/* { dg-final { scan-ipa-dump "speculatively devirtualizing" "devirt"  } } */
-/* { dg-final { cleanup-ipa-dump "devirt" } } */
+   when &c was used.  */
+/* { dg-final { scan-tree-dump-not "OBJ_TYPE_REF" "ccp1"  } } */
+/* { dg-final { cleanup-tree-dump "ccp1" } } */
index 9307d96..115277f 100644 (file)
@@ -1,7 +1,7 @@
 /* Verify that virtual calls are folded even early inlining puts them into one
    function with the definition.  */
 /* { dg-do run } */
-/* { dg-options "-O2 -fdump-tree-fre1-details"  } */
+/* { dg-options "-O2 -fdump-tree-einline"  } */
 
 extern "C" void abort (void);
 
@@ -58,5 +58,10 @@ int main (int argc, char *argv[])
   return 0;
 }
 
-/* { dg-final { scan-tree-dump "converting indirect call to function virtual int B::foo" "fre1"  } } */
-/* { dg-final { cleanup-tree-dump "fre1" } } */
+/* middleman_2 gets early inlined and the virtual call should get turned to
+   a direct call.  */
+/* { dg-final { scan-tree-dump "Inlining int middleman_1" "einline"  } } */
+/* { dg-final { scan-tree-dump "Inlining int middleman_2" "einline"  } } */
+/* { dg-final { scan-tree-dump "B::foo (" "einline"  } } */
+/* { dg-final { scan-tree-dump-times "OBJ_TYPE_REF" 2 "einline"  } } */
+/* { dg-final { cleanup-tree-dump "einline" } } */
index 079aa4b..58af089 100644 (file)
@@ -1,7 +1,7 @@
 /* Verify that virtual calls are folded even early inlining puts them into one
    function with the definition.  */
 /* { dg-do run } */
-/* { dg-options "-O2 -fdump-tree-fre1-details"  } */
+/* { dg-options "-O2 -fdump-tree-einline"  } */
 
 extern "C" void abort (void);
 
@@ -91,5 +91,6 @@ int main (int argc, char *argv[])
   return 0;
 }
 
-/* { dg-final { scan-tree-dump "converting indirect call to function" "fre1"  } } */
-/* { dg-final { cleanup-tree-dump "fre1" } } */
+/* We fold into thunk of C. Eventually we should inline the thunk.  */
+/* { dg-final { scan-tree-dump "C::_ZThn24_N1C3fooEi (" "einline"  } } */
+/* { dg-final { cleanup-tree-dump "einline" } } */