/* Perform optimizations on tree structure.
Copyright (C) 1998-2024 Free Software Foundation, Inc.
Written by Mark Michell (mark@codesourcery.com).
This file is part of GCC.
GCC is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GCC is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "target.h"
#include "cp-tree.h"
#include "stringpool.h"
#include "cgraph.h"
#include "debug.h"
#include "tree-inline.h"
#include "tree-iterator.h"
#include "attribs.h"
/* Prototypes. */
static void update_cloned_parm (tree, tree, bool);
/* CLONED_PARM is a copy of CLONE, generated for a cloned constructor
or destructor. Update it to ensure that the source-position for
the cloned parameter matches that for the original, and that the
debugging generation code will be able to find the original PARM. */
static void
update_cloned_parm (tree parm, tree cloned_parm, bool first)
{
DECL_ABSTRACT_ORIGIN (cloned_parm) = parm;
/* We may have taken its address. */
TREE_ADDRESSABLE (cloned_parm) = TREE_ADDRESSABLE (parm);
DECL_BY_REFERENCE (cloned_parm) = DECL_BY_REFERENCE (parm);
/* The definition might have different constness. */
TREE_READONLY (cloned_parm) = TREE_READONLY (parm);
TREE_USED (cloned_parm) = !first || TREE_USED (parm);
/* The name may have changed from the declaration. */
DECL_NAME (cloned_parm) = DECL_NAME (parm);
DECL_SOURCE_LOCATION (cloned_parm) = DECL_SOURCE_LOCATION (parm);
TREE_TYPE (cloned_parm) = TREE_TYPE (parm);
DECL_NOT_GIMPLE_REG_P (cloned_parm) = DECL_NOT_GIMPLE_REG_P (parm);
}
/* Like copy_decl_no_change, but handle DECL_OMP_PRIVATIZED_MEMBER
properly. */
static tree
cxx_copy_decl (tree decl, copy_body_data *id)
{
tree copy = copy_decl_no_change (decl, id);
if (VAR_P (decl)
&& DECL_HAS_VALUE_EXPR_P (decl)
&& DECL_ARTIFICIAL (decl)
&& DECL_LANG_SPECIFIC (decl)
&& DECL_OMP_PRIVATIZED_MEMBER (decl))
{
tree expr = DECL_VALUE_EXPR (copy);
walk_tree (&expr, copy_tree_body_r, id, NULL);
SET_DECL_VALUE_EXPR (copy, expr);
}
return copy;
}
/* FN is a function in High GIMPLE form that has a complete body and no
CFG. CLONE is a function whose body is to be set to a copy of FN,
mapping argument declarations according to the ARG_MAP splay_tree. */
static void
clone_body (tree clone, tree fn, void *arg_map)
{
copy_body_data id;
tree stmts;
/* Clone the body, as if we were making an inline call. But, remap
the parameters in the callee to the parameters of caller. */
memset (&id, 0, sizeof (id));
id.src_fn = fn;
id.dst_fn = clone;
id.src_cfun = DECL_STRUCT_FUNCTION (fn);
id.decl_map = static_cast *> (arg_map);
id.copy_decl = cxx_copy_decl;
id.transform_call_graph_edges = CB_CGE_DUPLICATE;
id.transform_new_cfg = true;
id.transform_return_to_modify = false;
/* We're not inside any EH region. */
id.eh_lp_nr = 0;
stmts = DECL_SAVED_TREE (fn);
walk_tree (&stmts, copy_tree_body_r, &id, NULL);
/* Also remap the initializer of any static variables so that they (in
particular, any label addresses) correspond to the base variant rather
than the abstract one. */
if (DECL_NAME (clone) == base_dtor_identifier
|| DECL_NAME (clone) == base_ctor_identifier)
{
unsigned ix;
tree decl;
FOR_EACH_LOCAL_DECL (DECL_STRUCT_FUNCTION (fn), ix, decl)
walk_tree (&DECL_INITIAL (decl), copy_tree_body_r, &id, NULL);
}
append_to_statement_list_force (stmts, &DECL_SAVED_TREE (clone));
}
/* DELETE_DTOR is a delete destructor whose body will be built.
COMPLETE_DTOR is the corresponding complete destructor. */
static void
build_delete_destructor_body (tree delete_dtor, tree complete_dtor)
{
tree parm = DECL_ARGUMENTS (delete_dtor);
tree virtual_size = cxx_sizeof (current_class_type);
/* Call the delete function. */
tree call_delete = build_op_delete_call (DELETE_EXPR, current_class_ptr,
virtual_size,
/*global_p=*/false,
/*placement=*/NULL_TREE,
/*alloc_fn=*/NULL_TREE,
tf_warning_or_error);
tree op = get_callee_fndecl (call_delete);
if (op && DECL_P (op) && destroying_delete_p (op))
{
/* The destroying delete will handle calling complete_dtor. */
add_stmt (call_delete);
}
else
{
/* Call the corresponding complete destructor. */
gcc_assert (complete_dtor);
tree call_dtor = build_cxx_call (complete_dtor, 1, &parm,
tf_warning_or_error);
/* Operator delete must be called, whether or not the dtor throws. */
add_stmt (build2 (TRY_FINALLY_EXPR, void_type_node,
call_dtor, call_delete));
}
/* Return the address of the object.
??? How is it useful to return an invalid address? */
maybe_return_this ();
}
/* Return name of comdat group for complete and base ctor (or dtor)
that have the same body. If dtor is virtual, deleting dtor goes
into this comdat group as well. */
static tree
cdtor_comdat_group (tree complete, tree base)
{
tree complete_name = DECL_ASSEMBLER_NAME (complete);
tree base_name = DECL_ASSEMBLER_NAME (base);
char *grp_name;
const char *p, *q;
bool diff_seen = false;
size_t idx;
gcc_assert (IDENTIFIER_LENGTH (complete_name)
== IDENTIFIER_LENGTH (base_name));
grp_name = XALLOCAVEC (char, IDENTIFIER_LENGTH (complete_name) + 1);
p = IDENTIFIER_POINTER (complete_name);
q = IDENTIFIER_POINTER (base_name);
for (idx = 0; idx < IDENTIFIER_LENGTH (complete_name); idx++)
if (p[idx] == q[idx])
grp_name[idx] = p[idx];
else
{
gcc_assert (!diff_seen
&& idx > 0
&& (p[idx - 1] == 'C' || p[idx - 1] == 'D'
|| p[idx - 1] == 'I')
&& p[idx] == '1'
&& q[idx] == '2');
grp_name[idx] = '5';
diff_seen = true;
}
grp_name[idx] = '\0';
gcc_assert (diff_seen);
return get_identifier (grp_name);
}
/* Returns true iff we can make the base and complete [cd]tor aliases of
the same symbol rather than separate functions. */
static bool
can_alias_cdtor (tree fn)
{
/* If aliases aren't supported by the assembler, fail. */
if (!TARGET_SUPPORTS_ALIASES)
return false;
/* We can't use an alias if there are virtual bases. */
if (CLASSTYPE_VBASECLASSES (DECL_CONTEXT (fn)))
return false;
gcc_assert (DECL_MAYBE_IN_CHARGE_CDTOR_P (fn));
/* Don't use aliases for weak/linkonce definitions unless we can put both
symbols in the same COMDAT group. */
return (DECL_INTERFACE_KNOWN (fn)
&& (SUPPORTS_ONE_ONLY || !DECL_WEAK (fn))
&& (!DECL_ONE_ONLY (fn)
|| (HAVE_COMDAT_GROUP && DECL_WEAK (fn))));
}
/* FN is a [cd]tor, fns is a pointer to an array of length 3. Fill fns
with pointers to the base, complete, and deleting variants. */
static void
populate_clone_array (tree fn, tree *fns)
{
tree clone;
fns[0] = NULL_TREE;
fns[1] = NULL_TREE;
fns[2] = NULL_TREE;
FOR_EACH_CLONE (clone, fn)
if (DECL_NAME (clone) == complete_dtor_identifier
|| DECL_NAME (clone) == complete_ctor_identifier)
fns[1] = clone;
else if (DECL_NAME (clone) == base_dtor_identifier
|| DECL_NAME (clone) == base_ctor_identifier)
fns[0] = clone;
else if (DECL_NAME (clone) == deleting_dtor_identifier)
fns[2] = clone;
else
gcc_unreachable ();
}
/* FN is a constructor or destructor, and there are FUNCTION_DECLs
cloned from it nearby. Instead of cloning this body, leave it
alone and create tiny one-call bodies for the cloned
FUNCTION_DECLs. These clones are sibcall candidates, and their
resulting code will be very thunk-esque. */
static bool
maybe_thunk_body (tree fn, bool force)
{
tree bind, block, call, clone, clone_result, fn_parm, fn_parm_typelist;
tree last_arg, modify, *args;
int parmno, vtt_parmno, max_parms;
tree fns[3];
if (!force && !flag_declone_ctor_dtor)
return 0;
/* If function accepts variable arguments, give up. */
last_arg = tree_last (TYPE_ARG_TYPES (TREE_TYPE (fn)));
if (last_arg != void_list_node)
return 0;
/* If we got this far, we've decided to turn the clones into thunks. */
/* We're going to generate code for fn, so it is no longer "abstract."
Also make the unified ctor/dtor private to either the translation unit
(for non-vague linkage ctors) or the COMDAT group (otherwise). */
populate_clone_array (fn, fns);
/* Can happen during error recovery (c++/71464). */
if (!fns[0] || !fns[1])
return 0;
/* Don't use thunks if the base clone omits inherited parameters. */
if (ctor_omit_inherited_parms (fns[0]))
return 0;
DECL_ABSTRACT_P (fn) = false;
if (!DECL_WEAK (fn))
{
TREE_PUBLIC (fn) = false;
DECL_EXTERNAL (fn) = false;
DECL_INTERFACE_KNOWN (fn) = true;
}
else if (HAVE_COMDAT_GROUP)
{
/* At eof, defer creation of mangling aliases temporarily. */
bool save_defer_mangling_aliases = defer_mangling_aliases;
defer_mangling_aliases = true;
tree comdat_group = cdtor_comdat_group (fns[1], fns[0]);
defer_mangling_aliases = save_defer_mangling_aliases;
cgraph_node::get_create (fns[0])->set_comdat_group (comdat_group);
cgraph_node::get_create (fns[1])->add_to_same_comdat_group
(cgraph_node::get_create (fns[0]));
symtab_node::get (fn)->add_to_same_comdat_group
(symtab_node::get (fns[0]));
if (fns[2])
/* If *[CD][12]* dtors go into the *[CD]5* comdat group and dtor is
virtual, it goes into the same comdat group as well. */
cgraph_node::get_create (fns[2])->add_to_same_comdat_group
(symtab_node::get (fns[0]));
/* Emit them now that the thunks are same comdat group aliases. */
if (!save_defer_mangling_aliases)
generate_mangling_aliases ();
TREE_PUBLIC (fn) = false;
DECL_EXTERNAL (fn) = false;
DECL_INTERFACE_KNOWN (fn) = true;
/* function_and_variable_visibility doesn't want !PUBLIC decls to
have these flags set. */
DECL_WEAK (fn) = false;
DECL_COMDAT (fn) = false;
}
/* Find the vtt_parm, if present. */
for (vtt_parmno = -1, parmno = 0, fn_parm = DECL_ARGUMENTS (fn);
fn_parm;
++parmno, fn_parm = TREE_CHAIN (fn_parm))
{
if (DECL_ARTIFICIAL (fn_parm)
&& DECL_NAME (fn_parm) == vtt_parm_identifier)
{
/* Compensate for removed in_charge parameter. */
vtt_parmno = parmno;
break;
}
}
/* Allocate an argument buffer for build_cxx_call().
Make sure it is large enough for any of the clones. */
max_parms = 0;
FOR_EACH_CLONE (clone, fn)
{
int length = list_length (DECL_ARGUMENTS (fn));
if (length > max_parms)
max_parms = length;
}
args = XALLOCAVEC (tree, max_parms);
/* We know that any clones immediately follow FN in TYPE_FIELDS. */
FOR_EACH_CLONE (clone, fn)
{
tree clone_parm;
/* If we've already generated a body for this clone, avoid
duplicating it. (Is it possible for a clone-list to grow after we
first see it?) */
if (DECL_SAVED_TREE (clone) || TREE_ASM_WRITTEN (clone))
continue;
/* Start processing the function. */
start_preparsed_function (clone, NULL_TREE, SF_PRE_PARSED);
if (clone == fns[2])
{
for (clone_parm = DECL_ARGUMENTS (clone); clone_parm;
clone_parm = TREE_CHAIN (clone_parm))
DECL_ABSTRACT_ORIGIN (clone_parm) = NULL_TREE;
/* Build the delete destructor by calling complete destructor and
delete function. */
build_delete_destructor_body (clone, fns[1]);
}
else
{
/* Walk parameter lists together, creating parameter list for
call to original function. */
for (parmno = 0,
fn_parm = DECL_ARGUMENTS (fn),
fn_parm_typelist = TYPE_ARG_TYPES (TREE_TYPE (fn)),
clone_parm = DECL_ARGUMENTS (clone);
fn_parm;
++parmno,
fn_parm = TREE_CHAIN (fn_parm))
{
if (parmno == vtt_parmno && ! DECL_HAS_VTT_PARM_P (clone))
{
gcc_assert (fn_parm_typelist);
/* Clobber argument with formal parameter type. */
args[parmno]
= convert (TREE_VALUE (fn_parm_typelist),
null_pointer_node);
}
else if (parmno == 1 && DECL_HAS_IN_CHARGE_PARM_P (fn))
{
tree in_charge
= copy_node (in_charge_arg_for_name (DECL_NAME (clone)));
args[parmno] = in_charge;
}
/* Map other parameters to their equivalents in the cloned
function. */
else
{
gcc_assert (clone_parm);
DECL_ABSTRACT_ORIGIN (clone_parm) = NULL;
args[parmno] = clone_parm;
/* Clear TREE_ADDRESSABLE on thunk arguments. */
TREE_ADDRESSABLE (clone_parm) = 0;
clone_parm = TREE_CHAIN (clone_parm);
}
if (fn_parm_typelist)
fn_parm_typelist = TREE_CHAIN (fn_parm_typelist);
}
/* We built this list backwards; fix now. */
mark_used (fn);
call = build_cxx_call (fn, parmno, args, tf_warning_or_error);
/* Arguments passed to the thunk by invisible reference should
be transmitted to the callee unchanged. Do not create a
temporary and invoke the copy constructor. The thunking
transformation must not introduce any constructor calls. */
CALL_FROM_THUNK_P (call) = 1;
block = make_node (BLOCK);
if (targetm.cxx.cdtor_returns_this ())
{
clone_result = DECL_RESULT (clone);
modify = build2 (MODIFY_EXPR, TREE_TYPE (clone_result),
clone_result, call);
modify = build1 (RETURN_EXPR, void_type_node, modify);
add_stmt (modify);
}
else
{
add_stmt (call);
}
bind = c_build_bind_expr (DECL_SOURCE_LOCATION (clone),
block, cur_stmt_list);
DECL_SAVED_TREE (clone) = push_stmt_list ();
add_stmt (bind);
}
DECL_ABSTRACT_ORIGIN (clone) = NULL;
expand_or_defer_fn (finish_function (/*inline_p=*/false));
}
return 1;
}
/* Copy most attributes from ATTRS, omitting attributes that can really only
apply to a single decl. */
tree
clone_attrs (tree attrs)
{
tree new_attrs = NULL_TREE;
tree *p = &new_attrs;
for (tree a = attrs; a; a = TREE_CHAIN (a))
{
tree aname = get_attribute_name (a);
if (is_attribute_namespace_p ("", a)
&& (is_attribute_p ("alias", aname)
|| is_attribute_p ("ifunc", aname)))
continue;
*p = copy_node (a);
p = &TREE_CHAIN (*p);
}
*p = NULL_TREE;
return new_attrs;
}
/* FN is a function that has a complete body. Clone the body as
necessary. Returns nonzero if there's no longer any need to
process the main body. */
bool
maybe_clone_body (tree fn)
{
tree comdat_group = NULL_TREE;
tree clone;
tree fns[3];
bool first = true;
int idx;
bool need_alias = false;
/* We only clone constructors and destructors. */
if (!DECL_MAYBE_IN_CHARGE_CDTOR_P (fn))
return 0;
populate_clone_array (fn, fns);
/* Remember if we can't have multiple clones for some reason. We need to
check this before we remap local static initializers in clone_body. */
if (!tree_versionable_function_p (fn))
need_alias = true;
/* We know that any clones immediately follow FN in the TYPE_FIELDS
list. */
push_to_top_level ();
for (idx = 0; idx < 3; idx++)
{
tree parm;
tree clone_parm;
clone = fns[idx];
if (!clone)
continue;
/* Update CLONE's source position information to match FN's. */
DECL_SOURCE_LOCATION (clone) = DECL_SOURCE_LOCATION (fn);
DECL_DECLARED_INLINE_P (clone) = DECL_DECLARED_INLINE_P (fn);
DECL_DECLARED_CONSTEXPR_P (clone) = DECL_DECLARED_CONSTEXPR_P (fn);
DECL_COMDAT (clone) = DECL_COMDAT (fn);
DECL_WEAK (clone) = DECL_WEAK (fn);
/* We don't copy the comdat group from fn to clone because the assembler
name of fn was corrupted by write_mangled_name by adding *INTERNAL*
to it. By doing so, it also corrupted the comdat group. */
if (DECL_ONE_ONLY (fn))
cgraph_node::get_create (clone)->set_comdat_group (cxx_comdat_group (clone));
DECL_USE_TEMPLATE (clone) = DECL_USE_TEMPLATE (fn);
DECL_EXTERNAL (clone) = DECL_EXTERNAL (fn);
DECL_INTERFACE_KNOWN (clone) = DECL_INTERFACE_KNOWN (fn);
DECL_NOT_REALLY_EXTERN (clone) = DECL_NOT_REALLY_EXTERN (fn);
TREE_PUBLIC (clone) = TREE_PUBLIC (fn);
DECL_VISIBILITY (clone) = DECL_VISIBILITY (fn);
DECL_VISIBILITY_SPECIFIED (clone) = DECL_VISIBILITY_SPECIFIED (fn);
DECL_DLLIMPORT_P (clone) = DECL_DLLIMPORT_P (fn);
DECL_ATTRIBUTES (clone) = clone_attrs (DECL_ATTRIBUTES (fn));
DECL_DISREGARD_INLINE_LIMITS (clone) = DECL_DISREGARD_INLINE_LIMITS (fn);
set_decl_section_name (clone, fn);
/* Adjust the parameter names and locations. */
parm = DECL_ARGUMENTS (fn);
clone_parm = DECL_ARGUMENTS (clone);
/* Update the `this' parameter, which is always first. */
update_cloned_parm (parm, clone_parm, first);
parm = DECL_CHAIN (parm);
clone_parm = DECL_CHAIN (clone_parm);
if (DECL_HAS_IN_CHARGE_PARM_P (fn))
parm = DECL_CHAIN (parm);
if (DECL_HAS_VTT_PARM_P (fn))
parm = DECL_CHAIN (parm);
if (DECL_HAS_VTT_PARM_P (clone))
clone_parm = DECL_CHAIN (clone_parm);
for (; parm && clone_parm;
parm = DECL_CHAIN (parm), clone_parm = DECL_CHAIN (clone_parm))
/* Update this parameter. */
update_cloned_parm (parm, clone_parm, first);
}
bool can_alias = can_alias_cdtor (fn);
/* If we decide to turn clones into thunks, they will branch to fn.
Must have original function available to call. */
if (!can_alias && maybe_thunk_body (fn, need_alias))
{
pop_from_top_level ();
/* We still need to emit the original function. */
return 0;
}
/* Emit the DWARF1 abstract instance. */
(*debug_hooks->deferred_inline_function) (fn);
/* We know that any clones immediately follow FN in the TYPE_FIELDS. */
for (idx = 0; idx < 3; idx++)
{
tree parm;
tree clone_parm;
int parmno;
hash_map *decl_map;
bool alias = false;
clone = fns[idx];
if (!clone)
continue;
/* Start processing the function. */
start_preparsed_function (clone, NULL_TREE, SF_PRE_PARSED);
/* Tell cgraph if both ctors or both dtors are known to have
the same body. */
if (can_alias
&& fns[0]
&& idx == 1
&& cgraph_node::get_create (fns[0])->create_same_body_alias
(clone, fns[0]))
{
alias = true;
if (DECL_ONE_ONLY (fns[0]))
{
/* For comdat base and complete cdtors put them
into the same, *[CD]5* comdat group instead of
*[CD][12]*. */
comdat_group = cdtor_comdat_group (fns[1], fns[0]);
cgraph_node::get_create (fns[0])->set_comdat_group (comdat_group);
if (symtab_node::get (clone)->same_comdat_group)
symtab_node::get (clone)->remove_from_same_comdat_group ();
symtab_node::get (clone)->add_to_same_comdat_group
(symtab_node::get (fns[0]));
}
}
/* Build the delete destructor by calling complete destructor
and delete function. */
if (idx == 2)
{
build_delete_destructor_body (clone, fns[1]);
/* If *[CD][12]* dtors go into the *[CD]5* comdat group and dtor is
virtual, it goes into the same comdat group as well. */
if (comdat_group)
cgraph_node::get_create (clone)->add_to_same_comdat_group
(symtab_node::get (fns[0]));
}
else if (alias)
/* No need to populate body. */ ;
else
{
/* If we can't have multiple copies of FN (say, because there's a
static local initialized with the address of a label), we need
to use an alias for the complete variant. */
if (idx == 1 && need_alias)
{
if (DECL_STRUCT_FUNCTION (fn)->cannot_be_copied_set)
sorry (DECL_STRUCT_FUNCTION (fn)->cannot_be_copied_reason, fn);
else
sorry ("making multiple clones of %qD", fn);
}
/* Remap the parameters. */
decl_map = new hash_map;
for (parmno = 0,
parm = DECL_ARGUMENTS (fn),
clone_parm = DECL_ARGUMENTS (clone);
parm;
++parmno,
parm = DECL_CHAIN (parm))
{
/* Map the in-charge parameter to an appropriate constant. */
if (DECL_HAS_IN_CHARGE_PARM_P (fn) && parmno == 1)
{
tree in_charge;
in_charge = in_charge_arg_for_name (DECL_NAME (clone));
decl_map->put (parm, in_charge);
}
else if (DECL_ARTIFICIAL (parm)
&& DECL_NAME (parm) == vtt_parm_identifier)
{
/* For a subobject constructor or destructor, the next
argument is the VTT parameter. Remap the VTT_PARM
from the CLONE to this parameter. */
if (DECL_HAS_VTT_PARM_P (clone))
{
DECL_ABSTRACT_ORIGIN (clone_parm) = parm;
decl_map->put (parm, clone_parm);
clone_parm = DECL_CHAIN (clone_parm);
}
/* Otherwise, map the VTT parameter to `NULL'. */
else
{
tree t
= fold_convert (TREE_TYPE (parm), null_pointer_node);
decl_map->put (parm, t);
}
}
/* Map other parameters to their equivalents in the cloned
function. */
else
{
tree replacement;
if (clone_parm)
{
replacement = clone_parm;
clone_parm = DECL_CHAIN (clone_parm);
}
else
{
/* Inheriting ctors can omit parameters from the base
clone. Replace them with null lvalues. */
tree reftype = build_reference_type (TREE_TYPE (parm));
replacement = fold_convert (reftype, null_pointer_node);
replacement = convert_from_reference (replacement);
}
decl_map->put (parm, replacement);
}
}
if (targetm.cxx.cdtor_returns_this ())
{
parm = DECL_RESULT (fn);
clone_parm = DECL_RESULT (clone);
decl_map->put (parm, clone_parm);
}
/* Clone the body. */
clone_body (clone, fn, decl_map);
/* Clean up. */
delete decl_map;
}
/* The clone can throw iff the original function can throw. */
cp_function_chain->can_throw = !TREE_NOTHROW (fn);
/* Now, expand this function into RTL, if appropriate. */
finish_function (/*inline_p=*/false);
BLOCK_ABSTRACT_ORIGIN (DECL_INITIAL (clone)) = DECL_INITIAL (fn);
if (alias)
{
if (expand_or_defer_fn_1 (clone))
emit_associated_thunks (clone);
/* We didn't generate a body, so remove the empty one. */
DECL_SAVED_TREE (clone) = NULL_TREE;
}
else
expand_or_defer_fn (clone);
first = false;
}
pop_from_top_level ();
/* We don't need to process the original function any further. */
return 1;
}
/* If maybe_clone_body is called while the cdtor is still tentative,
DECL_ONE_ONLY will be false and so will be can_alias_cdtor (fn).
In that case we wouldn't try to optimize using an alias and instead
would emit separate base and complete cdtor. The following function
attempts to still optimize that case when we import_export_decl
is called first time on one of the clones. */
void
maybe_optimize_cdtor (tree orig_decl)
{
tree fns[3];
tree fn = DECL_CLONED_FUNCTION (orig_decl);
gcc_checking_assert (DECL_MAYBE_IN_CHARGE_CDTOR_P (fn));
if (DECL_INTERFACE_KNOWN (fn)
|| !TREE_ASM_WRITTEN (fn)
|| !DECL_ONE_ONLY (orig_decl)
|| symtab->global_info_ready)
return;
populate_clone_array (fn, fns);
if (!fns[0] || !fns[1])
return;
for (int i = 2 - !fns[2]; i >= 0; --i)
if (fns[i] != orig_decl && DECL_INTERFACE_KNOWN (fns[i]))
return;
DECL_INTERFACE_KNOWN (fn) = 1;
comdat_linkage (fn);
if (!can_alias_cdtor (fn))
return;
/* For comdat base and complete cdtors put them into the same,
*[CD]5* comdat group instead of *[CD][12]*. */
auto n0 = cgraph_node::get_create (fns[0]);
auto n1 = cgraph_node::get_create (fns[1]);
auto n2 = fns[2] ? cgraph_node::get_create (fns[1]) : NULL;
if (n0->lowered || n1->lowered || (n2 && n2->lowered))
return;
import_export_decl (fns[0]);
n1->definition = false;
if (!n0->create_same_body_alias (fns[1], fns[0]))
return;
tree comdat_group = cdtor_comdat_group (fns[1], fns[0]);
n1 = cgraph_node::get (fns[1]);
n0->set_comdat_group (comdat_group);
if (n1->same_comdat_group)
n1->remove_from_same_comdat_group ();
n1->add_to_same_comdat_group (n0);
if (fns[2])
n2->add_to_same_comdat_group (n0);
import_export_decl (fns[1]);
/* Remove the body now that it is an alias. */
release_function_body (fns[1]);
}