On Wed, Dec 31, 2025 at 10:17 AM Jakub Jelinek <[email protected]> wrote:
>
> Hi!
>
> libstdc++ in various spots used just function/method declarations and
> definitions inside of libstdc++.so.6 for various reasons, whether because
> the bodies were too large or because it wanted a key method for the class
> to be defined in the library only to avoid vtables etc. in many TUs.
>
> Already when working on the first constexpr exception patch on the library
> side I've noticed that std::exception etc. have for C++26 the vtables
> emitted everywhere, but these two new PRs, PR123183 and PR123326 issues
> show that it is even bigger problem.
> While for both of these (one is about FreeBSD link failures when using
> libstdc++.a and format.o from that library is linked in and -liconv is not
> added, the other about MinGW clash between inline functions (which can't be
> weak) and non-inline definition in libstdc++.a) need to be resolved some way
> in any case, the FreeBSD perhaps through addition of libstdc++.spec or
> arrange in some other way to link in -liconv when linking with -lstdc++ and
> for MinGW perhaps making the libstdc++.a functions inline not in the class
> definitions (which is something that doesn't work on arm-eabi though),
> especially the FreeBSD PR shows quite undesirable optimization problem.
> Previously, when something needed std::bad_alloc::~bad_alloc(), it linked
> libstdc++.a(bad_alloc.o).  But now many C++26 compiled objects also
> export std::bad_alloc::~bad_alloc() and it depends on the luck which one
> will be linked, so with luck like before the very small bad_alloc.o, when
> less lucky uselessly the whole format.o or something perhaps even larger.
>
> Even before I think Jonathan complained that with C++ requiring more and
> more large functions/methods to be constexpr lots of things become inline
> functions which might not be desirable in many cases.

Yes that would be PR 93008.

Thanks,
Andrew

>
> The following patch introduces a new attribute - gnu::constexpr_only -
> which can be applied only to functions/methods with constexpr keyword and
> will make their bodies only available to constexpr evaluation but otherwise
> pretend it is just a declaration, not function definition which if used
> needs to be defined somewhere else (like in libstdc++.{so.6,a}).
> A constexpr method marked with this attribute can still be a key method
> of a class too.
>
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
>
> 2025-12-31  Jakub Jelinek  <[email protected]>
>
>         PR libstdc++/123183
>         PR libstdc++/123326
>         * doc/extend.texi (constexpr_only): Document new C++ function
>         attribute.
>
>         * tree.cc (handle_constexpr_only_attribute): New function.
>         (cxx_gnu_attributes): Add constexpr_only attribute.
>         * decl.cc (finish_function): For constexpr_only functions clear
>         DECL_SAVED_TREE after maybe_save_constexpr_fundef, set
>         DECL_INITIAL to error_mark_node and goto cleanup.
>         * semantics.cc (expand_or_defer_fn_1): Handle constexpr_only
>         functions like processing_template_decl.
>         * class.cc (determine_key_method): Ignore DECL_DECLARED_INLINE_P
>         for constexpr_only methods.
>         * constexpr.cc (cxx_eval_call_expression): Retrieve constexpr
>         body for constexpr_only functions even with DECL_SAVED_TREE NULL.
>
>         * g++.dg/ext/attr-constexpr-only1.C: New test.
>         * g++.dg/ext/attr-constexpr-only2.C: New test.
>
> --- gcc/doc/extend.texi.jj      2025-12-27 11:44:31.586957271 +0100
> +++ gcc/doc/extend.texi 2025-12-30 13:42:33.474926999 +0100
> @@ -31218,6 +31218,42 @@ decltype(auto) foo(T&& t) @{
>  @};
>  @end smallexample
>
> +@cindex @code{constexpr_only} function attribute
> +@item constexpr_only
> +The @code{constexpr_only} attribute can be applied to a @code{constexpr} 
> function
> +declaration.  When the function is defined, the function body is only used
> +for C++ constant evaluation purposes, otherwise the function definition is
> +treated as a external declaration.  If the function is called or has its
> +address taken and such reference is not optimized away, external definition
> +without the attribute still needs to be provided in some other translation
> +units.  When used on a @code{virtual constexpr} method, such a method can be
> +still a key method of the class.  This attribute is intended mainly for
> +functions which were intentionally defined out of line in libraries for
> +optimization (e.g.@: because the function is very large), key method or ABI
> +reasons but later on are required or desirable to be evaluable in constant
> +expressions.
> +
> +@smallexample
> +struct S @{
> +#if __cplusplus >= 202002L
> +  [[gnu::constexpr_only]] constexpr virtual ~S () @{@}
> +#else
> +  virtual ~S ();
> +#endif
> +@};
> +S s;
> +@end smallexample
> +
> +When compiled with @option{-std=c++17} or earlier, on many targets the
> +virtual table will be emitted only in some other translation unit which
> +will define @code{S::~S} method.  For @option{-std=c++20} if it is desirable
> +to support using @code{S} class in constant expressions, without the
> +attribute the virtual table would be emitted in every translation unit
> +which uses @code{S} class, with the attribute it behaves like for C++17
> +except it can be used in constant expressions.  The out of line definition
> +needs to be compiled with @option{-std=c++17} so that the attribute is not
> +used, or it needs to be avoided in some other way.
> +
>  @cindex @code{warn_unused} type attribute
>  @item warn_unused
>
> --- gcc/cp/tree.cc.jj   2025-12-20 12:01:31.884718947 +0100
> +++ gcc/cp/tree.cc      2025-12-30 12:36:54.601069297 +0100
> @@ -48,6 +48,7 @@ static tree handle_init_priority_attribu
>  static tree handle_abi_tag_attribute (tree *, tree, tree, int, bool *);
>  static tree handle_contract_attribute (tree *, tree, tree, int, bool *);
>  static tree handle_no_dangling_attribute (tree *, tree, tree, int, bool *);
> +static tree handle_constexpr_only_attribute (tree *, tree, tree, int, bool 
> *);
>
>  /* If REF is an lvalue, returns the kind of lvalue that REF is.
>     Otherwise, returns clk_none.  */
> @@ -5557,6 +5558,8 @@ static const attribute_spec cxx_gnu_attr
>      handle_abi_tag_attribute, NULL },
>    { "no_dangling", 0, 1, false, true, false, false,
>      handle_no_dangling_attribute, NULL },
> +  { "constexpr_only", 0, 0, true, false, false, false,
> +    handle_constexpr_only_attribute, NULL },
>  };
>
>  const scoped_attribute_specs cxx_gnu_attribute_table =
> @@ -5889,6 +5892,23 @@ handle_no_dangling_attribute (tree *node
>
>    return NULL_TREE;
>  }
> +
> +/* Handle a "constexpr_only" attribute; arguments as in
> +   struct attribute_spec.handler.  */
> +
> +tree
> +handle_constexpr_only_attribute (tree *node, tree name, tree, int, bool 
> *no_add_attrs)
> +{
> +  if (TREE_CODE (*node) != FUNCTION_DECL || !DECL_DECLARED_CONSTEXPR_P 
> (*node))
> +    {
> +      warning (OPT_Wattributes, "%qE attribute ignored", name);
> +      *no_add_attrs = true;
> +    }
> +  else
> +    DECL_DECLARED_INLINE_P (*node) = 0;
> +
> +  return NULL_TREE;
> +}
>
>  /* Return a new PTRMEM_CST of the indicated TYPE.  The MEMBER is the
>     thing pointed to by the constant.  */
> --- gcc/cp/decl.cc.jj   2025-12-27 11:44:31.534958161 +0100
> +++ gcc/cp/decl.cc      2025-12-31 12:02:45.225650460 +0100
> @@ -20480,6 +20480,17 @@ finish_function (bool inline_p)
>       the NRV transformation.   */
>    maybe_save_constexpr_fundef (fndecl);
>
> +  /* After saving the constexpr function body, if function is constexpr_only,
> +     pretend it is just a declaration rather than function definition.  */
> +  if (!processing_template_decl
> +      && lookup_attribute ("constexpr_only", DECL_ATTRIBUTES (fndecl)))
> +    {
> +      DECL_INITIAL (fndecl) = error_mark_node;
> +      DECL_SAVED_TREE (fndecl) = NULL_TREE;
> +      DECL_EXTERNAL (fndecl) = 1;
> +      goto cleanup;
> +    }
> +
>    /* Perform delayed folding before NRV transformation.  */
>    if (!processing_template_decl
>        && !DECL_IMMEDIATE_FUNCTION_P (fndecl)
> --- gcc/cp/semantics.cc.jj      2025-12-27 11:44:31.583957323 +0100
> +++ gcc/cp/semantics.cc 2025-12-30 13:03:14.994782969 +0100
> @@ -5518,7 +5518,9 @@ expand_or_defer_fn_1 (tree fn)
>  {
>    /* When the parser calls us after finishing the body of a template
>       function, we don't really want to expand the body.  */
> -  if (processing_template_decl)
> +  if (processing_template_decl
> +      || (DECL_DECLARED_CONSTEXPR_P (fn)
> +         && lookup_attribute ("constexpr_only", DECL_ATTRIBUTES (fn))))
>      {
>        /* Normally, collection only occurs in rest_of_compilation.  So,
>          if we don't collect here, we never collect junk generated
> --- gcc/cp/class.cc.jj  2025-09-29 15:01:29.000000000 +0200
> +++ gcc/cp/class.cc     2025-12-30 13:11:11.100721384 +0100
> @@ -7472,7 +7472,12 @@ determine_key_method (tree type)
>    for (method = TYPE_FIELDS (type); method; method = DECL_CHAIN (method))
>      if (TREE_CODE (method) == FUNCTION_DECL
>         && DECL_VINDEX (method) != NULL_TREE
> -       && ! DECL_DECLARED_INLINE_P (method)
> +       && (! DECL_DECLARED_INLINE_P (method)
> +           || (DECL_DECLARED_CONSTEXPR_P (method)
> +               /* constexpr_only methods will lose DECL_DECLARED_INLINE_P
> +                  when the attributes are processed.  */
> +               && lookup_attribute ("constexpr_only",
> +                                    DECL_ATTRIBUTES (method))))
>         && ! DECL_PURE_VIRTUAL_P (method))
>        {
>         SET_CLASSTYPE_KEY_METHOD (type, method);
> --- gcc/cp/constexpr.cc.jj      2025-11-21 14:18:48.000000000 +0100
> +++ gcc/cp/constexpr.cc 2025-12-30 14:25:21.790579349 +0100
> @@ -4142,7 +4142,10 @@ cxx_eval_call_expression (const constexp
>        bool cacheable = !!entry;
>        if (result && result != error_mark_node)
>         /* OK */;
> -      else if (!DECL_SAVED_TREE (fun))
> +      else if (!DECL_SAVED_TREE (fun)
> +              && !(DECL_DECLARED_CONSTEXPR_P (fun)
> +                   && lookup_attribute ("constexpr_only",
> +                                        DECL_ATTRIBUTES (fun))))
>         {
>           /* When at_eof >= 3, cgraph has started throwing away
>              DECL_SAVED_TREE, so fail quietly.  FIXME we get here because of
> --- gcc/testsuite/g++.dg/ext/attr-constexpr-only1.C.jj  2025-12-30 
> 14:00:03.788202322 +0100
> +++ gcc/testsuite/g++.dg/ext/attr-constexpr-only1.C     2025-12-30 
> 13:59:59.013282914 +0100
> @@ -0,0 +1,25 @@
> +// { dg-do compile { target c++20 } }
> +
> +struct S {
> +  [[gnu::constexpr_only]] constexpr virtual ~S () {}
> +  [[gnu::constexpr_only]] constexpr virtual const char *what () { return 
> "S"; }
> +} s;
> +
> +static_assert (S ().what ()[0] == 'S' && S ().what ()[1] == '\0');
> +// { dg-final { scan-assembler-not "_ZTV1S:" } }
> +// { dg-final { scan-assembler-not "_ZTI1S:" } }
> +// { dg-final { scan-assembler-not "_ZTS1S:" } }
> +// { dg-final { scan-assembler-not "_ZN1SD2Ev:" } }
> +// { dg-final { scan-assembler-not "_ZN1SD0Ev:" } }
> +// { dg-final { scan-assembler-not "_ZN1S4whatEv:" } }
> +
> +[[gnu::constexpr_only]] constexpr int
> +foo ()
> +{
> +  return 42;
> +}
> +
> +static_assert (foo () == 42);
> +
> +int (*p) () = foo;
> +// { dg-final { scan-assembler-not "_Z3foov:" } }
> --- gcc/testsuite/g++.dg/ext/attr-constexpr-only2.C.jj  2025-12-30 
> 14:33:01.785815152 +0100
> +++ gcc/testsuite/g++.dg/ext/attr-constexpr-only2.C     2025-12-30 
> 14:37:45.338029123 +0100
> @@ -0,0 +1,14 @@
> +// { dg-do compile { target c++11 } }
> +
> +[[gnu::constexpr_only]] inline void foo () {}                  // { 
> dg-warning "'constexpr_only' attribute ignored" }
> +[[gnu::constexpr_only]] int v;                                 // { 
> dg-warning "'constexpr_only' attribute ignored" }
> +[[gnu::constexpr_only]] constexpr int bar () { return 42; }
> +constexpr int bar () { return 42; }                            // { dg-error 
> "redefinition of 'constexpr int bar\\\(\\\)'" }
> +
> +namespace
> +{
> +  [[gnu::constexpr_only]] constexpr int baz () { return 42; }
> +}
> +
> +constexpr int a = baz ();
> +int (*b) () = baz;
>
>         Jakub
>

Reply via email to