On 3/1/21 6:09 PM, Patrick Palka wrote:
On Mon, 1 Mar 2021, Jason Merrill wrote:

On 2/28/21 12:40 PM, Patrick Palka wrote:
On Fri, 12 Feb 2021, Jason Merrill wrote:

On 2/10/21 9:41 AM, Patrick Palka wrote:
On Tue, 9 Feb 2021, Jason Merrill wrote:

On 2/8/21 2:03 PM, Patrick Palka wrote:
This sets up the functionality for controlling the initial set of
template parameters to pass to normalization when dealing with a
constraint-expression that is not associated with some constrained
declaration, for instance when normalizing a nested requirement of a
requires expression, or the constraints on a placeholder type.

The main new ingredient here is the data member
norm_info::initial_parms
which can be set by callers of the normalization routines to
communicate
the in-scope template parameters for the supplied
constraint-expression,
rather than always falling back to using current_template_parms.

This patch then uses this functionality in our handling of nested
requirements so that we can delay normalizing them until needed for
satisfaction.  We currently immediately normalize nested
requirements at
parse time, where we have the necessary template context, and cache
the
normal form in their TREE_TYPE node.  With this patch, we now delay
normalization until needed (as with other constraint expressions),
and
instead store the current value of current_template_parms in their
TREE_TYPE node (which we use to restore the template context at
normalization time).

In the subsequent patch, this functionality will also be used to
normalize placeholder type constraints during auto deduction.

gcc/cp/ChangeLog:

        * constraint.cc (build_parameter_mapping): Rely on the caller
to
        determine the in-scope template parameters.
        (norm_info::norm_info): Delegate the one-parameter constructor
        to the two-parameter constructor.  In the two-parameter
        constructor, fold in the definition of make_context, set
        initial_parms appropriately, and don't set the now-removed
        orig_decl member.
        (norm_info::make_context): Remove, now that its only use is
        inlined into the caller.
        (norm_info::update_context): Adjust call to
        build_parameter_mapping to pass in the relevant set of
in-scope
        template parameters.
        (norm_info::ctx_parms): Define this member function.
        (norm_info::context): Initialize to NULL_TREE.
        (norm_info::orig_decl): Remove this data member.
        (norm_info::initial_parms): Define this data member.
        (normalize_atom): Adjust call to build_parameter_mapping to
pass
        in the relevant set of in-scope template parameters.  Use
        info.initial_parms instead of info.orig_decl.
        (normalize_constraint_expression): Define an overload that
takes
        a norm_info object.  Cache the result of normalization.
Define
        the other overload in terms of this one, and handle a
NESTED_REQ
        argument by setting info.initial_parms appropriately.
        (tsubst_nested_requirement): Go through
        satisfy_constraint_expression so that we normalize on demand.
        (finish_nested_requirement): Set the TREE_TYPE of the
NESTED_REQ
        to current_template_parms.
        (diagnose_nested_requirements): Go through
        satisfy_constraint_expression, as with
tsubst_nested_requirement.
---
     gcc/cp/constraint.cc | 140
+++++++++++++++++++++++--------------------
     gcc/cp/cp-tree.h     |   4 +-
     2 files changed, 78 insertions(+), 66 deletions(-)

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 39c97986082..56134f8b2bf 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -133,7 +133,7 @@ struct sat_info : subst_info
       bool diagnose_unsatisfaction;
     };
     -static tree satisfy_constraint (tree, tree, sat_info);
+static tree satisfy_constraint_expression (tree, tree, sat_info);
       /* True if T is known to be some type other than bool. Note
that
this
        is false for dependent types and errors.  */
@@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
       return parms;
     }
     -/* Build the parameter mapping for EXPR using ARGS.  */
+/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
+   are the template parameters in scope for EXPR.  */
       static tree
