On Thu, Mar 26, 2026 at 08:40:36AM +0100, Jakub Jelinek wrote: > The following patch attempts to implement the > P3842R1 - A conservative fix for constexpr uncaught_exceptions() > and current_exception() > paper. std::current_exception() and std::uncaught_exceptions() aren't > constexpr anymore (admittedly, they weren't declared that way in the > headers before either, but the FE magically treated them as such), > instead two new FE builtins are introduced so that one can get their > behavior before this patch when needed. So, at constant evaluation > time they are constant evaluated and if they are not folded away, > they are transformed into std::current_exception() or > std::uncaught_exceptions() runtime calls during gimplification. > > Compared to the paper, I had to also deal with nested_exception default > ctor which also calls current_exception. > > Lightly tested on x86_64-linux so far.
Here is an updated version of the patch which implements P3842R2. In particular, reverts all nested_exception.h changes over the last year and removes it from the constexpr-eh3.C test. The proposed __builtin_current_exception() is I believe exactly the <it>current-exception</i>() in the paper. 2026-03-27 Jakub Jelinek <[email protected]> gcc/cp/ * cp-tree.h (enum cp_built_in_function): Add CP_BUILT_IN_CURRENT_EXCEPTION and CP_BUILT_IN_UNCAUGHT_EXCEPTIONS. * tree.cc (builtin_valid_in_constant_expr_p): Handle those. * decl.cc (cxx_init_decl_processing): Build decls for CP_BUILT_IN_CURRENT_EXCEPTION and CP_BUILT_IN_UNCAUGHT_EXCEPTIONS. (require_deduced_type): Deduce CP_BUILT_IN_CURRENT_EXCEPTION return type. * cp-gimplify.cc (cp_gimplify_expr): Handle CP_BUILT_IN_CURRENT_EXCEPTION and CP_BUILT_IN_UNCAUGHT_EXCEPTIONS. * constexpr.cc (enum cxa_builtin): Remove STD_UNCAUGHT_EXCEPTIONS and STD_CURRENT_EXCEPTION, add BUILTIN_UNCAUGHT_EXCEPTIONS and BUILTIN_CURRENT_EXCEPTION, renumber some enumerators. (cxx_cxa_builtin_fn_p): Don't check for std::uncaught_exceptions nor std::current_exception. (cxx_eval_cxa_builtin_fn): Handle BUILTIN_UNCAUGHT_EXCEPTIONS and BUILTIN_CURRENT_EXCEPTION instead of STD_UNCAUGHT_EXCEPTIONS and STD_CURRENT_EXCEPTION. (cxx_eval_builtin_function_call): Handle CP_BUILT_IN_CURRENT_EXCEPTION and CP_BUILT_IN_UNCAUGHT_EXCEPTIONS. Do just one fndecl_builtin_p check for BUILT_IN_FRONTEND and switch on DECL_FE_FUNCTION_CODE. gcc/testsuite/ * g++.dg/cpp26/constexpr-eh3.C: Use __builtin_current_exception() instead of std::current_exception() and __builttin_uncaught_exceptions() instead of std::uncaught_exceptions(). Remove nested_exception related parts of the test. * g++.dg/cpp26/constexpr-eh8.C: Use __builtin_current_exception() instead of std::current_exception(). libstdc++-v3/ * libsupc++/exception_ptr.h (std::make_exception_ptr): Use __builtin_current_exception() instead of current_exception() if the builtin exists. * libsupc++/nested_exception.h: Revert 2026-01-07 and 2025-07-10 changes. --- gcc/cp/cp-tree.h.jj 2026-03-27 10:17:14.017332648 +0100 +++ gcc/cp/cp-tree.h 2026-03-27 10:20:42.997381886 +0100 @@ -7135,6 +7135,8 @@ enum cp_built_in_function { CP_BUILT_IN_EH_PTR_ADJUST_REF, CP_BUILT_IN_IS_STRING_LITERAL, CP_BUILT_IN_CONSTEXPR_DIAG, + CP_BUILT_IN_CURRENT_EXCEPTION, + CP_BUILT_IN_UNCAUGHT_EXCEPTIONS, CP_BUILT_IN_LAST }; --- gcc/cp/tree.cc.jj 2026-03-27 10:17:14.036332338 +0100 +++ gcc/cp/tree.cc 2026-03-27 10:20:42.998245213 +0100 @@ -570,6 +570,8 @@ builtin_valid_in_constant_expr_p (const_ case CP_BUILT_IN_EH_PTR_ADJUST_REF: case CP_BUILT_IN_IS_STRING_LITERAL: case CP_BUILT_IN_CONSTEXPR_DIAG: + case CP_BUILT_IN_CURRENT_EXCEPTION: + case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS: return true; default: break; --- gcc/cp/decl.cc.jj 2026-03-27 10:17:14.019332616 +0100 +++ gcc/cp/decl.cc 2026-03-27 10:20:42.999379349 +0100 @@ -5627,6 +5627,20 @@ cxx_init_decl_processing (void) CP_BUILT_IN_EH_PTR_ADJUST_REF, BUILT_IN_FRONTEND, NULL, NULL_TREE); set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF); + + /* Similar case to __builtin_source_locaiton above. The concrete + return type is std::exception_ptr, but we can't form the type + at this point, so it is deduced later. */ + decl = add_builtin_function ("__builtin_current_exception", + auto_ftype, CP_BUILT_IN_CURRENT_EXCEPTION, + BUILT_IN_FRONTEND, NULL, NULL_TREE); + set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF); + + tree int_ftype = build_function_type_list (integer_type_node, NULL_TREE); + decl = add_builtin_function ("__builtin_uncaught_exceptions", + int_ftype, CP_BUILT_IN_UNCAUGHT_EXCEPTIONS, + BUILT_IN_FRONTEND, NULL, NULL_TREE); + set_call_expr_flags (decl, ECF_PURE | ECF_NOTHROW | ECF_LEAF); } decl @@ -21226,8 +21240,8 @@ require_deduced_type (tree decl, tsubst_ if (undeduced_auto_decl (decl)) { if (TREE_CODE (decl) == FUNCTION_DECL - && fndecl_built_in_p (decl, BUILT_IN_FRONTEND) - && DECL_FE_FUNCTION_CODE (decl) == CP_BUILT_IN_SOURCE_LOCATION) + && fndecl_built_in_p (decl, CP_BUILT_IN_SOURCE_LOCATION, + BUILT_IN_FRONTEND)) { /* Set the return type of __builtin_source_location. */ tree type = get_source_location_impl_type (); @@ -21242,6 +21256,33 @@ require_deduced_type (tree decl, tsubst_ return true; } + if (TREE_CODE (decl) == FUNCTION_DECL + && fndecl_built_in_p (decl, CP_BUILT_IN_CURRENT_EXCEPTION, + BUILT_IN_FRONTEND)) + { + /* Set the return type of __builtin_current_exception. */ + tree name = get_identifier ("exception_ptr"); + tree eptr = lookup_qualified_name (std_node, name); + tree fld; + if (TREE_CODE (eptr) != TYPE_DECL + || !CLASS_TYPE_P (TREE_TYPE (eptr)) + || !COMPLETE_TYPE_P (TREE_TYPE (eptr)) + || !(fld = next_aggregate_field (TYPE_FIELDS (TREE_TYPE (eptr)))) + || DECL_ARTIFICIAL (fld) + || TREE_CODE (TREE_TYPE (fld)) != POINTER_TYPE + || next_aggregate_field (DECL_CHAIN (fld)) + || !tree_int_cst_equal (TYPE_SIZE (TREE_TYPE (eptr)), + TYPE_SIZE (TREE_TYPE (fld)))) + { + error ("%qs used without %qs declaration", + "__builtin_current_exception", "std::exception_ptr"); + return false; + } + + apply_deduced_return_type (decl, TREE_TYPE (eptr)); + return true; + } + if (warning_suppressed_p (decl) && seen_error ()) /* We probably already complained about deduction failure. */; else if (complain & tf_error) --- gcc/cp/cp-gimplify.cc.jj 2026-03-27 10:17:14.016332665 +0100 +++ gcc/cp/cp-gimplify.cc 2026-03-27 10:20:43.000506227 +0100 @@ -979,6 +979,32 @@ cp_gimplify_expr (tree *expr_p, gimple_s "__builtin_eh_ptr_adjust_ref"); *expr_p = void_node; break; + case CP_BUILT_IN_CURRENT_EXCEPTION: + case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS: + { + const char *name + = (DECL_FE_FUNCTION_CODE (decl) + == CP_BUILT_IN_CURRENT_EXCEPTION + ? "current_exception" : "uncaught_exceptions"); + tree newdecl = lookup_qualified_name (std_node, name); + if (error_operand_p (newdecl)) + *expr_p = build_zero_cst (TREE_TYPE (*expr_p)); + else if (TREE_CODE (newdecl) != FUNCTION_DECL + || !same_type_p (TREE_TYPE (TREE_TYPE (newdecl)), + TREE_TYPE (TREE_TYPE (decl))) + || (TYPE_ARG_TYPES (TREE_TYPE (newdecl)) + != void_list_node)) + { + error_at (EXPR_LOCATION (*expr_p), + "unexpected %<std::%s%> declaration", + name); + *expr_p = build_zero_cst (TREE_TYPE (*expr_p)); + } + else + *expr_p = build_call_expr_loc (EXPR_LOCATION (*expr_p), + newdecl, 0); + break; + } case CP_BUILT_IN_IS_STRING_LITERAL: *expr_p = fold_builtin_is_string_literal (EXPR_LOCATION (*expr_p), --- gcc/cp/constexpr.cc.jj 2026-03-27 10:17:14.014332697 +0100 +++ gcc/cp/constexpr.cc 2026-03-27 10:20:43.001205329 +0100 @@ -1804,10 +1804,10 @@ enum cxa_builtin { CXA_BAD_CAST = 8, CXA_BAD_TYPEID = 9, CXA_THROW_BAD_ARRAY_NEW_LENGTH = 10, - STD_UNCAUGHT_EXCEPTIONS = 11, - STD_CURRENT_EXCEPTION = 12, - STD_RETHROW_EXCEPTION = 13, - BUILTIN_EH_PTR_ADJUST_REF = 14 + STD_RETHROW_EXCEPTION = 11, + BUILTIN_EH_PTR_ADJUST_REF = 12, + BUILTIN_UNCAUGHT_EXCEPTIONS = 13, + BUILTIN_CURRENT_EXCEPTION = 14 }; /* Return cxa_builtin if FNDECL is a __cxa_* function handled as @@ -1822,10 +1822,6 @@ cxx_cxa_builtin_fn_p (tree fndecl) { if (!decl_in_std_namespace_p (fndecl)) return CXA_NONE; - if (id_equal (DECL_NAME (fndecl), "uncaught_exceptions")) - return STD_UNCAUGHT_EXCEPTIONS; - if (id_equal (DECL_NAME (fndecl), "current_exception")) - return STD_CURRENT_EXCEPTION; if (id_equal (DECL_NAME (fndecl), "rethrow_exception")) return STD_RETHROW_EXCEPTION; return CXA_NONE; @@ -2186,7 +2182,7 @@ cxx_eval_cxa_builtin_fn (const constexpr *jump_target = var; } return void_node; - case STD_UNCAUGHT_EXCEPTIONS: + case BUILTIN_UNCAUGHT_EXCEPTIONS: if (nargs != 0) goto invalid_nargs; /* Similarly to __builtin_is_constant_evaluated (), we don't @@ -2202,7 +2198,7 @@ cxx_eval_cxa_builtin_fn (const constexpr } return build_int_cst (integer_type_node, ctx->global->uncaught_exceptions); - case STD_CURRENT_EXCEPTION: + case BUILTIN_CURRENT_EXCEPTION: if (nargs != 0) goto invalid_nargs; else @@ -2489,40 +2485,53 @@ cxx_eval_builtin_function_call (const co return t; } - /* For __builtin_is_constant_evaluated, defer it if not - ctx->manifestly_const_eval (as sometimes we try to constant evaluate - without manifestly_const_eval even expressions or parts thereof which - will later be manifestly const_eval evaluated), otherwise fold it to - true. */ - if (fndecl_built_in_p (fun, CP_BUILT_IN_IS_CONSTANT_EVALUATED, - BUILT_IN_FRONTEND)) - { - if (ctx->manifestly_const_eval == mce_unknown) - { - *non_constant_p = true; - return t; - } - return constant_boolean_node (ctx->manifestly_const_eval == mce_true, - boolean_type_node); - } - - if (fndecl_built_in_p (fun, CP_BUILT_IN_SOURCE_LOCATION, BUILT_IN_FRONTEND)) - { - temp_override<tree> ovr (current_function_decl); - if (ctx->call && ctx->call->fundef) - current_function_decl = ctx->call->fundef->decl; - return fold_builtin_source_location (t); - } - - if (fndecl_built_in_p (fun, CP_BUILT_IN_EH_PTR_ADJUST_REF, - BUILT_IN_FRONTEND)) - return cxx_eval_cxa_builtin_fn (ctx, t, BUILTIN_EH_PTR_ADJUST_REF, - fun, non_constant_p, overflow_p, - jump_target); + if (fndecl_built_in_p (fun, BUILT_IN_FRONTEND)) + switch (DECL_FE_FUNCTION_CODE (fun)) + { + case CP_BUILT_IN_IS_CONSTANT_EVALUATED: + /* For __builtin_is_constant_evaluated, defer it if not + ctx->manifestly_const_eval (as sometimes we try to constant evaluate + without manifestly_const_eval even expressions or parts thereof + which will later be manifestly const_eval evaluated), otherwise fold + it to true. */ + if (ctx->manifestly_const_eval == mce_unknown) + { + *non_constant_p = true; + return t; + } + return constant_boolean_node (ctx->manifestly_const_eval == mce_true, + boolean_type_node); - if (fndecl_built_in_p (fun, CP_BUILT_IN_CONSTEXPR_DIAG, BUILT_IN_FRONTEND)) - return cxx_eval_constexpr_diag (ctx, t, non_constant_p, overflow_p, - jump_target); + case CP_BUILT_IN_SOURCE_LOCATION: + { + temp_override<tree> ovr (current_function_decl); + if (ctx->call && ctx->call->fundef) + current_function_decl = ctx->call->fundef->decl; + return fold_builtin_source_location (t); + } + + case CP_BUILT_IN_EH_PTR_ADJUST_REF: + return cxx_eval_cxa_builtin_fn (ctx, t, BUILTIN_EH_PTR_ADJUST_REF, + fun, non_constant_p, overflow_p, + jump_target); + + case CP_BUILT_IN_CURRENT_EXCEPTION: + return cxx_eval_cxa_builtin_fn (ctx, t, BUILTIN_CURRENT_EXCEPTION, + fun, non_constant_p, overflow_p, + jump_target); + + case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS: + return cxx_eval_cxa_builtin_fn (ctx, t, BUILTIN_UNCAUGHT_EXCEPTIONS, + fun, non_constant_p, overflow_p, + jump_target); + + case CP_BUILT_IN_CONSTEXPR_DIAG: + return cxx_eval_constexpr_diag (ctx, t, non_constant_p, overflow_p, + jump_target); + + default: + break; + } int strops = 0; int strret = 0; --- gcc/testsuite/g++.dg/cpp26/constexpr-eh3.C.jj 2026-03-27 10:17:15.649306017 +0100 +++ gcc/testsuite/g++.dg/cpp26/constexpr-eh3.C 2026-03-27 10:25:17.000451093 +0100 @@ -20,12 +20,10 @@ constexpr std::bad_typeid k; constexpr const char *l = k.what (); constexpr std::exception_ptr m = nullptr; static_assert (m == nullptr); -constexpr std::exception_ptr n = std::current_exception (); +constexpr std::exception_ptr n = __builtin_current_exception (); static_assert (n == nullptr); constexpr std::exception_ptr o; static_assert (o == nullptr); -constexpr std::nested_exception p; -static_assert (p.nested_ptr () == nullptr); struct A { virtual ~A () {} }; struct B { virtual void b (); }; @@ -33,20 +31,13 @@ struct C { virtual void c (); }; struct D : private B { virtual void d (); }; struct E { virtual void e (); }; struct F : D, E, private C { virtual void f (); }; -struct G { constexpr G () { if (std::uncaught_exceptions () != 0) asm (""); } }; -struct H { constexpr H () : h (0) {} constexpr ~H () { if (std::uncaught_exceptions () != h) asm (""); } int h; }; -struct I : std::nested_exception { }; -struct J { virtual ~J () noexcept = default; }; -struct K final { }; -struct L : J, std::nested_exception { }; -struct M { }; -struct N : I, L { }; -struct O : private std::nested_exception { }; +struct G { constexpr G () { if (__builtin_uncaught_exceptions () != 0) asm (""); } }; +struct H { constexpr H () : h (0) {} constexpr ~H () { if (__builtin_uncaught_exceptions () != h) asm (""); } int h; }; constexpr int foo (int x) { - if (std::uncaught_exceptions () != 0) + if (__builtin_uncaught_exceptions () != 0) return -1; switch (x) { @@ -58,7 +49,7 @@ foo (int x) } catch (const std::bad_typeid &x) { - if (std::uncaught_exceptions () != 0) + if (__builtin_uncaught_exceptions () != 0) return -1; const char *p = x.what (); return 1; @@ -90,15 +81,15 @@ foo (int x) { H h; h.h = 1; - if (std::current_exception () != nullptr) + if (__builtin_current_exception () != nullptr) return -1; throw G (); } catch (const G &g) { - if (std::uncaught_exceptions () != 0) + if (__builtin_uncaught_exceptions () != 0) return -1; - if (std::current_exception () == nullptr) + if (__builtin_current_exception () == nullptr) return -1; return 3; } @@ -156,7 +147,7 @@ foo (int x) { if (a != 1) return -1; - b = std::current_exception (); + b = __builtin_current_exception (); if (b == nullptr) return -1; try @@ -167,14 +158,14 @@ foo (int x) { if (c != 2L) return -1; - d = std::current_exception (); + d = __builtin_current_exception (); if (d == nullptr || b == d) return -1; } - if (std::current_exception () != b) + if (__builtin_current_exception () != b) return -1; } - if (std::current_exception () != nullptr) + if (__builtin_current_exception () != nullptr) return -1; try { @@ -266,157 +257,6 @@ foo (int x) } return 8; } - case 8: - { - std::nested_exception a; - if (a.nested_ptr () != nullptr) - return -1; - try - { - std::nested_exception b; - if (b.nested_ptr () != nullptr) - return -1; - throw 42; - } - catch (...) - { - std::nested_exception c; - if (c.nested_ptr () != std::current_exception ()) - return -1; - std::nested_exception d = c; - if (d.nested_ptr () != c.nested_ptr ()) - return -1; - c = d; - try - { - c.rethrow_nested (); - } - catch (const int &e) - { - if (e != 42) - return -1; - } - } - return 9; - } - case 9: - try - { - std::throw_with_nested (I ()); - } - catch (const std::nested_exception &a) - { - if (a.nested_ptr () != nullptr) - return -1; - try - { - throw; - } - catch (const I &) - { - return 10; - } - } - return -1; - case 10: - try - { - std::throw_with_nested (J ()); - } - catch (const std::nested_exception &a) - { - if (a.nested_ptr () != nullptr) - return -1; - try - { - throw; - } - catch (const J &) - { - return 11; - } - } - return -1; - case 11: - try - { - std::throw_with_nested (K ()); - } - catch (const std::nested_exception &) - { - return -1; - } - catch (const K &) - { - return 12; - } - return -1; - case 12: - try - { - throw 42; - } - catch (...) - { - I a; - try - { - std::rethrow_if_nested (a); - } - catch (const int &b) - { - if (b == 42) - return 13; - } - } - return -1; - case 13: - try - { - throw J (); - } - catch (const J &a) - { - std::rethrow_if_nested (a); - return 14; - } - return -1; - case 14: - try - { - throw 42; - } - catch (...) - { - try - { - throw L (); - } - catch (const J &a) - { - try - { - std::rethrow_if_nested (a); - } - catch (const int &b) - { - if (b == 42) - return 15; - } - } - } - return -1; - case 15: - { - std::rethrow_if_nested (1); - M m; - std::rethrow_if_nested (m); - N n; - std::rethrow_if_nested (n); - O o; - std::rethrow_if_nested (o); - return 16; - } default: break; } @@ -431,12 +271,4 @@ static_assert (foo (4) == 5); static_assert (foo (5) == 6); static_assert (foo (6) == 7); static_assert (foo (7) == 8); -static_assert (foo (8) == 9); -static_assert (foo (9) == 10); -static_assert (foo (10) == 11); -static_assert (foo (11) == 12); -static_assert (foo (12) == 13); -static_assert (foo (13) == 14); -static_assert (foo (14) == 15); -static_assert (foo (15) == 16); -static_assert (std::uncaught_exceptions () == 0); +static_assert (__builtin_uncaught_exceptions () == 0); --- gcc/testsuite/g++.dg/cpp26/constexpr-eh8.C.jj 2026-03-27 10:17:15.649306017 +0100 +++ gcc/testsuite/g++.dg/cpp26/constexpr-eh8.C 2026-03-27 10:20:43.002066001 +0100 @@ -13,7 +13,7 @@ foo () } catch (...) { - return std::current_exception (); + return __builtin_current_exception (); } } --- libstdc++-v3/libsupc++/exception_ptr.h.jj 2026-03-27 10:17:22.954186813 +0100 +++ libstdc++-v3/libsupc++/exception_ptr.h 2026-03-27 10:20:43.002212856 +0100 @@ -342,7 +342,11 @@ namespace std _GLIBCXX_VISIBILITY(defaul } catch(...) { +#if __has_builtin(__builtin_current_exception) + return __builtin_current_exception(); +#else return current_exception(); +#endif } #endif return exception_ptr(); --- libstdc++-v3/libsupc++/nested_exception.h.jj 2026-03-27 10:17:22.955186796 +0100 +++ libstdc++-v3/libsupc++/nested_exception.h 2026-03-27 10:22:08.043534576 +0100 @@ -62,25 +62,17 @@ namespace std _GLIBCXX_VISIBILITY(defaul public: /// The default constructor stores the current exception (if any). - _GLIBCXX26_CONSTEXPR nested_exception() noexcept : _M_ptr(current_exception()) { } - _GLIBCXX26_CONSTEXPR nested_exception(const nested_exception&) noexcept = default; - _GLIBCXX26_CONSTEXPR nested_exception& operator=(const nested_exception&) noexcept = default; -#if __cplusplus >= 202400L - [[__gnu__::__gnu_inline__]] - constexpr inline virtual ~nested_exception() noexcept {} -#else virtual ~nested_exception() noexcept; -#endif /// Rethrow the stored exception, or terminate if none was stored. [[noreturn]] - _GLIBCXX26_CONSTEXPR void + void rethrow_nested() const { if (_M_ptr) @@ -89,7 +81,7 @@ namespace std _GLIBCXX_VISIBILITY(defaul } /// Access the stored exception. - _GLIBCXX26_CONSTEXPR exception_ptr + exception_ptr nested_ptr() const noexcept { return _M_ptr; } }; @@ -99,11 +91,11 @@ namespace std _GLIBCXX_VISIBILITY(defaul template<typename _Except> struct _Nested_exception : public _Except, public nested_exception { - _GLIBCXX26_CONSTEXPR explicit _Nested_exception(const _Except& __ex) + explicit _Nested_exception(const _Except& __ex) : _Except(__ex) { } - _GLIBCXX26_CONSTEXPR explicit _Nested_exception(_Except&& __ex) + explicit _Nested_exception(_Except&& __ex) : _Except(static_cast<_Except&&>(__ex)) { } }; @@ -152,7 +144,7 @@ namespace std _GLIBCXX_VISIBILITY(defaul */ template<typename _Tp> [[noreturn]] - _GLIBCXX26_CONSTEXPR inline void + inline void throw_with_nested(_Tp&& __t) { using _Up = typename decay<_Tp>::type; @@ -212,7 +204,7 @@ namespace std _GLIBCXX_VISIBILITY(defaul # if ! __cpp_rtti [[__gnu__::__always_inline__]] #endif - _GLIBCXX26_CONSTEXPR inline void + inline void rethrow_if_nested(const _Ex& __ex) { const _Ex* __ptr = __builtin_addressof(__ex); Jakub
