[v3,05/12] OpenMP: C++ front-end support for metadirectives

Message ID 20240720204231.2229891-6-sloosemore@baylibre.com
State New
Headers
Series Metadirective support + "declare variant" improvements |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed

Commit Message

Sandra Loosemore July 20, 2024, 8:42 p.m. UTC
  This patch adds C++ support for metadirectives.  It uses the
c-family support committed with the corresponding C front end patch
to do early parse-time metadirective resolution when possible.

Additional C/C++ common testcases are provided in a subsequent
patch in the series.

gcc/cp/ChangeLog
	* parser.cc (cp_parser_skip_to_end_of_block_or_statement): Add
	metadirective_p parameter, use it to control brace/parentheses
	behavior for metadirectives.
	(mangle_metadirective_region_label): New.
	(cp_parser_label_for_labeled_statement): Use it.
	(cp_parser_jump_statement): Likewise.
	(cp_parser_omp_context_selector): Add metadirective_p
	parameter, use it to control error behavior for non-constant exprs
	properties.
	(cp_parser_omp_context_selector_specification): Add metadirective_p
	parameter, use it for cp_parser_omp_context_selector call.
	(cp_finish_omp_declare_variant): Adjust call to
	cp_parser_omp_context_selector_specification.
	(analyze_metadirective_body): New.
	(cp_parser_omp_metadirective): New.
	(cp_parser_pragma): Handle PRAGMA_OMP_METADIRECTIVE.
	* parser.h (struct cp_parser): Add fields for metadirective parsing
	state.
	* pt.cc (tsubst_omp_context_selector): New.
	(tsubst_stmt): Handle OMP_METADIRECTIVE.

libgomp/ChangeLog
	* testsuite/libgomp.c++/metadirective-template-1.C: New.
	* testsuite/libgomp.c++/metadirective-template-2.C: New.
	* testsuite/libgomp.c++/metadirective-template-3.C: New.

Co-Authored-By: Kwok Cheung Yeung <kcy@codesourcery.com>
Co-Authored-By: Sandra Loosemore <sandra@codesourcery.com>
---
 gcc/cp/parser.cc                              | 524 +++++++++++++++++-
 gcc/cp/parser.h                               |   7 +
 gcc/cp/pt.cc                                  | 119 ++++
 .../libgomp.c++/metadirective-template-1.C    |  37 ++
 .../libgomp.c++/metadirective-template-2.C    |  41 ++
 .../libgomp.c++/metadirective-template-3.C    |  41 ++
 6 files changed, 756 insertions(+), 13 deletions(-)
 create mode 100644 libgomp/testsuite/libgomp.c++/metadirective-template-1.C
 create mode 100644 libgomp/testsuite/libgomp.c++/metadirective-template-2.C
 create mode 100644 libgomp/testsuite/libgomp.c++/metadirective-template-3.C
  

Comments

Jakub Jelinek Aug. 16, 2024, 11:15 a.m. UTC | #1
On Sat, Jul 20, 2024 at 02:42:24PM -0600, Sandra Loosemore wrote:
> +  const char *old_name = IDENTIFIER_POINTER (name);
> +  char *new_name = (char *) alloca (strlen (old_name) + 32);

XALLOCAVEC like for the C FE patch.