-build_parameter_mapping (tree expr, tree args, tree decl)
+build_parameter_mapping (tree expr, tree args, tree ctx_parms)
     {
-  tree ctx_parms = NULL_TREE;
-  if (decl)
-    {
-      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
-      ctx_parms = DECL_TEMPLATE_PARMS (decl);
-    }
-  else if (current_template_parms)
-    {
-      /* TODO: This should probably be the only case, but because
the
-        point of declaration of concepts is currently set after the
-        initializer, the template parameter lists are not available
-        when normalizing concept definitions, hence the case above.
*/
-      ctx_parms = current_template_parms;
-    }
-
       tree parms = find_template_parameters (expr, ctx_parms);
       tree map = map_arguments (parms, args);
       return map;
@@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree
t2)
       struct norm_info : subst_info
     {
-  explicit norm_info (tsubst_flags_t complain)
-    : subst_info (tf_warning_or_error | complain, NULL_TREE),
-      context()
+  explicit norm_info (tsubst_flags_t cmp)
+    : norm_info (NULL_TREE, cmp)
       {}
         /* Construct a top-level context for DECL.  */
         norm_info (tree in_decl, tsubst_flags_t complain)
-    : subst_info (tf_warning_or_error | complain, in_decl),
-      context (make_context (in_decl)),
-      orig_decl (in_decl)
-  {}
-
-  bool generate_diagnostics() const
+    : subst_info (tf_warning_or_error | complain, in_decl)
       {
-    return complain & tf_norm;
+    if (in_decl)
+      {
+       initial_parms = DECL_TEMPLATE_PARMS (in_decl);
+       if (generate_diagnostics ())
+         context = build_tree_list (NULL_TREE, in_decl);
+      }
+    else
+      initial_parms = current_template_parms;
       }
     -  tree make_context(tree in_decl)
+  bool generate_diagnostics() const
       {
-    if (generate_diagnostics ())
-      return build_tree_list (NULL_TREE, in_decl);
-    return NULL_TREE;
+    return complain & tf_norm;
       }
         void update_context(tree expr, tree args)
       {
         if (generate_diagnostics ())
           {
-       tree map = build_parameter_mapping (expr, args, in_decl);
+       tree map = build_parameter_mapping (expr, args, ctx_parms ());
        context = tree_cons (map, expr, context);
           }
         in_decl = get_concept_check_template (expr);
       }
     +  /* Returns the template parameters that are in scope for the
current
+     normalization context.  */
+
+  tree ctx_parms()
+  {
+    if (in_decl)
+      return DECL_TEMPLATE_PARMS (in_decl);
+    else
+      return initial_parms;
+  }
+
       /* Provides information about the source of a constraint. This
is a
          TREE_LIST whose VALUE is either a concept check or a
constrained
          declaration. The PURPOSE, for concept checks is a parameter
mapping
          for that check.  */
     -  tree context;
+  tree context = NULL_TREE;
         /* The declaration whose constraints we're normalizing.  The
targets
          of the parameter mapping of each atom will be in terms of
the
          template parameters of ORIG_DECL.  */
     -  tree orig_decl = NULL_TREE;
+  tree initial_parms = NULL_TREE;
     };
       static tree normalize_expression (tree, tree, norm_info);
@@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info
info)
         return normalize_concept_check (t, args, info);
         /* Build the parameter mapping for the atom.  */
-  tree map = build_parameter_mapping (t, args, info.in_decl);
+  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
         /* Build a new info object for the atom.  */
       tree ci = build_tree_list (t, info.context);
@@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info
info)
              tree target = TREE_PURPOSE (node);
              TREE_VEC_ELT (targets, i++) = target;
            }
-         tree ctx_parms = (info.orig_decl
-                           ? DECL_TEMPLATE_PARMS (info.orig_decl)
-                           : current_template_parms);
-         tree target_parms = find_template_parameters (targets,
ctx_parms);
+         tree target_parms = find_template_parameters (targets,
+
info.initial_parms);
          TREE_TYPE (map) = target_parms;
        }
     @@ -983,17 +977,43 @@ normalize_nontemplate_requirements (tree
decl,
bool
diag = false)
     /* Normalize an EXPR as a constraint.  */
       static tree
