This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
Re: RFA (cgraph): C++ 'structor decloning patch, Mark III
- From: Jason Merrill <jason at redhat dot com>
- To: "Joseph S. Myers" <joseph at codesourcery dot com>
- Cc: Jan Hubicka <jh at suse dot cz>, gcc-patches List <gcc-patches at gcc dot gnu dot org>, Bill Maddox <maddox at google dot com>, Stuart Hastings <stuart at apple dot com>
- Date: Thu, 21 Nov 2013 23:08:05 -0500
- Subject: Re: RFA (cgraph): C++ 'structor decloning patch, Mark III
- Authentication-results: sourceware.org; auth=none
- References: <528E45BB dot 6030100 at redhat dot com> <Pine dot LNX dot 4 dot 64 dot 1311211821360 dot 19332 at digraph dot polyomino dot org dot uk>
On 11/21/2013 01:24 PM, Joseph S. Myers wrote:
The new option should be able to use Var() in the .opt file rather than
having a variable defined explicitly or any explicit handling code in
c_common_handle_option, and shouldn't need to use UInteger (given the
option has no arguments).
Good point. Here's an updated patch:
commit ee912895052001f21ad8b9cf5a271091605e5ae7
Author: Jason Merrill <jason@redhat.com>
Date: Thu Jan 12 14:04:42 2012 -0500
PR c++/41090
Add -fdeclone-ctor-dtor.
gcc/cp/
* optimize.c (can_alias_cdtor, populate_clone_array): Split out
from maybe_clone_body.
(maybe_thunk_body): New function.
(maybe_clone_body): Call it.
* mangle.c (write_mangled_name): Remove code to suppress
writing of mangled name for cloned constructor or destructor.
(write_special_name_constructor): Handle decloned constructor.
(write_special_name_destructor): Handle decloned destructor.
* method.c (trivial_fn_p): Handle decloning.
* semantics.c (expand_or_defer_fn_1): Clone after setting linkage.
gcc/c-family/
* c.opt: Add -fdeclone-ctor-dtor.
* c-opts.c (c_common_post_options): Default to on iff -Os.
gcc/
* cgraph.h (comdat_local_p): New.
* cgraph.c (verify_cgraph_node): Make sure we don't call a
comdat-local function from outside its comdat.
* gimple-fold.c (can_refer_decl_in_current_unit_p): Check
references to comdat-local symbols.
* ipa-cp.c (decide_about_value): Don't clone comdat-local fns.
* ipa-inline.c (calls_comdat_local_p): New.
(can_inline_edge_p): Check it.
* ipa.c (symtab_remove_unreachable_nodes): Ignore comdat-local
symbols when marking everything in the group as reachable.
(function_and_variable_visibility): Handle comdat-local fns.
include/
* demangle.h (enum gnu_v3_ctor_kinds):
Added literal gnu_v3_unified_ctor.
(enum gnu_v3_ctor_kinds):
Added literal gnu_v3_unified_dtor.
libiberty/
* cp-demangle.c (cplus_demangle_fill_ctor,cplus_demangle_fill_dtor):
Handle unified ctor/dtor.
(d_ctor_dtor_name): Handle unified ctor/dtor.
diff --git a/gcc/c-family/c-opts.c b/gcc/c-family/c-opts.c
index f368cab..3576f7d 100644
--- a/gcc/c-family/c-opts.c
+++ b/gcc/c-family/c-opts.c
@@ -899,6 +899,10 @@ c_common_post_options (const char **pfilename)
if (warn_implicit_function_declaration == -1)
warn_implicit_function_declaration = flag_isoc99;
+ /* Declone C++ 'structors if -Os. */
+ if (flag_declone_ctor_dtor == -1)
+ flag_declone_ctor_dtor = optimize_size;
+
if (cxx_dialect >= cxx11)
{
/* If we're allowing C++0x constructs, don't warn about C++98
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index ac67885..76ba2d1 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -891,6 +891,10 @@ fdeduce-init-list
C++ ObjC++ Var(flag_deduce_init_list) Init(0)
-fdeduce-init-list enable deduction of std::initializer_list for a template type parameter from a brace-enclosed initializer-list
+fdeclone-ctor-dtor
+C++ ObjC++ Var(flag_declone_ctor_dtor) Init(-1)
+Factor complex constructors and destructors to favor space over speed
+
fdefault-inline
C++ ObjC++ Ignore
Does nothing. Preserved for backward compatibility.
diff --git a/gcc/cgraph.c b/gcc/cgraph.c
index 009a165..0854b95 100644
--- a/gcc/cgraph.c
+++ b/gcc/cgraph.c
@@ -2664,10 +2664,20 @@ verify_cgraph_node (struct cgraph_node *node)
error_found = true;
}
}
+ tree required_group = NULL_TREE;
+ if (comdat_local_p (node))
+ required_group = DECL_COMDAT_GROUP (node->decl);
for (e = node->callers; e; e = e->next_caller)
{
if (verify_edge_count_and_frequency (e))
error_found = true;
+ if (required_group
+ && DECL_COMDAT_GROUP (e->caller->decl) != required_group)
+ {
+ error ("comdat-local function called by %s outside its comdat",
+ identifier_to_locale (e->caller->name ()));
+ error_found = true;
+ }
if (!e->inline_failed)
{
if (node->global.inlined_to
diff --git a/gcc/cgraph.h b/gcc/cgraph.h
index 15719fb..b791811 100644
--- a/gcc/cgraph.h
+++ b/gcc/cgraph.h
@@ -1390,4 +1390,14 @@ symtab_can_be_discarded (symtab_node *node)
&& node->resolution != LDPR_PREVAILING_DEF_IRONLY
&& node->resolution != LDPR_PREVAILING_DEF_IRONLY_EXP));
}
+
+/* Return true if NODE is local to a particular COMDAT group, and must not
+ be named from outside the COMDAT. This is used for C++ decloned
+ constructors. */
+
+static inline bool
+comdat_local_p (symtab_node *node)
+{
+ return (node->same_comdat_group && !TREE_PUBLIC (node->decl));
+}
#endif /* GCC_CGRAPH_H */
diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
index 500c81f..cc1f8c3 100644
--- a/gcc/cp/decl.c
+++ b/gcc/cp/decl.c
@@ -10154,7 +10154,9 @@ grokdeclarator (const cp_declarator *declarator,
/* The TYPE_DECL is "abstract" because there will be
clones of this constructor/destructor, and there will
be copies of this TYPE_DECL generated in those
- clones. */
+ clones. The decloning optimization (for space) may
+ revert this subsequently if it determines that
+ the clones should share a common implementation. */
DECL_ABSTRACT (decl) = 1;
}
else if (current_class_type
diff --git a/gcc/cp/mangle.c b/gcc/cp/mangle.c
index 8a24d6c..d99062d 100644
--- a/gcc/cp/mangle.c
+++ b/gcc/cp/mangle.c
@@ -689,13 +689,6 @@ write_mangled_name (const tree decl, bool top_level)
mangled_name:;
write_string ("_Z");
write_encoding (decl);
- if (DECL_LANG_SPECIFIC (decl)
- && (DECL_MAYBE_IN_CHARGE_DESTRUCTOR_P (decl)
- || DECL_MAYBE_IN_CHARGE_CONSTRUCTOR_P (decl)))
- /* We need a distinct mangled name for these entities, but
- we should never actually output it. So, we append some
- characters the assembler won't like. */
- write_string (" *INTERNAL* ");
}
}
@@ -1653,25 +1646,21 @@ write_identifier (const char *identifier)
::= C2 # base object constructor
::= C3 # complete object allocating constructor
- Currently, allocating constructors are never used.
-
- We also need to provide mangled names for the maybe-in-charge
- constructor, so we treat it here too. mangle_decl_string will
- append *INTERNAL* to that, to make sure we never emit it. */
+ Currently, allocating constructors are never used. */
static void
write_special_name_constructor (const tree ctor)
{
if (DECL_BASE_CONSTRUCTOR_P (ctor))
write_string ("C2");
+ /* This is the old-style "[unified]" constructor.
+ In some cases, we may emit this function and call
+ it from the clones in order to share code and save space. */
+ else if (DECL_MAYBE_IN_CHARGE_CONSTRUCTOR_P (ctor))
+ write_string ("C4");
else
{
- gcc_assert (DECL_COMPLETE_CONSTRUCTOR_P (ctor)
- /* Even though we don't ever emit a definition of
- the old-style destructor, we still have to
- consider entities (like static variables) nested
- inside it. */
- || DECL_MAYBE_IN_CHARGE_CONSTRUCTOR_P (ctor));
+ gcc_assert (DECL_COMPLETE_CONSTRUCTOR_P (ctor));
write_string ("C1");
}
}
@@ -1681,11 +1670,7 @@ write_special_name_constructor (const tree ctor)
<special-name> ::= D0 # deleting (in-charge) destructor
::= D1 # complete object (in-charge) destructor
- ::= D2 # base object (not-in-charge) destructor
-
- We also need to provide mangled names for the maybe-incharge
- destructor, so we treat it here too. mangle_decl_string will
- append *INTERNAL* to that, to make sure we never emit it. */
+ ::= D2 # base object (not-in-charge) destructor */
static void
write_special_name_destructor (const tree dtor)
@@ -1694,14 +1679,14 @@ write_special_name_destructor (const tree dtor)
write_string ("D0");
else if (DECL_BASE_DESTRUCTOR_P (dtor))
write_string ("D2");
+ else if (DECL_MAYBE_IN_CHARGE_DESTRUCTOR_P (dtor))
+ /* This is the old-style "[unified]" destructor.
+ In some cases, we may emit this function and call
+ it from the clones in order to share code and save space. */
+ write_string ("D4");
else
{
- gcc_assert (DECL_COMPLETE_DESTRUCTOR_P (dtor)
- /* Even though we don't ever emit a definition of
- the old-style destructor, we still have to
- consider entities (like static variables) nested
- inside it. */
- || DECL_MAYBE_IN_CHARGE_DESTRUCTOR_P (dtor));
+ gcc_assert (DECL_COMPLETE_DESTRUCTOR_P (dtor));
write_string ("D1");
}
}
diff --git a/gcc/cp/method.c b/gcc/cp/method.c
index 7405365..e79a922 100644
--- a/gcc/cp/method.c
+++ b/gcc/cp/method.c
@@ -477,7 +477,8 @@ trivial_fn_p (tree fn)
return false;
/* If fn is a clone, get the primary variant. */
- fn = DECL_ORIGIN (fn);
+ if (tree prim = DECL_CLONED_FUNCTION (fn))
+ fn = prim;
return type_has_trivial_fn (DECL_CONTEXT (fn), special_function_p (fn));
}
diff --git a/gcc/cp/optimize.c b/gcc/cp/optimize.c
index b8df134..c7480ef 100644
--- a/gcc/cp/optimize.c
+++ b/gcc/cp/optimize.c
@@ -193,30 +193,40 @@ cdtor_comdat_group (tree complete, tree base)
return get_identifier (grp_name);
}
-/* 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. */
+/* Returns true iff we can make the base and complete [cd]tor aliases of
+ the same symbol rather than separate functions. */
-bool
-maybe_clone_body (tree fn)
+static bool
+can_alias_cdtor (tree fn)
+{
+#ifndef ASM_OUTPUT_DEF
+ /* If aliases aren't supported by the assembler, fail. */
+ return false;
+#endif
+ /* We can't use an alias if there are virtual bases. */
+ if (CLASSTYPE_VBASECLASSES (DECL_CONTEXT (fn)))
+ return false;
+ /* ??? Why not use aliases with -frepo? */
+ if (flag_use_repository)
+ return false;
+ gcc_assert (DECL_MAYBE_IN_CHARGE_CONSTRUCTOR_P (fn)
+ || DECL_MAYBE_IN_CHARGE_DESTRUCTOR_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 comdat_group = NULL_TREE;
tree clone;
- tree fns[3];
- bool first = true;
- bool in_charge_parm_used;
- int idx;
- bool need_alias = false;
- /* We only clone constructors and destructors. */
- if (!DECL_MAYBE_IN_CHARGE_CONSTRUCTOR_P (fn)
- && !DECL_MAYBE_IN_CHARGE_DESTRUCTOR_P (fn))
- return 0;
-
- /* Emit the DWARF1 abstract instance. */
- (*debug_hooks->deferred_inline_function) (fn);
-
- in_charge_parm_used = CLASSTYPE_VBASECLASSES (DECL_CONTEXT (fn)) != NULL;
fns[0] = NULL_TREE;
fns[1] = NULL_TREE;
fns[2] = NULL_TREE;
@@ -234,6 +244,206 @@ maybe_clone_body (tree fn)
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);
+ DECL_ABSTRACT (fn) = false;
+ if (!DECL_WEAK (fn))
+ {
+ TREE_PUBLIC (fn) = false;
+ DECL_EXTERNAL (fn) = false;
+ DECL_INTERFACE_KNOWN (fn) = true;
+ }
+ else if (HAVE_COMDAT_GROUP)
+ {
+ tree comdat_group = cdtor_comdat_group (fns[1], fns[0]);
+ DECL_COMDAT_GROUP (fns[0]) = comdat_group;
+ symtab_add_to_same_comdat_group (cgraph_get_create_node (fns[1]),
+ cgraph_get_create_node (fns[0]));
+ symtab_add_to_same_comdat_group (symtab_get_node (fn),
+ symtab_get_node (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. */
+ symtab_add_to_same_comdat_group (cgraph_get_create_node (fns[2]),
+ symtab_get_node (fns[0]));
+ 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 = (tree *) alloca (max_parms * sizeof (tree));
+
+ /* We know that any clones immediately follow FN in TYPE_METHODS. */
+ 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;
+ 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);
+ add_stmt (modify);
+ BLOCK_VARS (block) = clone_result;
+ }
+ 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 (0));
+ }
+ return 1;
+}
+
+/* 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_CONSTRUCTOR_P (fn)
+ && !DECL_MAYBE_IN_CHARGE_DESTRUCTOR_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. */
@@ -247,9 +457,6 @@ maybe_clone_body (tree fn)
{
tree parm;
tree clone_parm;
- int parmno;
- bool alias = false;
- struct pointer_map_t *decl_map;
clone = fns[idx];
if (!clone)
@@ -296,26 +503,44 @@ maybe_clone_body (tree fn)
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_METHODS list. */
+ for (idx = 0; idx < 3; idx++)
+ {
+ tree parm;
+ tree clone_parm;
+ int parmno;
+ struct pointer_map_t *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 (!in_charge_parm_used
+ if (can_alias
&& fns[0]
&& idx == 1
- && !flag_use_repository
- && DECL_INTERFACE_KNOWN (fns[0])
- && (SUPPORTS_ONE_ONLY || !DECL_WEAK (fns[0]))
- && (!DECL_ONE_ONLY (fns[0])
- || (HAVE_COMDAT_GROUP
- && DECL_WEAK (fns[0])))
- && !flag_syntax_only
- /* Set linkage flags appropriately before
- cgraph_create_function_alias looks at them. */
- && expand_or_defer_fn_1 (clone)
- && cgraph_same_body_alias (cgraph_get_node (fns[0]),
+ && cgraph_same_body_alias (cgraph_get_create_node (fns[0]),
clone, fns[0]))
{
alias = true;
diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
index 11f7812..56ea393 100644
--- a/gcc/cp/semantics.c
+++ b/gcc/cp/semantics.c
@@ -3901,20 +3901,6 @@ expand_or_defer_fn_1 (tree fn)
gcc_assert (DECL_SAVED_TREE (fn));
- /* If this is a constructor or destructor body, we have to clone
- it. */
- if (maybe_clone_body (fn))
- {
- /* We don't want to process FN again, so pretend we've written
- it out, even though we haven't. */
- TREE_ASM_WRITTEN (fn) = 1;
- /* If this is an instantiation of a constexpr function, keep
- DECL_SAVED_TREE for explain_invalid_constexpr_fn. */
- if (!is_instantiation_of_constexpr (fn))
- DECL_SAVED_TREE (fn) = NULL_TREE;
- return false;
- }
-
/* We make a decision about linkage for these functions at the end
of the compilation. Until that point, we do not want the back
end to output them -- but we do want it to see the bodies of
@@ -3962,6 +3948,20 @@ expand_or_defer_fn_1 (tree fn)
}
}
+ /* If this is a constructor or destructor body, we have to clone
+ it. */
+ if (maybe_clone_body (fn))
+ {
+ /* We don't want to process FN again, so pretend we've written
+ it out, even though we haven't. */
+ TREE_ASM_WRITTEN (fn) = 1;
+ /* If this is an instantiation of a constexpr function, keep
+ DECL_SAVED_TREE for explain_invalid_constexpr_fn. */
+ if (!is_instantiation_of_constexpr (fn))
+ DECL_SAVED_TREE (fn) = NULL_TREE;
+ return false;
+ }
+
/* There's no reason to do any of the work here if we're only doing
semantic analysis; this code just generates RTL. */
if (flag_syntax_only)
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 6fc56b9..675ad21 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -7265,6 +7265,18 @@ branch-less equivalents.
Enabled at levels @option{-O}, @option{-O2}, @option{-O3}, @option{-Os}.
+@item -fdeclone-ctor-dtor
+@opindex fdeclone-ctor-dtor
+The C++ ABI requires multiple entry points for constructors and
+destructors: one for a base subobject, one for a complete object, and
+one for a virtual destructor that calls operator delete afterwards.
+For a hierarchy with virtual bases, the base and complete variants are
+clones, which means two copies of the function. With this option, the
+base and complete variants are changed to be thunks that call a common
+implementation.
+
+Enabled by @option{-Os}.
+
@item -fdelete-null-pointer-checks
@opindex fdelete-null-pointer-checks
Assume that programs cannot safely dereference null pointers, and that
diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c
index 91214bc..a57767e 100644
--- a/gcc/gimple-fold.c
+++ b/gcc/gimple-fold.c
@@ -89,6 +89,12 @@ can_refer_decl_in_current_unit_p (tree decl, tree from_decl)
snode = symtab_get_node (decl);
if (!snode)
return false;
+ /* A decl local to a comdat group can only be referenced by other
+ decls in that group. */
+ if (comdat_local_p (snode)
+ && (!from_decl
+ || DECL_COMDAT_GROUP (from_decl) != DECL_COMDAT_GROUP (decl)))
+ return false;
node = dyn_cast <cgraph_node> (snode);
return !node || !node->global.inlined_to;
}
diff --git a/gcc/ipa-cp.c b/gcc/ipa-cp.c
index d0fa8db..96c889e 100644
--- a/gcc/ipa-cp.c
+++ b/gcc/ipa-cp.c
@@ -3318,6 +3318,10 @@ decide_about_value (struct cgraph_node *node, int index, HOST_WIDE_INT offset,
&caller_count))
return false;
+ /* Don't clone decls local to a comdat group. */
+ if (comdat_local_p (node))
+ return false;
+
if (dump_file && (dump_flags & TDF_DETAILS))
{
fprintf (dump_file, " - considering value ");
diff --git a/gcc/ipa-inline.c b/gcc/ipa-inline.c
index fbb63da..aa49bfe 100644
--- a/gcc/ipa-inline.c
+++ b/gcc/ipa-inline.c
@@ -230,6 +230,25 @@ report_inline_failed_reason (struct cgraph_edge *e)
}
}
+/* True iff NODE calls another function which is local to its comdat
+ (i.e. C++ decloned constructor); in that case, calls to NODE cannot be
+ inlined, as that would cause a reference from outside the comdat to the
+ local symbol. If we inline the call from NODE to the local function,
+ then inlining NODE can succeed. */
+
+static bool
+calls_comdat_local_p (struct cgraph_node *node)
+{
+ if (!node->same_comdat_group)
+ return false;
+ for (struct cgraph_edge *e = node->callees; e; e = e->next_callee)
+ {
+ if (comdat_local_p (e->callee))
+ return true;
+ }
+ return false;
+}
+
/* Decide if we can inline the edge and possibly update
inline_failed reason.
We check whether inlining is possible at all and whether
@@ -237,7 +256,7 @@ report_inline_failed_reason (struct cgraph_edge *e)
if REPORT is true, output reason to the dump file.
- if DISREGARD_LIMITES is true, ignore size limits.*/
+ if DISREGARD_LIMITS is true, ignore size limits.*/
static bool
can_inline_edge_p (struct cgraph_edge *e, bool report,
@@ -267,6 +286,11 @@ can_inline_edge_p (struct cgraph_edge *e, bool report,
e->inline_failed = CIF_BODY_NOT_AVAILABLE;
inlinable = false;
}
+ else if (calls_comdat_local_p (callee))
+ {
+ e->inline_failed = CIF_FUNCTION_NOT_INLINABLE;
+ inlinable = false;
+ }
else if (!inline_summary (callee)->inlinable
|| (caller_cfun && fn_contains_cilk_spawn_p (caller_cfun)))
{
diff --git a/gcc/ipa.c b/gcc/ipa.c
index 3950d4e..ab55388 100644
--- a/gcc/ipa.c
+++ b/gcc/ipa.c
@@ -363,14 +363,17 @@ symtab_remove_unreachable_nodes (bool before_inlining_p, FILE *file)
enqueue_node (origin_node, &first, reachable);
}
/* If any symbol in a comdat group is reachable, force
- all other in the same comdat group to be also reachable. */
+ all externally visible symbols in the same comdat
+ group to be reachable as well. Comdat-local symbols
+ can be discarded if all uses were inlined. */
if (node->same_comdat_group)
{
symtab_node *next;
for (next = node->same_comdat_group;
next != node;
next = next->same_comdat_group)
- if (!pointer_set_insert (reachable, next))
+ if (!comdat_local_p (next)
+ && !pointer_set_insert (reachable, next))
enqueue_node (next, &first, reachable);
}
/* Mark references as reachable. */
@@ -949,6 +952,8 @@ function_and_variable_visibility (bool whole_program)
node->forced_by_abi = false;
}
if (!node->externally_visible
+ /* Don't mess with a function local to a comdat group. */
+ && !comdat_local_p (node)
&& node->definition && !node->weakref
&& !DECL_EXTERNAL (node->decl))
{
diff --git a/gcc/testsuite/g++.dg/ext/label13a.C b/gcc/testsuite/g++.dg/ext/label13a.C
new file mode 100644
index 0000000..3be8e31
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/label13a.C
@@ -0,0 +1,25 @@
+// PR c++/41090
+// { dg-do run }
+// { dg-options "-save-temps" }
+// { dg-final { scan-assembler "_ZN1CC4Ev" } }
+// { dg-final cleanup-saved-temps }
+
+int i;
+struct A { A() {} };
+struct C: virtual A
+{
+ C();
+};
+
+C::C()
+{
+ static void *labelref = &&label;
+ goto *labelref;
+ label: i = 1;
+}
+
+int main()
+{
+ C c;
+ return (i != 1);
+}
diff --git a/include/demangle.h b/include/demangle.h
index 58bf547..bbad71b 100644
--- a/include/demangle.h
+++ b/include/demangle.h
@@ -173,6 +173,10 @@ enum gnu_v3_ctor_kinds {
gnu_v3_complete_object_ctor = 1,
gnu_v3_base_object_ctor,
gnu_v3_complete_object_allocating_ctor,
+ /* These are not part of the V3 ABI. Unified constructors are generated
+ as a speed-for-space optimization when the -fdeclone-ctor-dtor option
+ is used, and are always internal symbols. */
+ gnu_v3_unified_ctor,
gnu_v3_object_ctor_group
};
@@ -188,6 +192,10 @@ enum gnu_v3_dtor_kinds {
gnu_v3_deleting_dtor = 1,
gnu_v3_complete_object_dtor,
gnu_v3_base_object_dtor,
+ /* These are not part of the V3 ABI. Unified destructors are generated
+ as a speed-for-space optimization when the -fdeclone-ctor-dtor option
+ is used, and are always internal symbols. */
+ gnu_v3_unified_dtor,
gnu_v3_object_dtor_group
};
diff --git a/libiberty/cp-demangle.c b/libiberty/cp-demangle.c
index cbe4d8c..8902dd8 100644
--- a/libiberty/cp-demangle.c
+++ b/libiberty/cp-demangle.c
@@ -2081,6 +2081,9 @@ d_ctor_dtor_name (struct d_info *di)
case '3':
kind = gnu_v3_complete_object_allocating_ctor;
break;
+ case '4':
+ kind = gnu_v3_unified_ctor;
+ break;
case '5':
kind = gnu_v3_object_ctor_group;
break;
@@ -2106,6 +2109,10 @@ d_ctor_dtor_name (struct d_info *di)
case '2':
kind = gnu_v3_base_object_dtor;
break;
+ /* digit '3' is not used */
+ case '4':
+ kind = gnu_v3_unified_dtor;
+ break;
case '5':
kind = gnu_v3_object_dtor_group;
break;