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.
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