-normalize_constraint_expression (tree expr, bool diag)
+normalize_constraint_expression (tree expr, norm_info info)
     {
       if (!expr || expr == error_mark_node)
         return expr;
+
+  if (!info.generate_diagnostics ())
+    if (tree *p = hash_map_safe_get (normalized_map, expr))
+      return *p;

It seems like we only want this for NESTED_REQ.

I figured it'd also be beneficial to cache the normal form of a
placeholder type constraint, which will also goes through this overload.

True.  And if we change REQUIRES_EXPR handling to not go through here, it
should be just the two.  OK, let's leave this alone.

       ++processing_template_decl;
-  norm_info info (diag ? tf_norm : tf_none);
       tree norm = get_normalized_constraints (expr, info);
       --processing_template_decl;
+
+  if (!info.generate_diagnostics ())
+    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
+
       return norm;
     }
     +/* High-level wrapper for the above.  */
+
+static tree
+normalize_constraint_expression (tree expr, bool diag)
+{
+  norm_info info (diag ? tf_norm : tf_none);

I wonder if we want to add a norm_info constructor taking a sat_info
so we
don't need to mediate passing from one into the other with bool "diag"
parameters in various functions.  That doesn't need to happen in this
patch.

Sounds good.  I think such a constructor would let us eliminate the
bool-taking overload of normalize_constraint_expression altogether, if
we move the special handling of NESTED_REQ to the norm_info-taking
overload or to satisfy_constraint_expression.

Here's v2 of this patch, which refrains from adding a second overload of
normalize_constraint_expression.  Handling of NESTED_REQs now happens in
satisfy_constraint_expression.

The rest of the cleanups mentioned here will be performed in another
patch.

(For now, I didn't add a norm_info(sat_info) constructor since it wasn't
clear to me if this constructor should propagate in_decl or not.  For >
sat_info, in_decl is used purely for diagnostics, whereas for norm_info,
in_decl keeps track of the current normalization context, so carrying
over the value of in_decl might not always make sense.

Perhaps it might be better to add a norm_info(bool diag) constructor
instead?

I was thinking the norm_info(sat_info) constructor would do the equivalent of

+      norm_info ninfo (info.noisy () ? tf_norm : tf_none);

Ah, sounds good.  Shall I do that (and the tf_norm removal) in a
followup patch, or update this one?

In a followup is fine.

While we're at it, I think we could remove the tf_norm flag.)

Sure.

-- >8 --

Subject: [PATCH 3/6] c++: Delay normalizing nested requirements until
   satisfaction

This sets up the functionality for controlling the initial set of
template parameters to pass to normalization when dealing with a
constraint-expression that is not associated with some constrained
declaration, for instance when normalizing a nested requirement of a
requires expression, or the constraints on a placeholder type.

The main new ingredient here is the data member norm_info::initial_parms
which can be set by callers of the normalization routines to communicate
the in-scope template parameters for the supplied constraint-expression,
rather than always falling back to using current_template_parms.

This patch then uses this functionality in our handling of nested
requirements so that we can delay normalizing them until needed for
satisfaction.  We currently immediately normalize nested requirements at
parse time, where we have the necessary template context, and cache the
normal form in their TREE_TYPE node.  With this patch, we now delay
normalization until needed (as with other constraint expressions), and
instead store the current value of current_template_parms in their
TREE_TYPE node (which we use to restore the template context at
normalization time).

In the subsequent patch, this functionality will also be used to
normalize placeholder type constraints during auto deduction.

gcc/cp/ChangeLog:

        * constraint.cc (build_parameter_mapping): Rely on the caller to
        determine the in-scope template parameters.
        (norm_info::norm_info): Delegate the tsubst_flags_t constructor
        to the two-parameter constructor.  In the two-parameter
        constructor, fold in the definition of make_context, set
        initial_parms appropriately, and don't set the now-removed
        orig_decl member.
        (norm_info::make_context): Remove, now that its only use is
        inlined into the caller.
        (norm_info::update_context): Adjust call to
        build_parameter_mapping to pass in the relevant set of in-scope
        template parameters.
        (norm_info::ctx_parms): Define this member function.
        (norm_info::context): Initialize to NULL_TREE.
        (norm_info::orig_decl): Remove this data member.
        (norm_info::initial_parms): Define this data member.
        (normalize_atom): Adjust call to build_parameter_mapping to pass
        in the relevant set of in-scope template parameters.  Use
        info.initial_parms instead of info.orig_decl.
        (normalize_constraint_expression): Take a norm_info object
        instead of a bool.  Cache the result of normalization.
        (tsubst_nested_requirement): Call satisfy_constraint_expression
        instead of satisfy_constraint, so that we normalize on demand.
        (satisfy_constraint_expression): Handle a NESTED_REQ argument.
        Adjust call to normalize_constraint_expression.
        (finish_nested_requirement): Set the TREE_TYPE of the NESTED_REQ
        to current_template_parms.
        (diagnose_nested_requirements): Go through
        satisfy_constraint_expression, as with tsubst_nested_requirement.
---
   gcc/cp/constraint.cc | 138 ++++++++++++++++++++++---------------------
   1 file changed, 72 insertions(+), 66 deletions(-)

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 39c97986082..fcb249a642f 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -133,7 +133,7 @@ struct sat_info : subst_info
     bool diagnose_unsatisfaction;
   };
   -static tree satisfy_constraint (tree, tree, sat_info);
+static tree satisfy_constraint_expression (tree, tree, sat_info);
     /* True if T is known to be some type other than bool. Note that this
      is false for dependent types and errors.  */
@@ -594,26 +594,12 @@ map_arguments (tree parms, tree args)
     return parms;
   }
   -/* Build the parameter mapping for EXPR using ARGS.  */
