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
>