Index: gcc/ChangeLog =================================================================== --- gcc/ChangeLog (revision 130298) +++ gcc/ChangeLog (working copy) @@ -1,3 +1,26 @@ +2007-11-21 Bill Maddox + + * c-common.c, c-common.h (flag_declone_ctor_dtor): New variable. + (declone_ctor_dtor_threshold): New variable. + + * c-opts.c (c_common_handle_option): Add -fdeclone-ctor-dtor option. + This option causes the compiler to attempt to share code between + the in-charge and not-in-charge forms of a constructor or destructor. + The clones of the original ctor/dtor are implemented as trivial + thunks, effectively reversing the cloning transformation. The + result is link-compatible, except in the highly-unlikely case of + a symbol definition conflict with the mangled name of shared ctor/dtor, + which must be visible, and whose existence is not provided for in + the ABI. + + * configure.ac: New configuration option: + --enable-allow-inline-ctor-dtor-thunks + Permits inlining of constructors and destructors decloned + via -fdeclone-ctor-dtor. Use with caution, as the resulting + object files do not conform to the ABI, though the code + attempts to maintain link compatibility with code compiled + without decloning, thunk inlining, or neither. + 2007-11-19 Jason Merrill PR debug/28834, debug/29436, c/32326 Index: gcc/cp/optimize.c =================================================================== --- gcc/cp/optimize.c (revision 130298) +++ gcc/cp/optimize.c (working copy) @@ -45,6 +45,7 @@ /* Prototypes. */ static void update_cloned_parm (tree, tree, bool); +static bool maybe_thunk_body(tree); /* CLONED_PARM is a copy of CLONE, generated for a cloned constructor or destructor. Update it to ensure that the source-position for @@ -72,6 +73,177 @@ DECL_GIMPLE_REG_P (cloned_parm) = DECL_GIMPLE_REG_P (parm); } +/* 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) +{ + tree bind, block, call, clone, clone_result, fn_parm, fn_parm_typelist, last_arg, modify; + tree *actual_parms; + int parmno, vtt_parmno, max_parms; + + if (!flag_declone_ctor_dtor) + return 0; + +#ifndef ALLOW_INLINE_CTOR_DTOR_THUNKS + /* We cannot inline the thunks, so don't declone if the user wants inlining. */ + if (DECL_DECLARED_INLINE_P (fn) & !flag_really_no_inline) + return 0; +#endif + + /* If we've already seen this structor, avoid re-processing it. */ + if (TREE_ASM_WRITTEN (fn)) + return 1; + + /* If function accepts variable arguments, give up. */ + last_arg = tree_last (TYPE_ARG_TYPES (TREE_TYPE (fn))); + if ( ! VOID_TYPE_P (TREE_VALUE (last_arg))) + return 0; + + /* If we don't see a clone, nothing to do. */ + clone = TREE_CHAIN (fn); + if (!clone || ! DECL_CLONED_FUNCTION_P (clone)) + return 0; + + /* This is only a win if there are two or more clones. */ + if ( ! TREE_CHAIN (clone)) + return 0; + + /* Only thunk-ify non-trivial structors. */ + if (estimate_num_insns (DECL_SAVED_TREE (fn), + &eni_size_weights) < declone_ctor_dtor_threshold) + 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." + It has been suggested that we make the unified ctor/dtor private to + this compilation unit, but, since we are decloning to save space, it + is essential that we be able to coalesce multiple definitions. */ + DECL_ABSTRACT (fn) = 0; + + /* 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) + { + vtt_parmno = parmno; /* Compensate for removed in_charge parameter. */ + 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; + } + actual_parms = (tree *) alloca (max_parms * sizeof (tree)); + + /* We know that any clones immediately follow FN in the TYPE_METHODS list. */ + 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); + + /* 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)) + { + tree typed_null_pointer_node = copy_node (null_pointer_node); + gcc_assert (fn_parm_typelist); + /* Clobber actual parameter with formal parameter type. */ + TREE_TYPE (typed_null_pointer_node) = TREE_VALUE (fn_parm_typelist); + actual_parms[parmno] = typed_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))); + actual_parms[parmno] = in_charge; + } + /* Map other parameters to their equivalents in the cloned + function. */ + else + { + gcc_assert (clone_parm); + DECL_ABSTRACT_ORIGIN (clone_parm) = NULL; + actual_parms[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, actual_parms); + /* 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 (block, cur_stmt_list); + DECL_SAVED_TREE (clone) = push_stmt_list (); + add_stmt (bind); + +#ifndef ALLOW_INLINE_CTOR_DTOR_THUNKS + /* If we allow inlining of thunks, we may generate unresolved + references to the unified ctor/dtor unless the unified + ctor/dtor is defined within the current compilation unit. + We would thus fail to link against libraries compiled without + decloning. */ + DECL_INLINE(clone) = 0; + DECL_UNINLINABLE(clone) = 1; +#endif + + /* Now, expand this function into RTL, if appropriate. */ + finish_function (0); + DECL_ABSTRACT_ORIGIN (clone) = NULL; + /* It would seem a bit cleaner to just generate the clones + in pre-gimplified form. Unfortunately, build_cxx_call() + may rewrite the arguments, introducing expressions that + are not legal GIMPLE. */ + gimplify_function_tree(clone); + expand_or_defer_fn (clone); + } + 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. */ @@ -97,8 +269,6 @@ { tree parm; tree clone_parm; - int parmno; - struct pointer_map_t *decl_map; /* Update CLONE's source position information to match FN's. */ DECL_SOURCE_LOCATION (clone) = DECL_SOURCE_LOCATION (fn); @@ -133,7 +303,26 @@ parm = TREE_CHAIN (parm), clone_parm = TREE_CHAIN (clone_parm)) /* Update this parameter. */ update_cloned_parm (parm, clone_parm, first); + first = false; + } + /* If we decide to turn clones into thunks, they will branch to fn. + Must have original function available to call. */ + if (maybe_thunk_body (fn)) + { + pop_from_top_level (); + /* We still need to emit the original function. */ + return 0; + } + + /* We know that any clones immediately follow FN in the TYPE_METHODS list. */ + FOR_EACH_CLONE (clone, fn) + { + tree parm; + tree clone_parm; + int parmno; + struct pointer_map_t *decl_map; + /* Start processing the function. */ start_preparsed_function (clone, NULL_TREE, SF_PRE_PARSED); @@ -197,7 +386,6 @@ finish_function (0); BLOCK_ABSTRACT_ORIGIN (DECL_INITIAL (clone)) = DECL_INITIAL (fn); expand_or_defer_fn (clone); - first = false; } pop_from_top_level (); Index: gcc/cp/repo.c =================================================================== --- gcc/cp/repo.c (revision 130298) +++ gcc/cp/repo.c (working copy) @@ -277,13 +277,24 @@ file indicates that that DECL should be emitted in this translation unit, or 2 if the repository file is not in use. */ +static int +repo_emit_p_aux(tree decl); + int repo_emit_p (tree decl) { + /* We don't want to make this assertion on recursive calls. */ + gcc_assert (!DECL_REALLY_EXTERN (decl)); + + return repo_emit_p_aux(decl); +} + +static int +repo_emit_p_aux (tree decl) +{ gcc_assert (TREE_PUBLIC (decl)); gcc_assert (TREE_CODE (decl) == FUNCTION_DECL || TREE_CODE (decl) == VAR_DECL); - gcc_assert (!DECL_REALLY_EXTERN (decl)); /* When not using the repository, emit everything. */ if (!flag_use_repository) @@ -328,7 +339,7 @@ FOR_EACH_CLONE (clone, decl) /* The only possible results from the recursive call to repo_emit_p are 0 or 1. */ - if (repo_emit_p (clone)) + if (repo_emit_p_aux (clone)) emit_p = 1; return emit_p; } Index: gcc/cp/decl.c =================================================================== --- gcc/cp/decl.c (revision 130298) +++ gcc/cp/decl.c (working copy) @@ -8486,7 +8486,9 @@ /* 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 (constructor_name_p (unqualified_id, current_class_type)) Index: gcc/cp/mangle.c =================================================================== --- gcc/cp/mangle.c (revision 130298) +++ gcc/cp/mangle.c (working copy) @@ -706,13 +706,6 @@ 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* "); } } @@ -1376,25 +1369,21 @@ ::= C2 # base object constructor ::= C3 # complete object allocating constructor - Currently, allocating constructors are never used. + 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. */ - 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"); } } @@ -1404,12 +1393,8 @@ ::= D0 # deleting (in-charge) destructor ::= D1 # complete object (in-charge) destructor - ::= D2 # base object (not-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. */ - static void write_special_name_destructor (const tree dtor) { @@ -1417,14 +1402,14 @@ 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"); } } Index: gcc/cp/ChangeLog =================================================================== --- gcc/cp/ChangeLog (revision 130298) +++ gcc/cp/ChangeLog (working copy) @@ -1,3 +1,31 @@ +2007-11-21 Bill Maddox + + * decl2.c (import_export_decl): Add comment. + (cp_write_global_declarations): If configured to allow + inlining of decloned ctor/dtor thunks, make sure that + the unified constructor is emitted. Use COMDAT to avoid + code duplication. + * mangle.c (write_mangled_name): Remove code to suppress + writing of mangled name for cloned constructor or destructor. + The original ctor or dtor may be emitted if decloned. + (write_special_name_constructor): Handle decloned constructor. + (write_special_name_destructor): Handle decloned destructor. + * decl.c (grokdeclarator): Note the possibility of decloning + in a comment. + * optimize.c (maybe_thunk_body): New function. If the estimated + size of a constructor or destructor is larger than a specified + threshold, attempt to create a common constructor to which the + specialized constructor forms ("clones") delegate. This effectively + reverses the cloning transformation, but retains link compatibility. + This code is lifted with some small modifications from an earlier + patch submitted by Stuart Hastings. + (maybe_clone_body): Invoke maybe_thunk_body. + * repo.c (repo_emit_p): Avoid bogus assertion when recursing + on constructor/destructor clones. + (repo_emit_p_aux): New function. Most of the body of repo_emit_p + moved here. + + 2007-11-18 Jakub Jelinek PR c++/30988 Index: gcc/cp/decl2.c =================================================================== --- gcc/cp/decl2.c (revision 130298) +++ gcc/cp/decl2.c (working copy) @@ -2108,6 +2108,11 @@ tree clone; FOR_EACH_CLONE (clone, decl) mark_needed (clone); + /* In the case of decloning, should we also mark the maybe-in-charge + constructor as needed? This should fall out automatically from the + fact that it is called from the clones (thunks). Marking it here + explicitly would require that we be able to determine that decloning + has occurred. */ } else mark_needed (decl); @@ -3324,6 +3329,37 @@ rest_of_compilation would skip this function and we really cannot expand the same function twice. */ import_export_decl (decl); + +#ifdef ALLOW_INLINE_CTOR_DTOR_THUNKS + /* A ctor/dtor thunk introduced by decloning might be inlined, + possibly resulting in an external reference to the unified + ctor/dtor. This function will not be provided, however, by + compilation units compiled without decloning. To maintain + link-compatibility, it is therefore necessary for any compilation + unit that might inline a decloned ctor/dtor to provide its own + definition of the unified ctor/dtor. We force all definitions + of the unified ctor/dtor to be COMDAT, to prevent code duplication. + This includes definitions that would otherwise be strong. */ + if (flag_declone_ctor_dtor + && (DECL_MAYBE_IN_CHARGE_CONSTRUCTOR_P (decl) + || DECL_MAYBE_IN_CHARGE_DESTRUCTOR_P (decl))) + { + /* We must not coalesce ctors/dtors belonging to classes nested + within static functions. Like-named classes nested within + like-named static functions are distinct, but the mangled + names given to their members may be identical. */ + tree ctx = decl_function_context (decl); + if (ctx == NULL || TREE_PUBLIC (ctx)) + { + DECL_NOT_REALLY_EXTERN (decl) = 1; + /* The function will not actually be emitted + unless it is used in this compilation unit. */ + comdat_linkage (decl); + DECL_INTERFACE_KNOWN (decl) = 1; + } + } +#endif + if (DECL_NOT_REALLY_EXTERN (decl) && DECL_INITIAL (decl) && decl_needed_p (decl)) Index: gcc/configure.ac =================================================================== --- gcc/configure.ac (revision 130298) +++ gcc/configure.ac (working copy) @@ -794,6 +794,14 @@ esac], [enable_languages=c]) +AC_ARG_ENABLE(allow-inline-ctor-dtor-thunks, +[ --enable-allow-inline-ctor-dtor-thunks permit inlining of decloned ctors and dtors], [], +[enable_allow_inline_ctor_dtor_thunks=no]) +if test x$enable_allow_inline_ctor_dtor_thunks = xyes ; then + AC_DEFINE(ALLOW_INLINE_CTOR_DTOR_THUNKS, 1, + [Define to enable inlining of decloned constructors and destructors.]) +fi + # Used by documentation targets AC_SUBST(datarootdir) AC_SUBST(docdir) Index: gcc/c.opt =================================================================== --- gcc/c.opt (revision 130298) +++ gcc/c.opt (working copy) @@ -519,6 +519,13 @@ ObjC ObjC++ Joined -fconst-string-class= Use class for constant strings +fdeclone-ctor-dtor +C++ ObjC++ UInteger +Factor complex constructors and destructors to favor space over speed + +fdeclone-ctor-dtor= +C++ ObjC++ Joined RejectNegative UInteger + fdefault-inline C++ ObjC++ Inline member functions by default Index: gcc/c-opts.c =================================================================== --- gcc/c-opts.c (revision 130298) +++ gcc/c-opts.c (working copy) @@ -673,6 +673,15 @@ constant_string_class_name = arg; break; + case OPT_fdeclone_ctor_dtor: + flag_declone_ctor_dtor = value; + break; + + case OPT_fdeclone_ctor_dtor_: + flag_declone_ctor_dtor = true; + declone_ctor_dtor_threshold = value; + break; + case OPT_fdefault_inline: flag_default_inline = value; break; Index: gcc/c-common.c =================================================================== --- gcc/c-common.c (revision 130298) +++ gcc/c-common.c (working copy) @@ -470,8 +470,22 @@ int max_tinst_depth = 500; +/* For nonzero value, generate thunks to share a common constructor or + destructor body where the size of the common ctor/dtor is estimated + to be smaller than declone_ctor_dtor_threshold. Otherwise, the code + is replicated in the complete and base-class variants of the ctor/dtor. */ +int flag_declone_ctor_dtor; +/* Minimum (estimated) number of instructions in a constructor or + destructor to be decloned. Ideally, this should be a function of the + thunk overhead, which is dependent on the calling convention and on + the number of arguments. Currently, this parameter is settable + primarily to enable stress-testing, as many constructors are too small + to warrant decloning in practice. */ + +int declone_ctor_dtor_threshold = 5; + /* The elements of `ridpointers' are identifier nodes for the reserved type names and storage classes. It is indexed by a RID_... value. */ tree *ridpointers; Index: gcc/c-common.h =================================================================== --- gcc/c-common.h (revision 130298) +++ gcc/c-common.h (working copy) @@ -622,6 +622,22 @@ extern int skip_evaluation; +/* For nonzero value, generate thunks to share a common constructor or + destructor body where the size of the common ctor/dtor is estimated + to be smaller than declone_ctor_dtor_threshold. Otherwise, the code + is replicated in the complete and base-class variants of the ctor/dtor. */ + +extern int flag_declone_ctor_dtor; + +/* Minimum (estimated) number of instructions in a constructor or + destructor to be decloned. Ideally, this should be a function of the + thunk overhead, which is dependent on the calling convention and on + the number of arguments. Currently, this parameter is settable + primarily to enable stress-testing, as many constructors are too small + to warrant decloning in practice. */ + +extern int declone_ctor_dtor_threshold; + /* C types are partitioned into three subsets: object, function, and incomplete types. */ #define C_TYPE_OBJECT_P(type) \ Index: include/ChangeLog =================================================================== --- include/ChangeLog (revision 130298) +++ include/ChangeLog (working copy) @@ -1,3 +1,10 @@ +2007-11-21 Bill Maddox + + * demangle.h (enum gnu_v3_ctor_kinds): + Added literal gnu_v3_unified_ctor. + (enum gnu_v3_ctor_kinds): + Added literal gnu_v3_unified_dtor. + 2007-11-07 Joseph Myers Daniel Jacobowitz Index: include/demangle.h =================================================================== --- include/demangle.h (revision 130298) +++ include/demangle.h (working copy) @@ -163,7 +163,14 @@ enum gnu_v3_ctor_kinds { gnu_v3_complete_object_ctor = 1, gnu_v3_base_object_ctor, - gnu_v3_complete_object_allocating_ctor + gnu_v3_complete_object_allocating_ctor, + /* These are not actually legal in the V3 ABI !!! + Unified constructors are generated as a speed-for-space + optimization when the -fdeclone-ctor-dtor option is used. + The unified constructor name is global in order to support + linkonce semantics, but it should never be invoked across + a compilation unit boundary. */ + gnu_v3_unified_ctor }; /* Return non-zero iff NAME is the mangled form of a constructor name @@ -177,7 +184,14 @@ enum gnu_v3_dtor_kinds { gnu_v3_deleting_dtor = 1, gnu_v3_complete_object_dtor, - gnu_v3_base_object_dtor + gnu_v3_base_object_dtor, + /* These are not actually legal in the V3 ABI !!! + Unified destructors are generated as a speed-for-space + optimization when the -fdeclone-ctor-dtor option is used. + The unified destructor name is global in order to support + linkonce semantics, but it should never be invoked across + a compilation unit boundary. */ + gnu_v3_unified_dtor }; /* Return non-zero iff NAME is the mangled form of a destructor name Index: libiberty/ChangeLog =================================================================== --- libiberty/ChangeLog (revision 130298) +++ libiberty/ChangeLog (working copy) @@ -1,3 +1,9 @@ +2007-11-21 Bill Maddox + + * cp-demangle.c (cplus_demangle_fill_ctor,cplus_demangle_fill_dtor): + Handle unified ctor/dtor. + (d_ctor_dtor_name): Handle unified ctor/dtor. + 2007-11-12 Joseph Myers * floatformat.c (floatformat_ibm_long_double_is_valid): Fix Index: libiberty/cp-demangle.c =================================================================== --- libiberty/cp-demangle.c (revision 130298) +++ libiberty/cp-demangle.c (working copy) @@ -698,7 +698,7 @@ if (p == NULL || name == NULL || (kind < gnu_v3_complete_object_ctor - && kind > gnu_v3_complete_object_allocating_ctor)) + && kind > gnu_v3_unified_ctor)) return 0; p->type = DEMANGLE_COMPONENT_CTOR; p->u.s_ctor.kind = kind; @@ -717,7 +717,7 @@ if (p == NULL || name == NULL || (kind < gnu_v3_deleting_dtor - && kind > gnu_v3_base_object_dtor)) + && kind > gnu_v3_unified_dtor)) return 0; p->type = DEMANGLE_COMPONENT_DTOR; p->u.s_dtor.kind = kind; @@ -1685,7 +1685,10 @@ case '3': kind = gnu_v3_complete_object_allocating_ctor; break; - default: + case '4': + kind = gnu_v3_unified_ctor; + break; + default: return NULL; } d_advance (di, 2); @@ -1707,6 +1710,10 @@ case '2': kind = gnu_v3_base_object_dtor; break; + /* digit '3' is not used */ + case '4': + kind = gnu_v3_unified_dtor; + break; default: return NULL; }