On 11/2/23 11:28, Marek Polacek wrote:
On Sat, Oct 14, 2023 at 12:56:11AM -0400, Jason Merrill wrote:
On 10/10/23 13:20, Marek Polacek wrote:
On Tue, Aug 29, 2023 at 03:26:46PM -0400, Jason Merrill wrote:
On 8/23/23 15:49, Marek Polacek wrote:
+struct A {
+ int x;
+ int y = id(x);
+};
+
+template<class T>
+constexpr int k(int) { // k<int> is not an immediate function because
A(42) is a
+ return A(42).y; // constant expression and thus not
immediate-escalating
+}
Needs use(s) of k to test the comment.
True, and that revealed what I think is a bug in the standard.
In the test I'm saying:
// ??? [expr.const]#example-9 says:
// k<int> is not an immediate function because A(42) is a
// constant expression and thus not immediate-escalating
// But I think the call to id(x) is *not* a constant expression and thus
// it is an immediate-escalating expression. Therefore k<int> *is*
// an immediate function. So we get the error below. clang++ agrees.
id(x) is not a constant expression because x isn't constant.
Not when considering id(x) by itself, but in the evaluation of A(42), the
member x has just been initialized to constant 42. And A(42) is
constant-evaluated because "An aggregate initialization is an immediate
invocation if it evaluates a default member initializer that has a
subexpression that is an immediate-escalating expression."
I assume clang doesn't handle this passage properly yet because it was added
during core review of the paper, for parity between aggregate initialization
and constructor escalation.
This can be a follow-up patch.
I see. So the fix will be to, for the aggregate initialization case, pass
the whole A(42).y thing to cxx_constant_eval, not just id(x).
Well, A(42) is the immediate invocation, the .y is not part of it.
I suppose some
functions cannot possibly be promoted because they don't contain
any CALL_EXPRs. So we may be able to rule them out while doing
cp_fold_r early.
Yes. Or, the only immediate-escalating functions referenced have already
been checked.
It looks like you haven't pursued this yet? One implementation thought:
maybe_store_cfun... could stop skipping immediate_escalating_function_p
(current_function_decl), and after we're done folding if the current
function isn't in the hash_set we can go ahead and set
DECL_ESCALATION_CHECKED_P?
We can also do some escalation during constexpr evaluation: all the
functions involved need to be instantiated for the evaluation, and if we
encounter an immediate-escalating expression while evaluating a call to an
immediate-escalating function, we can promote it then. Though we can't
necessarily mark it as not needing promotion, as there might be i-e exprs in
branches that the particular evaluation doesn't take.
I've tried but I didn't get anywhere. The patch was basically
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -2983,7 +2983,13 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree
t,
} fb (new_call.bindings);
if (*non_constant_p)
- return t;
+ {
+ if (cxx_dialect >= cxx20
+ && ctx->manifestly_const_eval == mce_false
+ && DECL_IMMEDIATE_FUNCTION_P (fun))
+ maybe_promote_function_to_consteval (current_function_decl);
+ return t;
+ }
/* We can't defer instantiating the function any longer. */
if (!DECL_INITIAL (fun)
but since I have to check mce_false, it didn't do anything useful
in practice (that is, it wouldn't escalate anything in my tests).
Makes sense.
@@ -55,6 +64,8 @@ enum fold_flags {
ff_mce_false = 1 << 1,
/* Whether we're being called from cp_fold_immediate. */
ff_fold_immediate = 1 << 2,
+ /* Whether we're escalating immediate-escalating functions. */
+ ff_escalating = 1 << 3,
If we always escalate when we see a known immediate invocation, this means
recurse. And maybe we could use at_eof for that?
Yes. Though, at_eof == 1 doesn't imply that all templates have been
instantiated, only that we got to c_parse_final_cleanups. Rather than
keeping the ff_ flag, I've updated at_eof to mean that 2 signals that
all templates have been instantiated, and 3 that we're into cgraph
territory. (Might should use an enum rather than magic numbers.)
Sounds good.
};
using fold_flags_t = int;
@@ -428,6 +439,176 @@ lvalue_has_side_effects (tree e)
return TREE_SIDE_EFFECTS (e);
}
+/* Return true if FN is an immediate-escalating function. */
+
+static bool
+immediate_escalating_function_p (tree fn)
+{
+ if (!fn || !flag_immediate_escalation)
+ return false;
+
+ gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL);
Maybe check DECL_IMMEDIATE_FUNCTION_P early, rather than multiple times
below...
+ /* An immediate-escalating function is
+ -- the call operator of a lambda that is not declared with the consteval
+ specifier */
+ if (LAMBDA_FUNCTION_P (fn) && !DECL_IMMEDIATE_FUNCTION_P (fn))
+ return true;
+ /* -- a defaulted special member function that is not declared with the
+ consteval specifier */
+ special_function_kind sfk = special_memfn_p (fn);
+ if (sfk != sfk_none
+ && DECL_DEFAULTED_FN (fn)
+ && !DECL_IMMEDIATE_FUNCTION_P (fn))
+ return true;
+ /* -- a function that results from the instantiation of a templated entity
+ defined with the constexpr specifier. */
+ return is_instantiation_of_constexpr (fn);
+}
+
+/* Promote FN to an immediate function, including its clones, if it is
+ an immediate-escalating function. Return true if we did promote;
+ false otherwise. */
+
+static bool
+maybe_promote_function_to_consteval (tree fn)
+{
+ if (fn
+ && !DECL_IMMEDIATE_FUNCTION_P (fn)
...and in all the callers?
Ah, beautiful. And likewise for DECL_ESCALATION_CHECKED_P.
Agreed, but the function comment should mention that it returns false
for an immediate-escalating function that is no longer a candidate for
escalation. And maybe the function name should be something like
unchecked_immediate_escalating_function_p to clarify that it implements
something different from the term in the standard?
+ return;
+
+ vec_safe_push (deferred_escalating_exprs, current_function_decl);
+}
+
+/* Find an immediate-escalating expression or conversion in *TP.
+ If DATA_ is non-null, this function will promote function to
+ consteval as it goes; otherwise, we're just looking for what
+ made a function consteval, for diagnostic purposes. This
+ function assumes that *TP was instantiated. */
+
+static tree
+find_escalating_expr_r (tree *tp, int *walk_subtrees, void *data)
Can this merge with cp_fold_immediate_r? They seem to be doing essentially
the same thing.
I did not do it, sorry. I thought it would not make things more readable
as I don't see a whole lot of code to share.
The code is significantly different, sure, but both are walking the tree
to look for immediate-escalating expressions. Why do we need two of those?
+ if (code == ADDR_EXPR || !data->pset.add (stmt))
+ {
+ taking_address_of_imm_fn_error (stmt, decl);
+ *stmt_p = build_zero_cst (TREE_TYPE (stmt));
+ }
+ /* If we're giving hard errors, continue the walk rather than
+ bailing out after the first error. */
+ break;
+ }
+ return error_mark_node;
+ }
+ /* Not consteval yet, but could become one, in which case it's invalid
+ to take its address. */
+ else if (!(data->flags & ff_fold_immediate)
Why check ff_fold_immediate? Don't we want to do deferred checking of
immediate invocations in discarded branches?
On reflection, I was papering over a different problem here.
Inserting a new element to a hash table while we're traversing it is
dangerous because a new element might mean calling hash_table::expand
which creates a new table, std::moves the elements, and calls ggc_free
on the old table. Then iterating to the next element is going to crash
because it has been freed.
We were trying to add a new element while being called from
process_pending_immediate_escalating_fns. This should not happen.
The problem was that the DECL_ESCALATION_CHECKED_P flag may not have
been properly set for functions without any CALL_EXPRs in them, and that
immediate_escalating_function_p was not checking it, either.
The code is just
+ else if (immediate_escalating_function_p (decl))
+ remember_escalating_expr (stmt);
now.
The problem triggered in Wfree-nonheap-object-3.C. Unfortunately, I could
not reduce it very far so I don't have a test suitable for the testsuite.
The tricky thing is that the problem only manifests when we expand() a hash
table which is unlikely to happen in a small test and I don't see a --param
I could use.
You could save the value of deferred_escalating_exprs->elements() before
the loop and if it changes, abort in checking mode or goto back before
the loop when not checking?
It also seems odd that the ADDR_EXPR case calls vec_safe_push
(deferred_escalating_exprs, while the CALL_EXPR case calls
maybe_store_cfun_for_late_checking, why the different handling?
maybe_store_cfun_for_late_checking saves current_function_decl
so that we can check:
void g (int i) {
fn (i); // error if fn promotes to consteval
}
Yes, but why don't we want the same handling for ADDR_EXPR?
Jason