+/* Build the parameter mapping for EXPR using ARGS, where CTX_PARMS
+   are the template parameters in scope for EXPR.  */
     static tree
-build_parameter_mapping (tree expr, tree args, tree decl)
+build_parameter_mapping (tree expr, tree args, tree ctx_parms)
   {
-  tree ctx_parms = NULL_TREE;
-  if (decl)
-    {
-      gcc_assert (TREE_CODE (decl) == TEMPLATE_DECL);
-      ctx_parms = DECL_TEMPLATE_PARMS (decl);
-    }
-  else if (current_template_parms)
-    {
-      /* TODO: This should probably be the only case, but because the
-        point of declaration of concepts is currently set after the
-        initializer, the template parameter lists are not available
-        when normalizing concept definitions, hence the case above.  */
-      ctx_parms = current_template_parms;
-    }
-
     tree parms = find_template_parameters (expr, ctx_parms);
     tree map = map_arguments (parms, args);
     return map;
@@ -645,53 +631,63 @@ parameter_mapping_equivalent_p (tree t1, tree t2)
     struct norm_info : subst_info
   {
-  explicit norm_info (tsubst_flags_t complain)
-    : subst_info (tf_warning_or_error | complain, NULL_TREE),
-      context()
+  explicit norm_info (tsubst_flags_t cmp)
+    : norm_info (NULL_TREE, cmp)
     {}
       /* Construct a top-level context for DECL.  */
       norm_info (tree in_decl, tsubst_flags_t complain)
-    : subst_info (tf_warning_or_error | complain, in_decl),
-      context (make_context (in_decl)),
-      orig_decl (in_decl)
-  {}
-
-  bool generate_diagnostics() const
+    : subst_info (tf_warning_or_error | complain, in_decl)
     {
-    return complain & tf_norm;
+    if (in_decl)
+      {
+       initial_parms = DECL_TEMPLATE_PARMS (in_decl);
+       if (generate_diagnostics ())
+         context = build_tree_list (NULL_TREE, in_decl);
+      }
+    else
+      initial_parms = current_template_parms;
     }
   -  tree make_context(tree in_decl)
+  bool generate_diagnostics() const
     {
-    if (generate_diagnostics ())
-      return build_tree_list (NULL_TREE, in_decl);
-    return NULL_TREE;
+    return complain & tf_norm;
     }
       void update_context(tree expr, tree args)
     {
       if (generate_diagnostics ())
         {
-       tree map = build_parameter_mapping (expr, args, in_decl);
+       tree map = build_parameter_mapping (expr, args, ctx_parms ());
        context = tree_cons (map, expr, context);
         }
       in_decl = get_concept_check_template (expr);
     }
   +  /* Returns the template parameters that are in scope for the current
+     normalization context.  */
+
+  tree ctx_parms()
+  {
+    if (in_decl)
+      return DECL_TEMPLATE_PARMS (in_decl);
+    else
+      return initial_parms;
+  }

Why prefer in_decl to initial_parms?  In fact, why look at in_decl here at
all, when we already used it to set initial_parms in the constructor?

IIUC, that's true at the start of normalization, but in_decl gets
continuously updated as we recurse into a concept-id and normalize the
concept's definition (in normalize_concept_check via
norm_info::update_context), whereas initial_parms gets set at the start
of normalization (upon construction) and remains unchanged.

Ah, I see.  Then this patch is OK.


     /* Provides information about the source of a constraint. This is a
        TREE_LIST whose VALUE is either a concept check or a constrained
        declaration. The PURPOSE, for concept checks is a parameter mapping
        for that check.  */
   -  tree context;
+  tree context = NULL_TREE;
       /* The declaration whose constraints we're normalizing.  The targets
        of the parameter mapping of each atom will be in terms of the
        template parameters of ORIG_DECL.  */
   -  tree orig_decl = NULL_TREE;
+  tree initial_parms = NULL_TREE;
   };
     static tree normalize_expression (tree, tree, norm_info);
