Hi! As part of the PR86590 discussions that the current libstdc++ __constant_string is extremely costly, because we don't fold the __builtin_constant_p in the loop early enough and because Richard doesn't want __builtin_early_constant_p, this patch is an attempt to implement P0595R1 as a builtin (which will be likely needed anyway, so that libstdc++ will be able to use it even without -std=c++2a).
When evaluating (outermost) constant expressions with !ctx->quiet (i.e. when we require constant expressions) as well in the special case of reference initializers, or const non-volatile decl initializers, or TREE_STATIC decl initializers, the builtin is folded into true (for the ctx->quiet special cases if the constexpr evaluation doesn't turn a constant expression, we retry without the special flag), otherwise folding of it is deferred and it is flagged as non-constant expression, and folding during gimplification folds it into false. Not really sure about potential_constant_expression, for if it attempts to evaluate the condition with ctx->quiet true and the patch doesn't set the magic flag to fold the builtin to true in that case; it is unclear to me if in case the condition includes std::is_constant_evaluated () call we need to fold it also to true or not depending on what will we be evaluating later on. Bootstrapped/regtested on x86_64-linux. 2018-07-22 Jakub Jelinek <ja...@redhat.com> P0595R1 - is_constant_evaluated * builtins.def (BUILT_IN_IS_CONSTANT_EVALUATED): New C++ builtin. * builtins.c (fold_builtin_0): Fold BUILT_IN_IS_CONSTANT_EVALUATED to 0. cp/ * cp-tree.h (maybe_constant_init): Add const_evaluated argument. * typeck2.c (store_init_value): Pass true as new argument to maybe_constant_init. * constexpr.c (struct constexpr_ctx): Add const_evaluated field. (cxx_eval_builtin_function_call): Handle BUILT_IN_IS_CONSTANT_EVALUATED. (cxx_eval_outermost_constant_expr): Add const_evaluated argument, initialize const_evaluated field in ctx. If the result is TREE_CONSTANT and non_constant_p, retry with const_evaluated false if it was true. (is_sub_constant_expr): Initialize const_evaluated_field in ctx. (cxx_constant_value): Pass true as const_evaluated to cxx_eval_outermost_constant_expr. (maybe_constant_value): Pass false as const_evaluated to cxx_eval_outermost_constant_expr. (fold_non_dependent_expr): Likewise. (maybe_constant_init_1): Add const_evaluated argument, pass it down to cxx_eval_outermost_constant_expr. Pass !allow_non_constant instead of false as strict to cxx_eval_outermost_constant_expr. (maybe_constant_init): Add const_evaluated argument, pass it down to maybe_constant_init_1. (cxx_constant_init): Pass true as const_evaluated to maybe_constant_init_1. * cp-gimplify.c (cp_fold): Don't fold BUILT_IN_IS_CONSTANT_EVALUATED calls. lto/ * lto-lang.c (c_dialect_cxx): Define. ada/ * gcc-interface/utils.c (c_dialect_cxx): Define. brig/ * brig-lang.c (c_dialect_cxx): Define. testsuite/ * g++.dg/cpp2a/is-constant-evaluated1.C: New test. --- gcc/builtins.def.jj 2018-06-20 08:15:34.179862153 +0200 +++ gcc/builtins.def 2018-07-20 12:03:10.254453811 +0200 @@ -974,6 +974,11 @@ DEF_EXT_LIB_BUILTIN (BUILT_IN_PRINTF_ DEF_EXT_LIB_BUILTIN (BUILT_IN_VFPRINTF_CHK, "__vfprintf_chk", BT_FN_INT_FILEPTR_INT_CONST_STRING_VALIST_ARG, ATTR_NONNULL_1_FORMAT_PRINTF_3_0) DEF_EXT_LIB_BUILTIN (BUILT_IN_VPRINTF_CHK, "__vprintf_chk", BT_FN_INT_INT_CONST_STRING_VALIST_ARG, ATTR_FORMAT_PRINTF_2_0) +/* C++ __builtin_is_constant_evaluated. */ +DEF_BUILTIN (BUILT_IN_IS_CONSTANT_EVALUATED, "__builtin_is_constant_evaluated", + BUILT_IN_NORMAL, BT_FN_BOOL, BT_LAST, false, false, false, + ATTR_CONST_NOTHROW_LEAF_LIST, true, c_dialect_cxx ()) + /* Profiling hooks. */ DEF_BUILTIN (BUILT_IN_PROFILE_FUNC_ENTER, "__cyg_profile_func_enter", BUILT_IN_NORMAL, BT_FN_VOID_PTR_PTR, BT_LAST, false, false, false, ATTR_NULL, true, true) --- gcc/builtins.c.jj 2018-07-16 23:24:51.306429546 +0200 +++ gcc/builtins.c 2018-07-20 12:09:13.278818768 +0200 @@ -9104,6 +9104,10 @@ fold_builtin_0 (location_t loc, tree fnd case BUILT_IN_CLASSIFY_TYPE: return fold_builtin_classify_type (NULL_TREE); + case BUILT_IN_IS_CONSTANT_EVALUATED: + /* The C++ FE can evaluate this to something other than false. */ + return boolean_false_node; + default: break; } --- gcc/cp/cp-tree.h.jj 2018-07-18 22:57:10.657780293 +0200 +++ gcc/cp/cp-tree.h 2018-07-20 18:10:33.416409798 +0200 @@ -7536,7 +7536,7 @@ extern bool require_potential_rvalue_con extern tree cxx_constant_value (tree, tree = NULL_TREE); extern tree cxx_constant_init (tree, tree = NULL_TREE); extern tree maybe_constant_value (tree, tree = NULL_TREE); -extern tree maybe_constant_init (tree, tree = NULL_TREE); +extern tree maybe_constant_init (tree, tree = NULL_TREE, bool = false); extern tree fold_non_dependent_expr (tree, tsubst_flags_t = tf_warning_or_error); extern tree fold_simple (tree); extern bool is_sub_constant_expr (tree); --- gcc/cp/typeck2.c.jj 2018-06-29 09:38:17.786306395 +0200 +++ gcc/cp/typeck2.c 2018-07-20 18:11:36.649492893 +0200 @@ -837,7 +837,7 @@ store_init_value (tree decl, tree init, value = cxx_constant_init (value, decl); } else - value = maybe_constant_init (value, decl); + value = maybe_constant_init (value, decl, true); if (TREE_CODE (value) == CONSTRUCTOR && cp_has_mutable_p (type)) /* Poison this CONSTRUCTOR so it can't be copied to another constexpr variable. */ --- gcc/cp/constexpr.c.jj 2018-06-25 14:51:23.094989194 +0200 +++ gcc/cp/constexpr.c 2018-07-20 19:16:40.504036595 +0200 @@ -1007,6 +1007,8 @@ struct constexpr_ctx { /* Whether we are strictly conforming to constant expression rules or trying harder to get a constant value. */ bool strict; + /* Whether __builtin_is_constant_evaluated () should be true. */ + bool const_evaluated; }; /* A table of all constexpr calls that have been evaluated by the @@ -1184,6 +1186,18 @@ cxx_eval_builtin_function_call (const co return t; } + /* For __builtin_is_constant_evaluated, defer it if not ctx->const_evaluated, + otherwise fold it to true. */ + if (DECL_FUNCTION_CODE (fun) == BUILT_IN_IS_CONSTANT_EVALUATED) + { + if (!ctx->const_evaluated) + { + *non_constant_p = true; + return t; + } + return boolean_true_node; + } + /* Be permissive for arguments to built-ins; __builtin_constant_p should return constant false for a non-constant argument. */ constexpr_ctx new_ctx = *ctx; @@ -4884,7 +4898,9 @@ instantiate_constexpr_fns (tree t) static tree cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant, - bool strict = true, tree object = NULL_TREE) + bool strict = true, + bool const_evaluated = false, + tree object = NULL_TREE) { auto_timevar time (TV_CONSTEXPR); @@ -4893,7 +4909,8 @@ cxx_eval_outermost_constant_expr (tree t hash_map<tree,tree> map; constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL, NULL, - allow_non_constant, strict }; + allow_non_constant, strict, + const_evaluated || !allow_non_constant }; tree type = initialized_type (t); tree r = t; @@ -4982,6 +4999,12 @@ cxx_eval_outermost_constant_expr (tree t return error_mark_node; else if (non_constant_p && TREE_CONSTANT (r)) { + /* If __builtin_is_constant_evaluated () was evaluated to true + and the result is not a valid constant expression, we need to + punt. */ + if (const_evaluated) + return cxx_eval_outermost_constant_expr (t, true, strict, + false, object); /* This isn't actually constant, so unset TREE_CONSTANT. Don't clear TREE_CONSTANT on ADDR_EXPR, as the middle-end requires it to be set if it is invariant address, even when it is not @@ -5027,7 +5049,8 @@ is_sub_constant_expr (tree t) bool overflow_p = false; hash_map <tree, tree> map; - constexpr_ctx ctx = { NULL, &map, NULL, NULL, NULL, NULL, true, true }; + constexpr_ctx ctx + = { NULL, &map, NULL, NULL, NULL, NULL, true, true, false }; instantiate_constexpr_fns (t); cxx_eval_constant_expression (&ctx, t, false, &non_constant_p, @@ -5042,7 +5065,7 @@ is_sub_constant_expr (tree t) tree cxx_constant_value (tree t, tree decl) { - return cxx_eval_outermost_constant_expr (t, false, true, decl); + return cxx_eval_outermost_constant_expr (t, false, true, true, decl); } /* Helper routine for fold_simple function. Either return simplified @@ -5148,7 +5171,7 @@ maybe_constant_value (tree t, tree decl) if (tree *cached = cv_cache->get (t)) return *cached; - r = cxx_eval_outermost_constant_expr (t, true, true, decl); + r = cxx_eval_outermost_constant_expr (t, true, true, false, decl); gcc_checking_assert (r == t || CONVERT_EXPR_P (t) || TREE_CODE (t) == VIEW_CONVERT_EXPR @@ -5222,7 +5245,8 @@ fold_non_dependent_expr (tree t, return t; } - tree r = cxx_eval_outermost_constant_expr (t, true, true, NULL_TREE); + tree r = cxx_eval_outermost_constant_expr (t, true, true, false, + NULL_TREE); /* cp_tree_equal looks through NOPs, so allow them. */ gcc_checking_assert (r == t || CONVERT_EXPR_P (t) @@ -5246,7 +5270,8 @@ fold_non_dependent_expr (tree t, than wrapped in a TARGET_EXPR. */ static tree -maybe_constant_init_1 (tree t, tree decl, bool allow_non_constant) +maybe_constant_init_1 (tree t, tree decl, bool allow_non_constant, + bool const_evaluated) { if (!t) return t; @@ -5264,7 +5289,9 @@ maybe_constant_init_1 (tree t, tree decl else if (CONSTANT_CLASS_P (t) && allow_non_constant) /* No evaluation needed. */; else - t = cxx_eval_outermost_constant_expr (t, allow_non_constant, false, decl); + t = cxx_eval_outermost_constant_expr (t, allow_non_constant, + !allow_non_constant, + const_evaluated, decl); if (TREE_CODE (t) == TARGET_EXPR) { tree init = TARGET_EXPR_INITIAL (t); @@ -5277,9 +5304,9 @@ maybe_constant_init_1 (tree t, tree decl /* Wrapper for maybe_constant_init_1 which permits non constants. */ tree -maybe_constant_init (tree t, tree decl) +maybe_constant_init (tree t, tree decl, bool const_evaluated) { - return maybe_constant_init_1 (t, decl, true); + return maybe_constant_init_1 (t, decl, true, const_evaluated); } /* Wrapper for maybe_constant_init_1 which does not permit non constants. */ @@ -5287,7 +5314,7 @@ maybe_constant_init (tree t, tree decl) tree cxx_constant_init (tree t, tree decl) { - return maybe_constant_init_1 (t, decl, false); + return maybe_constant_init_1 (t, decl, false, true); } #if 0 --- gcc/cp/cp-gimplify.c.jj 2018-07-20 11:39:15.543037497 +0200 +++ gcc/cp/cp-gimplify.c 2018-07-20 12:21:29.869568404 +0200 @@ -2478,6 +2478,12 @@ cp_fold (tree x) && DECL_DECLARED_CONSTEXPR_P (current_function_decl)) nw = 1; + /* Defer folding __builtin_is_constant_evaluated. */ + if (callee + && DECL_BUILT_IN_CLASS (callee) == BUILT_IN_NORMAL + && DECL_FUNCTION_CODE (callee) == BUILT_IN_IS_CONSTANT_EVALUATED) + break; + x = copy_node (x); m = call_expr_nargs (x); --- gcc/lto/lto-lang.c.jj 2018-06-13 10:05:49.991124932 +0200 +++ gcc/lto/lto-lang.c 2018-07-20 12:29:28.087207392 +0200 @@ -246,6 +246,7 @@ static GTY(()) tree signed_size_type_nod int flag_isoc94; int flag_isoc99; int flag_isoc11; +#define c_dialect_cxx() 0 /* Attribute handlers. */ --- gcc/ada/gcc-interface/utils.c.jj 2018-07-17 12:48:21.096585583 +0200 +++ gcc/ada/gcc-interface/utils.c 2018-07-21 09:26:01.365363959 +0200 @@ -6458,6 +6458,7 @@ def_builtin_1 (enum built_in_function fn static int flag_isoc94 = 0; static int flag_isoc99 = 0; static int flag_isoc11 = 0; +#define c_dialect_cxx() 0 /* Install what the common builtins.def offers. */ --- gcc/brig/brig-lang.c.jj 2018-05-06 23:12:54.400623542 +0200 +++ gcc/brig/brig-lang.c 2018-07-21 09:25:37.121341436 +0200 @@ -587,6 +587,7 @@ static GTY(()) tree signed_size_type_nod int flag_isoc94; int flag_isoc99; int flag_isoc11; +#define c_dialect_cxx() 0 static void def_fn_type (builtin_type def, builtin_type ret, bool var, int n, ...) --- gcc/testsuite/g++.dg/cpp2a/is-constant-evaluated1.C.jj 2018-07-20 18:43:58.240798640 +0200 +++ gcc/testsuite/g++.dg/cpp2a/is-constant-evaluated1.C 2018-07-20 19:09:44.712391964 +0200 @@ -0,0 +1,64 @@ +// P0595R1 +// { dg-do compile { target c++14 } } + +template<int N> struct X { int v = N; }; +X<__builtin_is_constant_evaluated ()> x; // type X<true> +int y = 4; +int a = __builtin_is_constant_evaluated () ? y : 1; // initializes a to 1 +int b = __builtin_is_constant_evaluated () ? 2 : y; // initializes b to 2 +int c = y + (__builtin_is_constant_evaluated () ? 2 : y); // initializes c to 2*y + +struct false_type { static constexpr bool value = false; }; +struct true_type { static constexpr bool value = true; }; +template<class T, class U> +struct is_same : false_type {}; +template<class T> +struct is_same<T, T> : true_type {}; + +constexpr int +foo (int x) +{ + const int n = __builtin_is_constant_evaluated () ? 13 : 17; // n == 13 + int m = __builtin_is_constant_evaluated () ? 13 : 17; // m might be 13 or 17 (see below) + char arr[n] = {}; // char[13] + return m + sizeof (arr) + x; +} + +constexpr int +bar () +{ + const int n = __builtin_is_constant_evaluated() ? 13 : 17; + X<n> x1; + X<__builtin_is_constant_evaluated() ? 13 : 17> x2; + static_assert (is_same<decltype (x1), decltype (x2)>::value, "x1/x2's type"); + return x1.v + x2.v; +} + +int p = foo (0); // m == 13; initialized to 26 +int q = p + foo (0); // m == 17 for this call; initialized to 56 +static_assert (bar () == 26, "bar"); + +struct S { int a, b; }; + +S s = { __builtin_is_constant_evaluated () ? 2 : 3, y }; +S t = { __builtin_is_constant_evaluated () ? 2 : 3, 4 }; + +static_assert (is_same<decltype (x), X<true> >::value, "x's type"); + +int +main () +{ + if (a != 1 || b != 2 || c != 8 || p != 26 || q != 56) + __builtin_abort (); + if (s.a != 3 || s.b != 4 || t.a != 2 || t.b != 4) + __builtin_abort (); + if (foo (y) != 34) + __builtin_abort (); +#if __cplusplus >= 201703L + if constexpr (foo (0) != 26) + __builtin_abort (); +#endif + constexpr int w = foo (0); + if (w != 26) + __builtin_abort (); +} Jakub