> +	      /* FIXME: I believe it is an unimplemented feature rather
> +		 than a user error to have non-constant expressions
> +		 inside "declare variant".  */
> +	      t = metadirective_p
> +		? cp_parser_expression (parser)
> +		: cp_parser_constant_expression (parser);
>  	      if (t != error_mark_node)
>  		{
>  		  t = fold_non_dependent_expr (t);
> -		  if (!value_dependent_expression_p (t)
> +		  if (!metadirective_p
> +		      && !value_dependent_expression_p (t)
>  		      && (!INTEGRAL_TYPE_P (TREE_TYPE (t))
>  			  || !tree_fits_shwi_p (t)))
>  		    error_at (token->location, "property must be "
>  			      "constant integer expression");
> +		  if (metadirective_p
> +		      && !INTEGRAL_TYPE_P (TREE_TYPE (t)))

Shouldn't this be && !type_dependent_expression_p (t) before the
!INTEGRAL_TYPE_P check?
I mean
template <typename T, T N>
void
foo ()
{
  #pragma omp metadirective ... user={condition(N)} ...
...
}
should be valid, or just typename T and foo (T x) and condition(x).

> +		    error_at (token->location,
> +			      "property must be integer expression");
> --- a/gcc/cp/parser.h
> +++ b/gcc/cp/parser.h
> @@ -450,6 +450,13 @@ struct GTY(()) cp_parser {
>    /* Pointer to state for parsing omp_loops.  Managed by
>       cp_parser_omp_for_loop in parser.cc and not used outside that file.  */
>    struct omp_for_parse_data * GTY((skip)) omp_for_parse_state;
> +
> +  /* Set if we are processing a statement body associated with a
> +     metadirective variant.  */
> +  bool in_metadirective_body;
> +
> +  vec<tree> * GTY((skip)) metadirective_body_labels;
> +  unsigned metadirective_region_num;

Again, there is unnecessary padding here (pointer, 8-bit bool, pointer,
32-bit unsigned) and maybe put the stuff into a separate structure and just
use a pointer to it?  Like the omp_for_parse_state.  Though, it is less
important than in the C++ FE.
>  };
>  
>  /* In parser.cc  */
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 108e929b8ee..109121be501 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -17851,6 +17851,79 @@ tsubst_omp_clauses (tree clauses, enum c_omp_region_type ort,
>    return new_clauses;
>  }
>  
> +/* Like tsubst_copy, but specifically for OpenMP context selectors.  */
> +static tree
> +tsubst_omp_context_selector (tree ctx, tree args, tsubst_flags_t complain,
> +			     tree in_decl)
> +{
> +  tree new_ctx = NULL_TREE;
> +  for (tree set = ctx; set; set = TREE_CHAIN (set))
> +    {
> +      tree selectors = NULL_TREE;
> +      for (tree sel = OMP_TSS_TRAIT_SELECTORS (set); sel;
> +	   sel = TREE_CHAIN (sel))
> +	{
> +	  enum omp_ts_code code = OMP_TS_CODE (sel);
> +	  tree properties = NULL_TREE;
> +	  tree score = OMP_TS_SCORE (sel);
> +	  tree t;
> +
> +	  if (score)
> +	    {
> +	      score = tsubst_expr (score, args, complain, in_decl);
> +	      score = fold_non_dependent_expr (score);

I think for partial template specialization processing_template_decl
can be true, so wonder if in that case it shouldn't again not diagnose
anything if score is still value dependent expression.

> +	  switch (omp_ts_map[OMP_TS_CODE (sel)].tp_type)
> +	      {
> +	      case OMP_TRAIT_PROPERTY_DEV_NUM_EXPR:
> +	      case OMP_TRAIT_PROPERTY_BOOL_EXPR:
> +		t = tsubst_expr (OMP_TP_VALUE (OMP_TS_PROPERTIES (sel)),
> +				 args, complain, in_decl);
> +		t = fold_non_dependent_expr (t);
> +		if (!INTEGRAL_TYPE_P (TREE_TYPE (t)))
> +		  error_at (cp_expr_loc_or_input_loc (t),
> +			    "property must be integer expression");

And similarly here.  Also, where do we do instantiation of the declare
variant selectors (if we do that at all)?

	Jakub
  

Patch

diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 045b3fe7a5b..d4358ab35a1 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -3003,7 +3003,7 @@  static void cp_parser_skip_to_end_of_statement
 static void cp_parser_consume_semicolon_at_end_of_statement
   (cp_parser *);
 static void cp_parser_skip_to_end_of_block_or_statement
-  (cp_parser *);
+  (cp_parser *, bool = false);
 static bool cp_parser_skip_to_closing_brace
   (cp_parser *);
 static bool cp_parser_skip_entire_template_parameter_list
@@ -4196,9 +4196,11 @@  cp_parser_consume_semicolon_at_end_of_statement (cp_parser *parser)
    have consumed a non-nested `;'.  */
 
 static void
-cp_parser_skip_to_end_of_block_or_statement (cp_parser* parser)
+cp_parser_skip_to_end_of_block_or_statement (cp_parser* parser,
+					     bool metadirective_p)
 {
   int nesting_depth = 0;
+  int bracket_depth = 0;
 
   /* Unwind generic function template scope if necessary.  */
   if (parser->fully_implicit_function_template_p)
@@ -4220,7 +4222,7 @@  cp_parser_skip_to_end_of_block_or_statement (cp_parser* parser)
 
 	case CPP_SEMICOLON:
 	  /* Stop if this is an unnested ';'. */
-	  if (!nesting_depth)
+	  if (!nesting_depth && (!metadirective_p || bracket_depth <= 0))
 	    nesting_depth = -1;
 	  break;
 
@@ -4239,6 +4241,19 @@  cp_parser_skip_to_end_of_block_or_statement (cp_parser* parser)
 	  nesting_depth++;
 	  break;
 
+	case CPP_OPEN_PAREN:
+	  /* Track parentheses in case the statement is a standalone 'for'
+	     statement - we want to skip over the semicolons separating the
+	     operands.  */
+	  if (metadirective_p && nesting_depth == 0)
+	    bracket_depth++;
+	  break;
+
+	case CPP_CLOSE_PAREN:
+	  if (metadirective_p && nesting_depth == 0)
+	    bracket_depth--;
+	  break;
+
 	case CPP_KEYWORD:
 	  if (!cp_token_is_module_directive (token))
 	    break;
@@ -13021,6 +13036,18 @@  attr_chainon (tree attrs, tree attr)
   return chainon (attrs, attr);
 }
 
+
+/* Helper function for cp_parser_label: mangle a metadirective region
+   label NAME.  */
+static tree
+mangle_metadirective_region_label (cp_parser *parser, tree name)
+{
+  const char *old_name = IDENTIFIER_POINTER (name);
+  char *new_name = (char *) alloca (strlen (old_name) + 32);
+  sprintf (new_name, "%s_MDR%u", old_name, parser->metadirective_region_num);
+  return get_identifier (new_name);
+}
+
 /* Parse the label for a labeled-statement, i.e.
 
    label:
@@ -13123,7 +13150,12 @@  cp_parser_label_for_labeled_statement (cp_parser* parser, tree attributes)
 
     default:
       /* Anything else must be an ordinary label.  */
-      label = finish_label_stmt (cp_parser_identifier (parser));
+      cp_expr identifier = cp_parser_identifier (parser);
+      if (identifier != error_mark_node
+	  && parser->in_metadirective_body
+	  && parser->metadirective_body_labels->contains (*identifier))
+	*identifier = mangle_metadirective_region_label (parser, *identifier);
+      label = finish_label_stmt (identifier);
       if (label && TREE_CODE (label) == LABEL_DECL)
 	{
 	  FALLTHROUGH_LABEL_P (label) = fallthrough_p;
@@ -14959,7 +14991,15 @@  cp_parser_jump_statement (cp_parser* parser)
 	  finish_goto_stmt (cp_parser_expression (parser));
 	}
       else
-	finish_goto_stmt (cp_parser_identifier (parser));
+	{
+	  cp_expr identifier = cp_parser_identifier (parser);
+	  if (identifier != error_mark_node
+	      && parser->in_metadirective_body
+	      && parser->metadirective_body_labels->contains (*identifier))
+	    *identifier = mangle_metadirective_region_label (parser,
+							     *identifier);
+	  finish_goto_stmt (identifier);
+	}
       /* Look for the final `;'.  */
       cp_parser_require (parser, CPP_SEMICOLON, RT_SEMICOLON);
       break;
@@ -48388,7 +48428,7 @@  cp_parser_omp_declare_simd (cp_parser *parser, cp_token *pragma_tok,
 
 static tree
 cp_parser_omp_context_selector (cp_parser *parser, enum omp_tss_code set,
-				bool has_parms_p)
+				bool has_parms_p, bool metadirective_p)
 {
   tree ret = NULL_TREE;
   do
@@ -48549,17 +48589,25 @@  cp_parser_omp_context_selector (cp_parser *parser, enum omp_tss_code set,
 	      break;
 	    case OMP_TRAIT_PROPERTY_DEV_NUM_EXPR:
 	    case OMP_TRAIT_PROPERTY_BOOL_EXPR:
-	      /* FIXME: this is bogus, the expression need
-		 not be constant.  */
-	      t = cp_parser_constant_expression (parser);
+	      /* FIXME: I believe it is an unimplemented feature rather
+		 than a user error to have non-constant expressions
+		 inside "declare variant".  */
+	      t = metadirective_p
+		? cp_parser_expression (parser)
+		: cp_parser_constant_expression (parser);
 	      if (t != error_mark_node)
 		{
 		  t = fold_non_dependent_expr (t);
-		  if (!value_dependent_expression_p (t)
+		  if (!metadirective_p
+		      && !value_dependent_expression_p (t)
 		      && (!INTEGRAL_TYPE_P (TREE_TYPE (t))
 			  || !tree_fits_shwi_p (t)))
 		    error_at (token->location, "property must be "
 			      "constant integer expression");
+		  if (metadirective_p
+		      && !INTEGRAL_TYPE_P (TREE_TYPE (t)))
+		    error_at (token->location,
+			      "property must be integer expression");
 		  else
 		    properties = make_trait_property (NULL_TREE, t,
 						      properties);
@@ -48638,7 +48686,8 @@  cp_parser_omp_context_selector (cp_parser *parser, enum omp_tss_code set,
 
 static tree
 cp_parser_omp_context_selector_specification (cp_parser *parser,
-					      bool has_parms_p)
+					      bool has_parms_p,
+					      bool metadirective_p)
 {
   tree ret = NULL_TREE;
   do
@@ -48665,7 +48714,8 @@  cp_parser_omp_context_selector_specification (cp_parser *parser,
 	return error_mark_node;
 
       tree selectors
-	= cp_parser_omp_context_selector (parser, set, has_parms_p);
+	= cp_parser_omp_context_selector (parser, set, has_parms_p,
+					  metadirective_p);
       if (selectors == error_mark_node)
 	{
 	  cp_parser_skip_to_closing_brace (parser);
@@ -48975,7 +49025,8 @@  cp_finish_omp_declare_variant (cp_parser *parser, cp_token *pragma_tok,
   if (!parens.require_open (parser))
     goto fail;
 
-  tree ctx = cp_parser_omp_context_selector_specification (parser, true);
+  tree ctx = cp_parser_omp_context_selector_specification (parser, true,
+							   false);
   if (ctx == error_mark_node)
     goto fail;
   ctx = omp_check_context_selector (match_loc, ctx, false);
@@ -49608,6 +49659,449 @@  cp_parser_omp_end (cp_parser *parser, cp_token *pragma_tok)
     }
 }
 
+
+/* Helper function for cp_parser_omp_metadirective.  */
+
+static void
+analyze_metadirective_body (cp_parser *parser,
+			    vec<cp_token> &tokens,
+			    vec<tree> &labels)
+{
+  int nesting_depth = 0;
+  int bracket_depth = 0;
+  bool in_case = false;
+  bool in_label_decl = false;
+  cp_token *pragma_tok = NULL;
+
+  while (1)
+    {
+      cp_token *token = cp_lexer_peek_token (parser->lexer);
+      bool stop = false;
+
+      if (cp_lexer_next_token_is_keyword (parser->lexer, RID_CASE))
+	in_case = true;
+      else if (cp_lexer_next_token_is_keyword (parser->lexer, RID_LABEL))
+	in_label_decl = true;
+
+      switch (token->type)
+	{
+	case CPP_EOF:
+	  break;
+	case CPP_NAME:
+	  if ((!in_case
+	       && cp_lexer_nth_token_is (parser->lexer, 2, CPP_COLON))
+	      || in_label_decl)
+	    labels.safe_push (token->u.value);
+	  goto add;
+	case CPP_OPEN_BRACE:
+	  ++nesting_depth;
+	  goto add;
+	case CPP_CLOSE_BRACE:
+	  if (--nesting_depth == 0 && bracket_depth == 0)
+	    stop = true;
+	  goto add;
+	case CPP_OPEN_PAREN:
+	  ++bracket_depth;
+	  goto add;
+	case CPP_CLOSE_PAREN:
+	  --bracket_depth;
+	  goto add;
+	case CPP_COLON:
+	  in_case = false;
+	  goto add;
+	case CPP_SEMICOLON:
+	  if (nesting_depth == 0 && bracket_depth == 0)
+	    stop = true;
+	  /* Local label declarations are terminated by a semicolon.  */
+	  in_label_decl = false;
+	  goto add;
+	case CPP_PRAGMA:
+	  parser->lexer->in_pragma = true;
+	  pragma_tok = token;
+	  goto add;
+	case CPP_PRAGMA_EOL:
+	  /* C++ attribute syntax for OMP directives lexes as a pragma,
+	     but we must reset the associated lexer state when we reach
+	     the end in order to get the tokens for the statement that
+	     come after it.  */
+	  tokens.safe_push (*token);
+	  cp_parser_skip_to_pragma_eol (parser, pragma_tok);
+	  pragma_tok = NULL;
+	  continue;
+	default:
+	add:
+	  tokens.safe_push (*token);
+	  cp_lexer_consume_token (parser->lexer);
+	  if (stop)
+	    break;
+	  continue;
+	}
+      break;
+    }
+}
+
+/* OpenMP 5.0:
+
+  # pragma omp metadirective [clause[, clause]]
+*/
+
+static void
+cp_parser_omp_metadirective (cp_parser *parser, cp_token *pragma_tok,
+			     bool *if_p)
+{
+  static unsigned int metadirective_region_count = 0;
+
+  auto_vec<cp_token> directive_tokens;
+  auto_vec<cp_token> body_tokens;
+  auto_vec<tree> body_labels;
+  auto_vec<const struct c_omp_directive *> directives;
+  auto_vec<tree> ctxs;
+  bool default_seen = false;
+  int directive_token_idx = 0;
+  location_t pragma_loc = pragma_tok->location;
+  tree standalone_body = NULL_TREE;
+  vec<struct omp_variant> candidates;
+  bool requires_body = false;
+
+  tree ret = make_node (OMP_METADIRECTIVE);
+  SET_EXPR_LOCATION (ret, pragma_loc);
+  TREE_TYPE (ret) = void_type_node;
+  OMP_METADIRECTIVE_VARIANTS (ret) = NULL_TREE;
+
+  while (cp_lexer_next_token_is_not (parser->lexer, CPP_PRAGMA_EOL))
+    {
+      if (cp_lexer_next_token_is (parser->lexer, CPP_COMMA))
+	cp_lexer_consume_token (parser->lexer);
+      if (cp_lexer_next_token_is_not (parser->lexer, CPP_NAME)
+	  && cp_lexer_next_token_is_not (parser->lexer, CPP_KEYWORD))
+	{
+	  cp_parser_error (parser, "expected %<when%>, "
+			   "%<otherwise%>, or %<default%> clause");
+	  goto fail;
+	}
+
+      location_t match_loc = cp_lexer_peek_token (parser->lexer)->location;
+      const char *p
+	= IDENTIFIER_POINTER (cp_lexer_peek_token (parser->lexer)->u.value);
+      cp_lexer_consume_token (parser->lexer);
+      bool default_p
+	= strcmp (p, "default") == 0 || strcmp (p, "otherwise") == 0;
+      if (default_p)
+	{
+	  if (default_seen)
+	    {
+	      error_at (match_loc, "too many %<otherwise%> or %<default%> "
+			"clauses in %<metadirective%>");
+	      cp_parser_skip_to_end_of_block_or_statement (parser, true);
+	      goto fail;
+	    }
+	  else
+	    default_seen = true;
+	}
+      if (!strcmp (p, "when") == 0 && !default_p)
+	{
+	  error_at (match_loc, "%qs is not valid for %qs",
+		    p, "metadirective");
+	  cp_parser_skip_to_end_of_block_or_statement (parser, true);
+	  goto fail;
+	}
+
+      matching_parens parens;
+      tree ctx = NULL_TREE;
+      bool skip = false;
+
+      if (!parens.require_open (parser))
+	goto fail;
+
+      if (!default_p)
+	{
+	  ctx = cp_parser_omp_context_selector_specification (parser, false,
+							      true);
+	  if (ctx == error_mark_node)
+	    goto fail;
+	  ctx = omp_check_context_selector (match_loc, ctx, true);
+	  if (ctx == error_mark_node)
+	    goto fail;
+
+	  /* Remove the selector from further consideration if it can be
+	     evaluated as a non-match at this point.  */
+	  /* FIXME: we could still do this if the context selector
+	     doesn't have any dependent subexpressions.  */
+	  skip = (!processing_template_decl
+		  && omp_context_selector_matches (ctx, true, true) == 0);
+
+	  if (cp_lexer_next_token_is_not (parser->lexer, CPP_COLON))
+	    {
+	      cp_parser_require (parser, CPP_COLON, RT_COLON);
+	      goto fail;
+	    }
+	  cp_lexer_consume_token (parser->lexer);
+	}
+
+      /* Read in the directive type and create a dummy pragma token for
+	 it.  */
+      location_t loc = cp_lexer_peek_token (parser->lexer)->location;
+
+      const char *directive[3] = {};
+      int i;
+      for (i = 0; i < 3; i++)
+	{
+	  tree id;
+	  if (cp_lexer_peek_nth_token (parser->lexer, i + 1)->type
+	      == CPP_CLOSE_PAREN)
+	    {
+	      if (i == 0)
+		directive[i++] = "nothing";
+	      break;
+	    }
+	  else if (cp_lexer_peek_nth_token (parser->lexer, i + 1)->type
+		   == CPP_NAME)
+	    id = cp_lexer_peek_nth_token (parser->lexer, i + 1)->u.value;
+	  else if (cp_lexer_peek_nth_token (parser->lexer, i + 1)->keyword
+		   != RID_MAX)
+	    {
+	      enum rid rid
+		= cp_lexer_peek_nth_token (parser->lexer, i + 1)->keyword;
+	      id = ridpointers[rid];
+	    }
+	  else
+	    break;
+
+	  directive[i] = IDENTIFIER_POINTER (id);
+	}
+      if (i == 0)
+	{
+	  error_at (loc, "expected directive name");
+	  cp_parser_skip_to_end_of_block_or_statement (parser, true);
+	  goto fail;
+	}
+
+      const struct c_omp_directive *omp_directive
+	= c_omp_categorize_directive (directive[0],
+				      directive[1],
+				      directive[2]);
+
+      if (omp_directive == NULL)
+	{
+	  for (int j = 0; j < i; j++)
+	    cp_lexer_consume_token (parser->lexer);
+	  cp_parser_error (parser, "unknown directive name");
+	  goto fail;
+	}
+      else
+	{
+	  int token_count = 0;
+	  if (omp_directive->first) token_count++;
+	  if (omp_directive->second) token_count++;
+	  if (omp_directive->third) token_count++;
+	  for (int j = 0; j < token_count; j++)
+	    cp_lexer_consume_token (parser->lexer);
+	}
+      if (p == NULL)
+	{
+	  cp_parser_error (parser, "expected directive name");
+	  goto fail;
+	}
+      if (omp_directive->id == PRAGMA_OMP_METADIRECTIVE)
+	{
+	  cp_parser_error (parser,
+			   "metadirectives cannot be used as variants of a "
+			   "%<metadirective%>");
+	  goto fail;
+	}
+      if (omp_directive->kind == C_OMP_DIR_DECLARATIVE)
+	{
+	  sorry_at (loc, "declarative directive variants of a "
+			 "%<metadirective%> are not supported");
+	  goto fail;
+	}
+      if (omp_directive->kind == C_OMP_DIR_CONSTRUCT)
+	requires_body = true;
+
+      if (!skip)
+	{
+	  cp_token pragma_token;
+	  pragma_token.type = CPP_PRAGMA;
+	  pragma_token.location = loc;
+	  pragma_token.u.value = build_int_cst (NULL, omp_directive->id);
+
+	  directives.safe_push (omp_directive);
+	  directive_tokens.safe_push (pragma_token);
+	  ctxs.safe_push (ctx);
+	}
+
+      /* Read in tokens for the directive clauses.  */
+      int nesting_depth = 0;
+      while (1)
+	{
+	  cp_token *token = cp_lexer_peek_token (parser->lexer);
+	  switch (token->type)
+	    {
+	    case CPP_EOF:
+	    case CPP_PRAGMA_EOL:
+	      break;
+	    case CPP_OPEN_PAREN:
+	      ++nesting_depth;
+	      goto add;
+	    case CPP_CLOSE_PAREN:
+	      if (nesting_depth-- == 0)
+		break;
+	      goto add;
+	    default:
+	    add:
+	      if (!skip)
+		directive_tokens.safe_push (*token);
+	      cp_lexer_consume_token (parser->lexer);
+	      continue;
+	    }
+	  break;
+	}
+
+      cp_lexer_consume_token (parser->lexer);
+
+      if (!skip)
+	{
+	  cp_token eol_token = {};
+	  eol_token.type = CPP_PRAGMA_EOL;
+	  eol_token.keyword = RID_MAX;
+	  directive_tokens.safe_push (eol_token);
+	}
+    }
+  cp_parser_skip_to_pragma_eol (parser, pragma_tok);
+
+  if (!default_seen)
+    {
+      /* Add a default clause that evaluates to 'omp nothing'.  */
+      const struct c_omp_directive *omp_directive
+	= c_omp_categorize_directive ("nothing", NULL, NULL);
+
+      cp_token pragma_token = {};
+      pragma_token.type = CPP_PRAGMA;
+      pragma_token.keyword = RID_MAX;
+      pragma_token.location = UNKNOWN_LOCATION;
+      pragma_token.u.value = build_int_cst (NULL, PRAGMA_OMP_NOTHING);
+
+      directives.safe_push (omp_directive);
+      directive_tokens.safe_push (pragma_token);
+      ctxs.safe_push (NULL_TREE);
+
+      cp_token eol_token = {};
+      eol_token.type = CPP_PRAGMA_EOL;
+      eol_token.keyword = RID_MAX;
+      directive_tokens.safe_push (eol_token);
+    }
+
+  if (requires_body)
+    analyze_metadirective_body (parser, body_tokens, body_labels);
+
+  /* Process each candidate directive.  */
+  unsigned i;
+  tree ctx;
+  cp_lexer *lexer;
+
+  lexer = cp_lexer_alloc ();
+  lexer->debugging_p = parser->lexer->debugging_p;
+  vec_safe_reserve (lexer->buffer,
+		    directive_tokens.length () + body_tokens.length () + 2);
+
+  FOR_EACH_VEC_ELT (ctxs, i, ctx)
+    {
+      lexer->buffer->truncate (0);
+
+      /* Add the directive tokens.  */
+      do
+	lexer->buffer->quick_push (directive_tokens [directive_token_idx++]);
+      while (lexer->buffer->last ().type != CPP_PRAGMA_EOL);
+
+      /* Add the body tokens.  */
+      gcc_assert (requires_body || body_tokens.is_empty ());
+      for (unsigned j = 0; j < body_tokens.length (); j++)
+	lexer->buffer->quick_push (body_tokens[j]);
+
+      /* Make sure nothing tries to read past the end of the tokens.  */
+      cp_token eof_token = {};
+      eof_token.type = CPP_EOF;
+      eof_token.keyword = RID_MAX;
+      lexer->buffer->quick_push (eof_token);
+      lexer->buffer->quick_push (eof_token);
+
+      lexer->next_token = lexer->buffer->address();
+      lexer->last_token = lexer->next_token + lexer->buffer->length () - 1;
+
+      cp_lexer *old_lexer = parser->lexer;
+      bool old_in_metadirective_body = parser->in_metadirective_body;
+      vec<tree> *old_metadirective_body_labels
+	= parser->metadirective_body_labels;
+      unsigned int old_metadirective_region_num
+	= parser->metadirective_region_num;
+      parser->lexer = lexer;
+      cp_lexer_set_source_position_from_token (lexer->next_token);
+      parser->in_metadirective_body = true;
+      parser->metadirective_body_labels = &body_labels;
+      parser->metadirective_region_num = ++metadirective_region_count;
+
+      int prev_errorcount = errorcount;
+      tree directive = push_stmt_list ();
+      tree directive_stmt = begin_compound_stmt (0);
+
+      cp_parser_pragma (parser, pragma_compound, if_p);
+      finish_compound_stmt (directive_stmt);
+      directive = pop_stmt_list (directive);
+
+      bool standalone_p
+	= directives[i]->kind == C_OMP_DIR_STANDALONE
+	  || directives[i]->kind == C_OMP_DIR_UTILITY;
+      if (standalone_p && requires_body)
+	{
+	  /* Parsing standalone directives will not consume the body
+	     tokens, so do that here.  */
+	  if (standalone_body == NULL_TREE)
+	    {
+	      standalone_body = push_stmt_list ();
+	      cp_parser_statement (parser, NULL_TREE, false, if_p);
+	      standalone_body = pop_stmt_list (standalone_body);
+	    }
+	  else
+	    cp_parser_skip_to_end_of_block_or_statement (parser, true);
+	}
+
+      tree body = standalone_p ? standalone_body : NULL_TREE;
+      tree variant = make_omp_metadirective_variant (ctx, directive, body);
+      OMP_METADIRECTIVE_VARIANTS (ret)
+	= chainon (OMP_METADIRECTIVE_VARIANTS (ret), variant);
+
+      /* Check that all valid tokens have been consumed if no parse errors
+	 encountered.  */
+      gcc_assert (errorcount != prev_errorcount
+		  || cp_lexer_next_token_is (parser->lexer, CPP_EOF));
+
+      parser->lexer = old_lexer;
+      cp_lexer_set_source_position_from_token (old_lexer->next_token);
+      parser->in_metadirective_body = old_in_metadirective_body;
+      parser->metadirective_body_labels = old_metadirective_body_labels;
+      parser->metadirective_region_num = old_metadirective_region_num;
+    }
+
+  /* Try to resolve the metadirective early.  */
+  if (!processing_template_decl)
+    {
+      candidates = omp_early_resolve_metadirective (ret);
+      if (!candidates.is_empty ())
+	ret = c_omp_expand_metadirective (candidates);
+    }
+
+  add_stmt (ret);
+  return;
+
+fail:
+  /* Skip the metadirective pragma.  */
+  cp_parser_skip_to_pragma_eol (parser, pragma_tok);
+
+  /* Skip the metadirective body.  */
+  cp_parser_skip_to_end_of_block_or_statement (parser, true);
+}
+
+
 /* Helper function of cp_parser_omp_declare_reduction.  Parse the combiner
    expression and optional initializer clause of
    #pragma omp declare reduction.  We store the expression(s) as
@@ -51548,6 +52042,10 @@  cp_parser_pragma (cp_parser *parser, enum pragma_context context, bool *if_p)
       cp_parser_omp_nothing (parser, pragma_tok);
       return false;
 
+    case PRAGMA_OMP_METADIRECTIVE:
+      cp_parser_omp_metadirective (parser, pragma_tok, if_p);
+      return true;
+
     case PRAGMA_OMP_ERROR:
       return cp_parser_omp_error (parser, pragma_tok, context);
 
diff --git a/gcc/cp/parser.h b/gcc/cp/parser.h
index 09b356e5e73..ae9e600d095 100644
--- a/gcc/cp/parser.h
+++ b/gcc/cp/parser.h
@@ -450,6 +450,13 @@  struct GTY(()) cp_parser {
   /* Pointer to state for parsing omp_loops.  Managed by
      cp_parser_omp_for_loop in parser.cc and not used outside that file.  */
   struct omp_for_parse_data * GTY((skip)) omp_for_parse_state;
+
+  /* Set if we are processing a statement body associated with a
+     metadirective variant.  */
+  bool in_metadirective_body;
+
+  vec<tree> * GTY((skip)) metadirective_body_labels;
+  unsigned metadirective_region_num;
 };
 
 /* In parser.cc  */
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 108e929b8ee..109121be501 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -17851,6 +17851,79 @@  tsubst_omp_clauses (tree clauses, enum c_omp_region_type ort,
   return new_clauses;
 }
 
+/* Like tsubst_copy, but specifically for OpenMP context selectors.  */
+static tree
+tsubst_omp_context_selector (tree ctx, tree args, tsubst_flags_t complain,
+			     tree in_decl)
+{
+  tree new_ctx = NULL_TREE;
+  for (tree set = ctx; set; set = TREE_CHAIN (set))
+    {
+      tree selectors = NULL_TREE;
+      for (tree sel = OMP_TSS_TRAIT_SELECTORS (set); sel;
+	   sel = TREE_CHAIN (sel))
+	{
+	  enum omp_ts_code code = OMP_TS_CODE (sel);
+	  tree properties = NULL_TREE;
+	  tree score = OMP_TS_SCORE (sel);
+	  tree t;
+
+	  if (score)
+	    {
+	      score = tsubst_expr (score, args, complain, in_decl);
+	      score = fold_non_dependent_expr (score);
+	      if (!INTEGRAL_TYPE_P (TREE_TYPE (score))
+		  || TREE_CODE (score) != INTEGER_CST)
+		{
+		  error_at (cp_expr_loc_or_input_loc (score),
+			    "%<score%> argument must "
+			    "be constant integer expression");
+		  score = NULL_TREE;
+		}
+	      else if (tree_int_cst_sgn (score) < 0)
+		{
+		  error_at (cp_expr_loc_or_input_loc (score),
+			    "%<score%> argument must be non-negative");
+		  score = NULL_TREE;
+		}
+	    }
+
+	  switch (omp_ts_map[OMP_TS_CODE (sel)].tp_type)
+	      {
+	      case OMP_TRAIT_PROPERTY_DEV_NUM_EXPR:
+	      case OMP_TRAIT_PROPERTY_BOOL_EXPR:
+		t = tsubst_expr (OMP_TP_VALUE (OMP_TS_PROPERTIES (sel)),
+				 args, complain, in_decl);
+		t = fold_non_dependent_expr (t);
+		if (!INTEGRAL_TYPE_P (TREE_TYPE (t)))
+		  error_at (cp_expr_loc_or_input_loc (t),
+			    "property must be integer expression");
+		properties = make_trait_property (NULL_TREE, t, NULL_TREE);
+		break;
+	      case OMP_TRAIT_PROPERTY_CLAUSE_LIST:
+		if (OMP_TS_CODE (sel) == OMP_TRAIT_CONSTRUCT_SIMD)
+		  properties = tsubst_omp_clauses (OMP_TS_PROPERTIES (sel),
+						   C_ORT_OMP_DECLARE_SIMD,
+						   args, complain, in_decl);
+		break;
+	      default:
+		/* Nothing to do here, just copy.  */
+		for (tree prop = OMP_TS_PROPERTIES (sel);
+		     prop; prop = TREE_CHAIN (prop))
+		  properties = make_trait_property (OMP_TP_NAME (prop),
+						    OMP_TP_VALUE (prop),
+						    properties);
+	      }
+	  selectors = make_trait_selector (code, score, properties, selectors);
+	}
+      new_ctx = make_trait_set_selector (OMP_TSS_CODE (set),
+					 nreverse (selectors),
+					 new_ctx);
+    }
+  return nreverse (new_ctx);
+}
+
+
 /* Like tsubst_expr, but unshare TREE_LIST nodes.  */
 
 static tree
@@ -19411,6 +19484,52 @@  tsubst_stmt (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	}
       break;
 
+    case OMP_METADIRECTIVE:
+      {
+	tree variants = NULL_TREE;
+	for (tree v = OMP_METADIRECTIVE_VARIANTS (t); v; v = TREE_CHAIN (v))
+	  {
+	    tree ctx = OMP_METADIRECTIVE_VARIANT_SELECTOR (v);
+	    tree directive = OMP_METADIRECTIVE_VARIANT_DIRECTIVE (v);
+	    tree body = OMP_METADIRECTIVE_VARIANT_BODY (v);
+	    tree s;
+
+	    /* CTX is null if this is the default variant.  */
+	    if (ctx)
+	      {
+		ctx = tsubst_omp_context_selector (ctx, args, complain,
+						   in_decl);
+		/* Remove the selector from further consideration if it can be
+		   evaluated as a non-match at this point.  */
+		if (omp_context_selector_matches (ctx, true, true) == 0)
+		  continue;
+	      }
+	    s = push_stmt_list ();
+	    RECUR (directive);
+	    directive = pop_stmt_list (s);
+	    if (body)
+	      {
+		s = push_stmt_list ();
+		RECUR (body);
+		body = pop_stmt_list (s);
+	      }
+	    variants
+	      = chainon (variants,
+			 make_omp_metadirective_variant (ctx, directive,
+							 body));
+	  }
+	t = copy_node (t);
+	OMP_METADIRECTIVE_VARIANTS (t) = variants;
+
+	/* Try to resolve the metadirective early.  */
+	vec<struct omp_variant> candidates
+	  = omp_early_resolve_metadirective (t);
+	if (!candidates.is_empty ())
+	  t = c_omp_expand_metadirective (candidates);
+	add_stmt (t);
+	break;
+      }
+
     case TRANSACTION_EXPR:
       {
 	int flags = 0;
diff --git a/libgomp/testsuite/libgomp.c++/metadirective-template-1.C b/libgomp/testsuite/libgomp.c++/metadirective-template-1.C
new file mode 100644
index 00000000000..2d826574861
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c++/metadirective-template-1.C
@@ -0,0 +1,37 @@ 
+# include <stdio.h>
+
+template <bool tasking> int
+fib (int n)
+{
+  int i, j;
+  if (n < 2)
+    return n;
+  else if ( tasking && n < 8 )  // serial/taskless cutoff for n<8
+    return fib<false> (n);
+  else
+    {
+#pragma omp metadirective				\
+  when (user = {condition (tasking)}: task shared(i))
+      i = fib<tasking> (n - 1);
+#pragma omp metadirective					\
+  when (user = {condition (score(10): tasking)}: task shared(j))
+      j = fib<tasking> (n - 2);
+#pragma omp metadirective			\
+  when (user = {condition (tasking)}: taskwait)
+      return i + j;
+    }
+}
+
+int main ()
+{
+  int n = 15, o = 610;
+#pragma omp parallel
+#pragma omp single
+  {
+    if (fib<true> (n) != o)
+      __builtin_abort ();
+    if (fib<false> (n) != o)
+      __builtin_abort ();
+  }
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c++/metadirective-template-2.C b/libgomp/testsuite/libgomp.c++/metadirective-template-2.C
new file mode 100644
index 00000000000..9134fefc7ac
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c++/metadirective-template-2.C
@@ -0,0 +1,41 @@ 
+# include <stdio.h>
+
+template <bool tasking> int
+fib (int n, bool flag)
+{
+  int i, j;
+  if (n < 2)
+    return n;
+  else if ( tasking && flag && n < 8 )  // serial/taskless cutoff for n<8
+    return fib<false> (n, false);
+  else
+    {
+#pragma omp metadirective				\
+  when (user = {condition (tasking && flag)}: task shared(i))
+      i = fib<tasking> (n - 1, flag);
+#pragma omp metadirective					\
+  when (user = {condition (score(10): tasking && flag)}: task shared(j))
+      j = fib<tasking> (n - 2, flag);
+#pragma omp metadirective			\
+  when (user = {condition (tasking && flag)}: taskwait)
+      return i + j;
+    }
+}
+
+int main ()
+{
+  int n = 15, o = 610;
+#pragma omp parallel
+#pragma omp single
+  {
+    if (fib<true> (n, true) != o)
+      __builtin_abort ();
+    if (fib<true> (n, false) != o)
+      __builtin_abort ();
+    if (fib<false> (n, false) != o)
+      __builtin_abort ();
+    if (fib<false> (n, true) != o)
+      __builtin_abort ();
+  }
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c++/metadirective-template-3.C b/libgomp/testsuite/libgomp.c++/metadirective-template-3.C
new file mode 100644
index 00000000000..cc48fde0fda
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c++/metadirective-template-3.C
@@ -0,0 +1,41 @@ 
+# include <stdio.h>
+
+template <bool tasking> int
+fib (int n, bool flag)
+{
+  int i, j;
+  if (n < 2)
+    return n;
+  else if ( tasking && flag && n < 8 )  // serial/taskless cutoff for n<8
+    return fib<false> (n, false);
+  else
+    {
+#pragma omp metadirective				\
+  when (user = {condition (tasking && flag)}: task shared(i))	\
+  when (user = {condition (!tasking && !flag)}: nothing) \
+  otherwise (error at(execution) message("oops 1"))
+      i = fib<tasking> (n - 1, flag);
+#pragma omp metadirective					\
+  when (user = {condition (score(10): tasking && flag)}: task shared(j)) \
+  when (user = {condition (tasking || flag)} : \
+	  error at(execution) message ("oops 2"))
+      j = fib<tasking> (n - 2, flag);
+#pragma omp metadirective			\
+  when (user = {condition (tasking && flag)}: taskwait)
+      return i + j;
+    }
+}
+
+int main ()
+{
+  int n = 15, o = 610;
+#pragma omp parallel
+#pragma omp single
+  {
+    if (fib<true> (n, true) != o)
+      __builtin_abort ();
+    if (fib<false> (n, false) != o)
+      __builtin_abort ();
+  }
+  return 0;
+}