@@ -773,7 +769,7 @@ normalize_atom (tree t, tree args, norm_info info)
       return normalize_concept_check (t, args, info);
       /* Build the parameter mapping for the atom.  */
-  tree map = build_parameter_mapping (t, args, info.in_decl);
+  tree map = build_parameter_mapping (t, args, info.ctx_parms ());
       /* Build a new info object for the atom.  */
     tree ci = build_tree_list (t, info.context);
@@ -803,10 +799,8 @@ normalize_atom (tree t, tree args, norm_info info)
              tree target = TREE_PURPOSE (node);
              TREE_VEC_ELT (targets, i++) = target;
            }
-         tree ctx_parms = (info.orig_decl
-                           ? DECL_TEMPLATE_PARMS (info.orig_decl)
-                           : current_template_parms);
-         tree target_parms = find_template_parameters (targets, ctx_parms);
+         tree target_parms = find_template_parameters (targets,
+                                                       info.initial_parms);
          TREE_TYPE (map) = target_parms;
        }
   @@ -983,14 +977,22 @@ normalize_nontemplate_requirements (tree decl, bool
diag = false)
   /* Normalize an EXPR as a constraint.  */
     static tree
-normalize_constraint_expression (tree expr, bool diag)
+normalize_constraint_expression (tree expr, norm_info info)
   {
     if (!expr || expr == error_mark_node)
       return expr;
+
+  if (!info.generate_diagnostics ())
+    if (tree *p = hash_map_safe_get (normalized_map, expr))
+      return *p;
+
     ++processing_template_decl;
-  norm_info info (diag ? tf_norm : tf_none);
     tree norm = get_normalized_constraints (expr, info);
     --processing_template_decl;
+
+  if (!info.generate_diagnostics ())
+    hash_map_safe_put<hm_ggc> (normalized_map, expr, norm);
+
     return norm;
   }
   @@ -2086,16 +2088,14 @@ tsubst_compound_requirement (tree t, tree args,
subst_info info)
   static tree
   tsubst_nested_requirement (tree t, tree args, subst_info info)
   {
-  /* Perform satisfaction quietly with the regular normal form.  */
+  /* Perform satisfaction quietly first.  */
     sat_info quiet (tf_none, info.in_decl);
-  tree norm = TREE_VALUE (TREE_TYPE (t));
-  tree diag_norm = TREE_PURPOSE (TREE_TYPE (t));
-  tree result = satisfy_constraint (norm, args, quiet);
+  tree result = satisfy_constraint_expression (t, args, quiet);
     if (result == error_mark_node)
       {
-      /* Replay the error using the diagnostic normal form.  */
+      /* Replay the error.  */
         sat_info noisy (tf_warning_or_error, info.in_decl);
-      satisfy_constraint (diag_norm, args, noisy);
+      satisfy_constraint_expression (t, args, noisy);
       }
     if (result != boolean_true_node)
       return error_mark_node;
@@ -3040,8 +3040,22 @@ satisfy_constraint_expression (tree t, tree args,
sat_info info)
         tree tmpl = get_concept_check_template (id);
         norm = normalize_concept_definition (tmpl, info.noisy ());
       }
