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 <ja...@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;