+  else if (TREE_CODE (t) == NESTED_REQ)
+    {
+      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
+      /* The TREE_TYPE contains the set of template parameters that were in
+        scope for this nested requirement; use them as the initial template
+        parameters for normalization.  */
+      ninfo.initial_parms = TREE_TYPE (t);
+      norm = normalize_constraint_expression (TREE_OPERAND (t, 0), ninfo);
+    }
+  else if (EXPR_P (t))
+    {
+      norm_info ninfo (info.noisy () ? tf_norm : tf_none);
+      norm = normalize_constraint_expression (t, ninfo);
+    }
     else
-    norm = normalize_constraint_expression (t, info.noisy ());
+    gcc_unreachable ();
       /* Perform satisfaction.  */
     return satisfy_constraint (norm, args, info);
@@ -3301,15 +3315,9 @@ finish_compound_requirement (location_t loc, tree
expr, tree type, bool noexcept
   tree
   finish_nested_requirement (location_t loc, tree expr)
   {
-  /* We need to normalize the constraints now, at parse time, while
-     we have the necessary template context.  We normalize twice,
-     once without diagnostic information and once with, which we'll
-     later use for quiet and noisy satisfaction respectively.  */
-  tree norm = normalize_constraint_expression (expr, /*diag=*/false);
-  tree diag_norm = normalize_constraint_expression (expr, /*diag=*/true);
-
-  /* Build the constraint, saving its two normalizations as its type.  */
-  tree r = build1 (NESTED_REQ, build_tree_list (diag_norm, norm), expr);
+  /* Build the requirement, saving the set of in-scope template
+     parameters as its type.  */
+  tree r = build1 (NESTED_REQ, current_template_parms, expr);
     SET_EXPR_LOCATION (r, loc);
     return r;
   }
@@ -3710,12 +3718,9 @@ diagnose_type_requirement (tree req, tree args, tree
in_decl)
   static void
   diagnose_nested_requirement (tree req, tree args)
   {
-  /* Quietly check for satisfaction first using the regular normal form.
-     We can elaborate details later if needed.  */
-  tree norm = TREE_VALUE (TREE_TYPE (req));
-  tree diag_norm = TREE_PURPOSE (TREE_TYPE (req));
-  sat_info info (tf_none, NULL_TREE);
-  tree result = satisfy_constraint (norm, args, info);
+  /* Quietly check for satisfaction first.  */
+  sat_info quiet (tf_none, NULL_TREE);
+  tree result = satisfy_constraint_expression (req, args, quiet);
     if (result == boolean_true_node)
       return;
   @@ -3723,10 +3728,11 @@ diagnose_nested_requirement (tree req, tree args)
     location_t loc = cp_expr_location (expr);
     if (diagnosing_failed_constraint::replay_errors_p ())
       {
-      /* Replay the substitution error using the diagnostic normal form.
*/
+      /* Replay the substitution error with re-normalized requirements.  */
         inform (loc, "nested requirement %qE is not satisfied, because",
expr);
+
         sat_info noisy (tf_warning_or_error, NULL_TREE,
/*diag_unsat=*/true);
-      satisfy_constraint (diag_norm, args, noisy);
+      satisfy_constraint_expression (req, args, noisy);
       }
     else
       inform (loc, "nested requirement %qE is not satisfied", expr);





Reply via email to