[1/7] openmp: Add C support for parsing metadirectives

Message ID 4a277a5e-6070-b287-7bc8-c2bcc72532a0@codesourcery.com
State New
Headers
Series openmp: OpenMP metadirectives support |

Commit Message

Kwok Cheung Yeung Dec. 10, 2021, 5:31 p.m. UTC
  This patch adds support for parsing metadirectives in the C parser.

Metadirectives are represented by a OMP_METADIRECTIVE tree node. It has 
a single operand (accessed by OMP_METADIRECTIVE_CLAUSES) which contains 
a chain of TREE_LIST nodes, each one representing a clause from the 
metadirective. TREE_PURPOSE(clause) contains the selector of the clause, 
while TREE_VALUE(clause) contains another TREE_LIST - the TREE_PURPOSE 
contains the tree for the directive, while the TREE_VALUE contains the 
standalone body (if any).

If an OMP directive has an associated body, it will be part of the tree 
at TREE_PURPOSE(TREE_VALUE(clause)) - the standalone body at 
TREE_VALUE(TREE_VALUE(clause) is only used for standalone directives 
that do not have an associated body (strictly speaking, it isn't a part 
of the directive variant at all). At present, all standalone bodies in a 
metadirective are shared, and will point to the same tree node.

Labels in the statement body are handled by first scanning the body for 
labels, then enclosing the statements in a lexical block with the found 
labels declared as local using __label__. This prevents labels in the 
body interfering with each other when the body is re-parsed.

I have removed support for the 'omp begin metadirective'..'omp end 
metadirective' form of the directive that was originally in the WIP 
patch. According to the spec, the only variant directives that can be 
used in this form must have an 'end <directive>' form (apart from the 
'nothing' directive), and in C/C++, the only directive that we support 
with an end form is 'declare target', which we currently forbid since it 
is declarative.

Kwok
From dc88559b0295104472a0cbf79de03b0549bd35f5 Mon Sep 17 00:00:00 2001
From: Kwok Cheung Yeung <kcy@codesourcery.com>
Date: Mon, 6 Dec 2021 19:15:23 +0000
Subject: [PATCH 1/7] openmp: Add C support for parsing metadirectives

This patch implements parsing for the OpenMP metadirective introduced in
OpenMP 5.0.  Metadirectives are parsed into an OMP_METADIRECTIVE node,
with the variant clauses forming a chain accessible via
OMP_METADIRECTIVE_CLAUSES.  Each clause contains the context selector
and tree for the variant.

User conditions in the selector are now permitted to be non-constant when
used in metadirectives as specified in OpenMP 5.1.

2021-12-10  Kwok Cheung Yeung  <kcy@codesourcery.com>

	gcc/
	* omp-general.c (omp_context_selector_matches): Add extra argument.
	(omp_resolve_metadirective): New stub function.
	* omp-general.h (struct omp_metadirective_variant): New.
	(omp_context_selector_matches): Add extra argument.
	(omp_resolve_metadirective): New prototype.
	* tree.def (OMP_METADIRECTIVE): New.
	* tree.h (OMP_METADIRECTIVE_CLAUSES): New macro.

	gcc/c/
	* c-parser.c (c_parser_skip_to_end_of_block_or_statement): Handle
	parentheses in statement.
	(c_parser_omp_metadirective): New prototype.
	(c_parser_omp_context_selector): Add extra argument.  Allow
	non-constant expressions.
	(c_parser_omp_context_selector_specification): Add extra argument and
	propagate it to c_parser_omp_context_selector.
	(analyze_metadirective_body): New.
	(c_parser_omp_metadirective): New.
	(c_parser_omp_construct): Handle PRAGMA_OMP_METADIRECTIVE.

	gcc/c-family
	* c-common.h (enum c_omp_directive_kind): Add C_OMP_DIR_META.
	(c_omp_expand_metadirective): New prototype.
	* c-gimplify.c (genericize_omp_metadirective_stmt): New.
	(c_genericize_control_stmt): Handle OMP_METADIRECTIVE tree nodes.
	* c-omp.c (omp_directives): Classify metadirectives as C_OMP_DIR_META.
	(c_omp_expand_metadirective): New stub function.
	* c-pragma.c (omp_pragmas): Add entry for metadirective.
	* c-pragma.h (enum pragma_kind): Add PRAGMA_OMP_METADIRECTIVE.
---
 gcc/c-family/c-common.h   |   4 +-
 gcc/c-family/c-gimplify.c |  25 +++
 gcc/c-family/c-omp.c      |  14 +-
 gcc/c-family/c-pragma.c   |   1 +
 gcc/c-family/c-pragma.h   |   1 +
 gcc/c/c-parser.c          | 403 +++++++++++++++++++++++++++++++++++++-
 gcc/omp-general.c         |  14 +-
 gcc/omp-general.h         |   9 +-
 gcc/tree.def              |   5 +
 gcc/tree.h                |   3 +
 10 files changed, 465 insertions(+), 14 deletions(-)
  

Comments

Jakub Jelinek May 27, 2022, 5:44 p.m. UTC | #1
On Fri, Dec 10, 2021 at 05:31:08PM +0000, Kwok Cheung Yeung wrote:
> This patch adds support for parsing metadirectives in the C parser.
> 
> Metadirectives are represented by a OMP_METADIRECTIVE tree node. It has a
> single operand (accessed by OMP_METADIRECTIVE_CLAUSES) which contains a

I think naming this OMP_METADIRECTIVE_CLAUSES when the operand isn't
a chain of OMP_CLAUSE trees is misleading, I think better would be
OMP_METADIRECTIVE_VARIANTS.

> I have removed support for the 'omp begin metadirective'..'omp end
> metadirective' form of the directive that was originally in the WIP patch.
> According to the spec, the only variant directives that can be used in this
> form must have an 'end <directive>' form (apart from the 'nothing'
> directive), and in C/C++, the only directive that we support with an end
> form is 'declare target', which we currently forbid since it is declarative.

I guess that is fine initially, but eventually we should have support
for parsing of omp begin metadirective and omp end metadirective even
if we just always sorry then.

> --- a/gcc/c/c-parser.c
> +++ b/gcc/c/c-parser.c
> @@ -1390,6 +1390,17 @@ c_parser_skip_to_end_of_block_or_statement (c_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.  */
> +	  nesting_depth++;
> +	  break;
> +
> +	case CPP_CLOSE_PAREN:
> +	  nesting_depth--;
> +	  break;
> +
>  	case CPP_PRAGMA:
>  	  /* If we see a pragma, consume the whole thing at once.  We
>  	     have some safeguards against consuming pragmas willy-nilly.

I find this hunk very risky, it is used in many places and I'm not convinced
that is the behavior we want everywhere else.
I'd say the options are either to copy the function and add this only to the
copy and use that in metadirective handling, or add a default bool argument
and only do something about nesting_depth if the argument is non-default.
Furthermore, I don't think we want to just blindly decrement nesting_depth,
the CPP_CLOSE_BRACE takes care of never decrementing it below zero.
And, the function uses ++nesting_depth etc. instead of nesting_depth++
so some consistency would be nice.

> @@ -19187,6 +19200,7 @@ c_parser_omp_for_loop (location_t loc, c_parser *parser, enum tree_code code,
>    location_t for_loc;
>    bool tiling = false;
>    bool inscan = false;
> +
>    vec<tree, va_gc> *for_block = make_tree_vector ();
>  
>    for (cl = clauses; cl; cl = OMP_CLAUSE_CHAIN (cl))

Why?

> @@ -21606,10 +21621,16 @@ c_parser_omp_context_selector (c_parser *parser, tree set, tree parms)
>  		{
>  		  mark_exp_read (t);
>  		  t = c_fully_fold (t, false, NULL);
> -		  if (!INTEGRAL_TYPE_P (TREE_TYPE (t))
> -		      || !tree_fits_shwi_p (t))
> +		  if (!metadirective_p
> +		      && (!INTEGRAL_TYPE_P (TREE_TYPE (t))
> +			  || !tree_fits_shwi_p (t)))
>  		    error_at (token->location, "property must be "
> -			      "constant integer expression");
> +					       "constant integer expression");
> +		  else if (metadirective_p
> +			   && !INTEGRAL_TYPE_P (TREE_TYPE (t)))
> +		    /* Allow non-constant user expressions in metadirectives.  */
> +		    error_at (token->location, "property must be "
> +					       "integer expression");
>  		  else
>  		    properties = tree_cons (NULL_TREE, t, properties);
>  		}

I don't understand this change.  In OpenMP 5.0, condition selector had to be
constant.  In OpenMP 5.1, it can be non-constant and then it is a dynamic
selector.  But there is no restriction that it must be constant for
declare variant.
I think enabling this is orthogonal to the metadirective support, so either
the initial version shouldn't support dynamic selectors and a follow-up
patch should add support for them for both metadirectives and declare
variant, or the support should be added for both at the same time.

> @@ -22930,6 +22953,368 @@ c_parser_omp_error (c_parser *parser, enum pragma_context context)
>    return false;
>  }
>  
> +/* Helper function for c_parser_omp_metadirective.  */
> +
> +static void
> +analyze_metadirective_body (c_parser *parser,
> +			    vec<c_token> &tokens,
> +			    vec<tree> &labels)
> +{
> +  int nesting_depth = 0;
> +  int bracket_depth = 0;
> +  bool ignore_label = false;
> +
> +  /* Read in the body tokens to the tokens for each candidate directive.  */
> +  while (1)
> +    {
> +      c_token *token = c_parser_peek_token (parser);
> +      bool stop = false;
> +
> +      if (c_parser_next_token_is_keyword (parser, RID_CASE))
> +	ignore_label = true;
> +
> +      switch (token->type)
> +	{
> +	case CPP_EOF:
> +	  break;
> +	case CPP_NAME:
> +	  if (!ignore_label
> +	      && c_parser_peek_2nd_token (parser)->type == CPP_COLON)
> +	    labels.safe_push (token->value);

This looks risky, not all CPP_NAME CPP_COLON adjacent tokens will be
actually labels.
E.g. in
  { struct S { int a, b; } c = { a: 1, b: 2 }; }
a and b aren't labels.
I'm afraid we need real parsing to find out what is a label and what is not.
Or for C
  { void bar (void) { goto a; a:; } bar (); }
a is indeed a label, but in a nested function (for C++ e.g. in lambda)
and I doubt we want to privatize those either.
Gathering this way just potential label candidates and later marking only
those that were actually encountered during parsing might work.

> +c_parser_omp_metadirective (location_t loc, c_parser *parser,
> +			    char *p_name, omp_clause_mask, tree *,
> +			    bool *if_p)
> +{
> +  tree ret;
> +  auto_vec<c_token> directive_tokens;
> +  auto_vec<c_token> body_tokens;
> +  auto_vec<tree> body_labels;
> +  auto_vec<const struct c_omp_directive *> directives;
> +  auto_vec<tree> ctxs;
> +  vec<struct omp_metadirective_variant> candidates;
> +  bool default_seen = false;
> +  int directive_token_idx = 0;
> +  tree standalone_body = NULL_TREE;
> +
> +  ret = make_node (OMP_METADIRECTIVE);
> +  SET_EXPR_LOCATION (ret, loc);
> +  TREE_TYPE (ret) = void_type_node;
> +  OMP_METADIRECTIVE_CLAUSES (ret) = NULL_TREE;
> +  strcat (p_name, " metadirective");
> +
> +  while (c_parser_next_token_is_not (parser, CPP_PRAGMA_EOL))
> +    {
> +      if (c_parser_next_token_is_not (parser, CPP_NAME)
> +	  && c_parser_next_token_is_not (parser, CPP_KEYWORD))
> +	{
> +	  c_parser_error (parser, "expected %<when%> or %<default%>");

Consistency would suggest
"expected %<#pragma omp%> clause"
or, if you want to spell those out,
"expected %<when%> or %<default%> clause"
But note that we'll need to add %<otherwise%> clause to the list soon
as an alias to %<default%>.

> +	  goto error;
> +	}
> +
> +      location_t match_loc = c_parser_peek_token (parser)->location;
> +      const char *p = IDENTIFIER_POINTER (c_parser_peek_token (parser)->value);
> +      c_parser_consume_token (parser);
> +      bool default_p = strcmp (p, "default") == 0;
> +      if (default_p)
> +	{
> +	  if (default_seen)
> +	    {
> +	      c_parser_error (parser, "there can only be one default clause "
> +				      "in a metadirective");

As I said elsewhere, "too many %qs clauses", "default"

> +	      goto error;
> +	    }
> +	  default_seen = true;
> +	}
> +      if (!(strcmp (p, "when") == 0 || default_p))
> +	{
> +	  c_parser_error (parser, "expected %<when%> or %<default%>");

Consistency would suggest
error_at (here, "%qs is not valid for %qs", p, "metadirective");

> +	  /* Remove the selector from further consideration if can be
> +	     evaluated as a non-match at this point.  */
> +	  skip = (omp_context_selector_matches (ctx, true) == 0);

The outer ()s aren't needed.

> +
> +	  if (c_parser_next_token_is_not (parser, CPP_COLON))
> +	    {
> +	      c_parser_error (parser, "expected colon");
> +	      goto error;
> +	    }

c_parser_require (parser, CPP_COLON, "expected %<:%>")
instead?  Or at least "expected %<:%>"

> +      /* Read in the directive type and create a dummy pragma token for
> +	 it.  */
> +      location_t loc = c_parser_peek_token (parser)->location;
> +
> +      p = NULL;
> +      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
> +	p = "nothing";
> +      else if (c_parser_next_token_is_keyword (parser, RID_FOR))
> +	{
> +	  p = "for";
> +	  c_parser_consume_token (parser);
> +	}
> +      else if (c_parser_next_token_is (parser, CPP_NAME))
> +	{
> +	  p = IDENTIFIER_POINTER (c_parser_peek_token (parser)->value);
> +	  c_parser_consume_token (parser);
> +	}
> +
> +      if (p == NULL)
> +	{
> +	  c_parser_error (parser, "expected directive name");
> +	  goto error;
> +	}
> +
> +      const struct c_omp_directive *omp_directive
> +	= c_omp_categorize_directive (p, NULL, NULL);

The NULL, NULL looks wrong.  I don't see how you'd handle
say when (whatever: target enter data) correctly then,
or any other multiple identifier directive.
You should simply peek (raw) at the next token after it and
if it is also a CPP_NAME, supply the other name, and in that
case look for the token even after it and if it is CPP_NAME, supply
that too.
            const char *directive[3] = {};
            for (int i = 0; i < 3; i++)
              {
                tree id = NULL_TREE;
                if (first + i == last)
                  break;
                if (first[i].type == CPP_NAME)
                  id = first[i].u.value;
                else if (first[i].type == CPP_KEYWORD)
                  id = ridpointers[(int) first[i].keyword];
                else
                  break;
                directive[i] = IDENTIFIER_POINTER (id);
              }
is what the C++ FE does.
> +
> +      if (omp_directive == NULL)
> +	{
> +	  c_parser_error (parser, "unknown directive name");
> +	  goto error;
> +	}
> +      if (omp_directive->id == PRAGMA_OMP_METADIRECTIVE)
> +	{
> +	  c_parser_error (parser,
> +			  "metadirectives cannot be used as directive "
> +			  "variants");

"variants of a %<metadirective%>" ?

> +	  goto error;
> +	}
> +      if (omp_directive->kind == C_OMP_DIR_DECLARATIVE)
> +	{
> +	  sorry_at (loc, "declarative directive variants are not supported");

"declarative directive variants of a %<metadirective%> not supported" ?

> +  analyze_metadirective_body (parser, body_tokens, body_labels);

I think the code above this should determine if all the directives are
standalone/informational/utility and in that case don't try to analyze
any body, no?

> @@ -23043,7 +23433,6 @@ c_parser_omp_construct (c_parser *parser, bool *if_p)
>      gcc_assert (EXPR_LOCATION (stmt) != UNKNOWN_LOCATION);
>  }
>  
> -
>  /* OpenMP 2.5:
>     # pragma omp threadprivate (variable-list) */
>  

Why?

	Jakub
  

Patch

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c089fda12e4..ef37051791f 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1257,7 +1257,8 @@  enum c_omp_directive_kind {
   C_OMP_DIR_CONSTRUCT,
   C_OMP_DIR_DECLARATIVE,
   C_OMP_DIR_UTILITY,
-  C_OMP_DIR_INFORMATIONAL
+  C_OMP_DIR_INFORMATIONAL,
+  C_OMP_DIR_META
 };
 
 struct c_omp_directive {
@@ -1270,6 +1271,7 @@  struct c_omp_directive {
 extern const struct c_omp_directive *c_omp_categorize_directive (const char *,
 								 const char *,
 								 const char *);
+extern tree c_omp_expand_metadirective (vec<struct omp_metadirective_variant> &);
 
 /* Return next tree in the chain for chain_next walking of tree nodes.  */
 static inline tree
diff --git a/gcc/c-family/c-gimplify.c b/gcc/c-family/c-gimplify.c
index 0d38b706f4c..4c5feddf041 100644
--- a/gcc/c-family/c-gimplify.c
+++ b/gcc/c-family/c-gimplify.c
@@ -449,6 +449,26 @@  genericize_omp_for_stmt (tree *stmt_p, int *walk_subtrees, void *data,
   finish_bc_block (&OMP_FOR_BODY (stmt), bc_continue, clab);
 }
 
+/* Genericize a OMP_METADIRECTIVE node *STMT_P.  */
+
+static void
+genericize_omp_metadirective_stmt (tree *stmt_p, int *walk_subtrees,
+				   void *data, walk_tree_fn func,
+				   walk_tree_lh lh)
+{
+  tree stmt = *stmt_p;
+
+  for (tree clause = OMP_METADIRECTIVE_CLAUSES (stmt);
+       clause != NULL_TREE;
+       clause = TREE_CHAIN (clause))
+    {
+      tree variant = TREE_VALUE (clause);
+      walk_tree_1 (&TREE_PURPOSE (variant), func, data, NULL, lh);
+      walk_tree_1 (&TREE_VALUE (variant), func, data, NULL, lh);
+    }
+
+  *walk_subtrees = 0;
+}
 
 /* Lower structured control flow tree nodes, such as loops.  The
    STMT_P, WALK_SUBTREES, and DATA arguments are as for the walk_tree_fn
@@ -497,6 +517,11 @@  c_genericize_control_stmt (tree *stmt_p, int *walk_subtrees, void *data,
       genericize_omp_for_stmt (stmt_p, walk_subtrees, data, func, lh);
       break;
 
+    case OMP_METADIRECTIVE:
+      genericize_omp_metadirective_stmt (stmt_p, walk_subtrees, data, func,
+					 lh);
+      break;
+
     case STATEMENT_LIST:
       if (TREE_SIDE_EFFECTS (stmt))
 	{
diff --git a/gcc/c-family/c-omp.c b/gcc/c-family/c-omp.c
index 3f84fd1b5cb..9a7a6834f1b 100644
--- a/gcc/c-family/c-omp.c
+++ b/gcc/c-family/c-omp.c
@@ -3133,7 +3133,7 @@  static const struct c_omp_directive omp_directives[] = {
   /* { "begin", "declare", "variant", PRAGMA_OMP_BEGIN,
     C_OMP_DIR_DECLARATIVE, false }, */
   /* { "begin", "metadirective", nullptr, PRAGMA_OMP_BEGIN,
-    C_OMP_DIR_???, ??? },  */
+    C_OMP_DIR_META, false },  */
   { "cancel", nullptr, nullptr, PRAGMA_OMP_CANCEL,
     C_OMP_DIR_STANDALONE, false },
   { "cancellation", "point", nullptr, PRAGMA_OMP_CANCELLATION_POINT,
@@ -3163,7 +3163,7 @@  static const struct c_omp_directive omp_directives[] = {
   /* { "end", "declare", "variant", PRAGMA_OMP_END,
     C_OMP_DIR_DECLARATIVE, false }, */
   /* { "end", "metadirective", nullptr, PRAGMA_OMP_END,
-    C_OMP_DIR_???, ??? },  */
+    C_OMP_DIR_META, false },  */
   /* error with at(execution) is C_OMP_DIR_STANDALONE.  */
   { "error", nullptr, nullptr, PRAGMA_OMP_ERROR,
     C_OMP_DIR_UTILITY, false },
@@ -3179,8 +3179,8 @@  static const struct c_omp_directive omp_directives[] = {
     C_OMP_DIR_CONSTRUCT, true },
   { "master", nullptr, nullptr, PRAGMA_OMP_MASTER,
     C_OMP_DIR_CONSTRUCT, true },
-  /* { "metadirective", nullptr, nullptr, PRAGMA_OMP_METADIRECTIVE,
-    C_OMP_DIR_???, ??? },  */
+  { "metadirective", nullptr, nullptr, PRAGMA_OMP_METADIRECTIVE,
+    C_OMP_DIR_META, false },
   { "nothing", nullptr, nullptr, PRAGMA_OMP_NOTHING,
     C_OMP_DIR_UTILITY, false },
   /* ordered with depend clause is C_OMP_DIR_STANDALONE.  */
@@ -3263,3 +3263,9 @@  c_omp_categorize_directive (const char *first, const char *second,
     }
   return NULL;
 }
+
+tree
+c_omp_expand_metadirective (vec<struct omp_metadirective_variant> &)
+{
+  return NULL_TREE;
+}
diff --git a/gcc/c-family/c-pragma.c b/gcc/c-family/c-pragma.c
index c4ed4205820..bd27de7f126 100644
--- a/gcc/c-family/c-pragma.c
+++ b/gcc/c-family/c-pragma.c
@@ -1365,6 +1365,7 @@  static const struct omp_pragma_def omp_pragmas[] = {
   { "error", PRAGMA_OMP_ERROR },
   { "end", PRAGMA_OMP_END_DECLARE_TARGET },
   { "flush", PRAGMA_OMP_FLUSH },
+  { "metadirective", PRAGMA_OMP_METADIRECTIVE },
   { "nothing", PRAGMA_OMP_NOTHING },
   { "requires", PRAGMA_OMP_REQUIRES },
   { "scope", PRAGMA_OMP_SCOPE },
diff --git a/gcc/c-family/c-pragma.h b/gcc/c-family/c-pragma.h
index 0c5b07ab4e1..145260e0c20 100644
--- a/gcc/c-family/c-pragma.h
+++ b/gcc/c-family/c-pragma.h
@@ -61,6 +61,7 @@  enum pragma_kind {
   PRAGMA_OMP_NOTHING,
   PRAGMA_OMP_MASKED,
   PRAGMA_OMP_MASTER,
+  PRAGMA_OMP_METADIRECTIVE,
   PRAGMA_OMP_ORDERED,
   PRAGMA_OMP_PARALLEL,
   PRAGMA_OMP_REQUIRES,
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index e99c84776f1..9689a221975 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -1390,6 +1390,17 @@  c_parser_skip_to_end_of_block_or_statement (c_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.  */
+	  nesting_depth++;
+	  break;
+
+	case CPP_CLOSE_PAREN:
+	  nesting_depth--;
+	  break;
+
 	case CPP_PRAGMA:
 	  /* If we see a pragma, consume the whole thing at once.  We
 	     have some safeguards against consuming pragmas willy-nilly.
@@ -1586,6 +1597,8 @@  static bool c_parser_omp_cancellation_point (c_parser *, enum pragma_context);
 static bool c_parser_omp_target (c_parser *, enum pragma_context, bool *);
 static void c_parser_omp_end_declare_target (c_parser *);
 static bool c_parser_omp_declare (c_parser *, enum pragma_context);
+static tree c_parser_omp_metadirective (location_t, c_parser *, char *,
+					omp_clause_mask, tree *, bool *);
 static void c_parser_omp_requires (c_parser *);
 static bool c_parser_omp_error (c_parser *, enum pragma_context);
 static bool c_parser_omp_ordered (c_parser *, enum pragma_context, bool *);
@@ -19187,6 +19200,7 @@  c_parser_omp_for_loop (location_t loc, c_parser *parser, enum tree_code code,
   location_t for_loc;
   bool tiling = false;
   bool inscan = false;
+
   vec<tree, va_gc> *for_block = make_tree_vector ();
 
   for (cl = clauses; cl; cl = OMP_CLAUSE_CHAIN (cl))
@@ -21398,7 +21412,8 @@  static const char *const omp_user_selectors[] = {
      score(score-expression)  */
 
 static tree
-c_parser_omp_context_selector (c_parser *parser, tree set, tree parms)
+c_parser_omp_context_selector (c_parser *parser, tree set, tree parms,
+			       bool metadirective_p)
 {
   tree ret = NULL_TREE;
   do
@@ -21606,10 +21621,16 @@  c_parser_omp_context_selector (c_parser *parser, tree set, tree parms)
 		{
 		  mark_exp_read (t);
 		  t = c_fully_fold (t, false, NULL);
-		  if (!INTEGRAL_TYPE_P (TREE_TYPE (t))
-		      || !tree_fits_shwi_p (t))
+		  if (!metadirective_p
+		      && (!INTEGRAL_TYPE_P (TREE_TYPE (t))
+			  || !tree_fits_shwi_p (t)))
 		    error_at (token->location, "property must be "
-			      "constant integer expression");
+					       "constant integer expression");
+		  else if (metadirective_p
+			   && !INTEGRAL_TYPE_P (TREE_TYPE (t)))
+		    /* Allow non-constant user expressions in metadirectives.  */
+		    error_at (token->location, "property must be "
+					       "integer expression");
 		  else
 		    properties = tree_cons (NULL_TREE, t, properties);
 		}
@@ -21675,7 +21696,8 @@  c_parser_omp_context_selector (c_parser *parser, tree set, tree parms)
      user  */
 
 static tree
-c_parser_omp_context_selector_specification (c_parser *parser, tree parms)
+c_parser_omp_context_selector_specification (c_parser *parser, tree parms,
+					     bool metadirective_p = false)
 {
   tree ret = NULL_TREE;
   do
@@ -21721,7 +21743,8 @@  c_parser_omp_context_selector_specification (c_parser *parser, tree parms)
       if (!braces.require_open (parser))
 	return error_mark_node;
 
-      tree selectors = c_parser_omp_context_selector (parser, set, parms);
+      tree selectors = c_parser_omp_context_selector (parser, set, parms,
+						      metadirective_p);
       if (selectors == error_mark_node)
 	ret = error_mark_node;
       else if (ret != error_mark_node)
@@ -22930,6 +22953,368 @@  c_parser_omp_error (c_parser *parser, enum pragma_context context)
   return false;
 }
 
+/* Helper function for c_parser_omp_metadirective.  */
+
+static void
+analyze_metadirective_body (c_parser *parser,
+			    vec<c_token> &tokens,
+			    vec<tree> &labels)
+{
+  int nesting_depth = 0;
+  int bracket_depth = 0;
+  bool ignore_label = false;
+
+  /* Read in the body tokens to the tokens for each candidate directive.  */
+  while (1)
+    {
+      c_token *token = c_parser_peek_token (parser);
+      bool stop = false;
+
+      if (c_parser_next_token_is_keyword (parser, RID_CASE))
+	ignore_label = true;
+
+      switch (token->type)
+	{
+	case CPP_EOF:
+	  break;
+	case CPP_NAME:
+	  if (!ignore_label
+	      && c_parser_peek_2nd_token (parser)->type == CPP_COLON)
+	    labels.safe_push (token->value);
+	  goto add;
+	case CPP_OPEN_BRACE:
+	  ++nesting_depth;
+	  goto add;
+	case CPP_CLOSE_BRACE:
+	  if (--nesting_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:
+	  ignore_label = false;
+	  goto add;
+	case CPP_SEMICOLON:
+	  if (nesting_depth == 0 && bracket_depth == 0)
+	    stop = true;
+	  goto add;
+	default:
+	add:
+	  tokens.safe_push (*token);
+	  if (token->type == CPP_PRAGMA)
+	    c_parser_consume_pragma (parser);
+	  else if (token->type == CPP_PRAGMA_EOL)
+	    c_parser_skip_to_pragma_eol (parser);
+	  else
+	    c_parser_consume_token (parser);
+	  if (stop)
+	    break;
+	  continue;
+	}
+      break;
+    }
+}
+
+/* OpenMP 5.0:
+
+  # pragma omp metadirective [clause[, clause]]
+*/
+
+static tree
+c_parser_omp_metadirective (location_t loc, c_parser *parser,
+			    char *p_name, omp_clause_mask, tree *,
+			    bool *if_p)
+{
+  tree ret;
+  auto_vec<c_token> directive_tokens;
+  auto_vec<c_token> body_tokens;
+  auto_vec<tree> body_labels;
+  auto_vec<const struct c_omp_directive *> directives;
+  auto_vec<tree> ctxs;
+  vec<struct omp_metadirective_variant> candidates;
+  bool default_seen = false;
+  int directive_token_idx = 0;
+  tree standalone_body = NULL_TREE;
+
+  ret = make_node (OMP_METADIRECTIVE);
+  SET_EXPR_LOCATION (ret, loc);
+  TREE_TYPE (ret) = void_type_node;
+  OMP_METADIRECTIVE_CLAUSES (ret) = NULL_TREE;
+  strcat (p_name, " metadirective");
+
+  while (c_parser_next_token_is_not (parser, CPP_PRAGMA_EOL))
+    {
+      if (c_parser_next_token_is_not (parser, CPP_NAME)
+	  && c_parser_next_token_is_not (parser, CPP_KEYWORD))
+	{
+	  c_parser_error (parser, "expected %<when%> or %<default%>");
+	  goto error;
+	}
+
+      location_t match_loc = c_parser_peek_token (parser)->location;
+      const char *p = IDENTIFIER_POINTER (c_parser_peek_token (parser)->value);
+      c_parser_consume_token (parser);
+      bool default_p = strcmp (p, "default") == 0;
+      if (default_p)
+	{
+	  if (default_seen)
+	    {
+	      c_parser_error (parser, "there can only be one default clause "
+				      "in a metadirective");
+	      goto error;
+	    }
+	  default_seen = true;
+	}
+      if (!(strcmp (p, "when") == 0 || default_p))
+	{
+	  c_parser_error (parser, "expected %<when%> or %<default%>");
+	  goto error;
+	}
+
+      matching_parens parens;
+      tree ctx = NULL_TREE;
+      bool skip = false;
+
+      if (!parens.require_open (parser))
+	goto error;
+
+      if (!default_p)
+	{
+	  ctx = c_parser_omp_context_selector_specification (parser,
+							     NULL_TREE, true);
+	  if (ctx == error_mark_node)
+	    goto error;
+	  ctx = omp_check_context_selector (match_loc, ctx);
+	  if (ctx == error_mark_node)
+	    goto error;
+
+	  /* Remove the selector from further consideration if can be
+	     evaluated as a non-match at this point.  */
+	  skip = (omp_context_selector_matches (ctx, true) == 0);
+
+	  if (c_parser_next_token_is_not (parser, CPP_COLON))
+	    {
+	      c_parser_error (parser, "expected colon");
+	      goto error;
+	    }
+	  c_parser_consume_token (parser);
+	}
+
+      /* Read in the directive type and create a dummy pragma token for
+	 it.  */
+      location_t loc = c_parser_peek_token (parser)->location;
+
+      p = NULL;
+      if (c_parser_next_token_is (parser, CPP_CLOSE_PAREN))
+	p = "nothing";
+      else if (c_parser_next_token_is_keyword (parser, RID_FOR))
+	{
+	  p = "for";
+	  c_parser_consume_token (parser);
+	}
+      else if (c_parser_next_token_is (parser, CPP_NAME))
+	{
+	  p = IDENTIFIER_POINTER (c_parser_peek_token (parser)->value);
+	  c_parser_consume_token (parser);
+	}
+
+      if (p == NULL)
+	{
+	  c_parser_error (parser, "expected directive name");
+	  goto error;
+	}
+
+      const struct c_omp_directive *omp_directive
+	= c_omp_categorize_directive (p, NULL, NULL);
+
+      if (omp_directive == NULL)
+	{
+	  c_parser_error (parser, "unknown directive name");
+	  goto error;
+	}
+      if (omp_directive->id == PRAGMA_OMP_METADIRECTIVE)
+	{
+	  c_parser_error (parser,
+			  "metadirectives cannot be used as directive "
+			  "variants");
+	  goto error;
+	}
+      if (omp_directive->kind == C_OMP_DIR_DECLARATIVE)
+	{
+	  sorry_at (loc, "declarative directive variants are not supported");
+	  goto error;
+	}
+
+      if (!skip)
+	{
+	  c_token pragma_token;
+	  pragma_token.type = CPP_PRAGMA;
+	  pragma_token.location = loc;
+	  pragma_token.pragma_kind = (enum pragma_kind) 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)
+	{
+	  c_token *token = c_parser_peek_token (parser);
+	  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);
+	      c_parser_consume_token (parser);
+	      continue;
+	    }
+	  break;
+	}
+
+      c_parser_consume_token (parser);
+
+      if (!skip)
+	{
+	  c_token eol_token;
+	  memset (&eol_token, 0, sizeof (eol_token));
+	  eol_token.type = CPP_PRAGMA_EOL;
+	  directive_tokens.safe_push (eol_token);
+	}
+    }
+  c_parser_skip_to_pragma_eol (parser);
+
+  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);
+
+      c_token pragma_token;
+      pragma_token.type = CPP_PRAGMA;
+      pragma_token.location = UNKNOWN_LOCATION;
+      pragma_token.pragma_kind = PRAGMA_OMP_NOTHING;
+
+      directives.safe_push (omp_directive);
+      directive_tokens.safe_push (pragma_token);
+      ctxs.safe_push (NULL_TREE);
+
+      c_token eol_token;
+      memset (&eol_token, 0, sizeof (eol_token));
+      eol_token.type = CPP_PRAGMA_EOL;
+      directive_tokens.safe_push (eol_token);
+    }
+
+  analyze_metadirective_body (parser, body_tokens, body_labels);
+
+  /* Process each candidate directive.  */
+  unsigned i;
+  tree ctx;
+
+  FOR_EACH_VEC_ELT (ctxs, i, ctx)
+    {
+      auto_vec<c_token> tokens;
+
+      /* Add the directive tokens.  */
+      do
+	tokens.safe_push (directive_tokens [directive_token_idx++]);
+      while (tokens.last ().type != CPP_PRAGMA_EOL);
+
+      /* Add the body tokens.  */
+      for (unsigned j = 0; j < body_tokens.length (); j++)
+	tokens.safe_push (body_tokens[j]);
+
+      /* Make sure nothing tries to read past the end of the tokens.  */
+      c_token eof_token;
+      memset (&eof_token, 0, sizeof (eof_token));
+      eof_token.type = CPP_EOF;
+      tokens.safe_push (eof_token);
+      tokens.safe_push (eof_token);
+
+      unsigned int old_tokens_avail = parser->tokens_avail;
+      c_token *old_tokens = parser->tokens;
+
+      parser->tokens = tokens.address ();
+      parser->tokens_avail = tokens.length ();
+
+      tree directive = c_begin_compound_stmt (true);
+
+      /* Declare all non-local labels that occur within the directive body
+	 as local.  */
+      for (unsigned j = 0; j < body_labels.length (); j++)
+	{
+	  tree label = declare_label (body_labels[j]);
+
+	  C_DECLARED_LABEL_FLAG (label) = 1;
+	  add_stmt (build_stmt (loc, DECL_EXPR, label));
+	}
+
+      c_parser_pragma (parser, pragma_compound, if_p);
+      directive = c_end_compound_stmt (loc, directive, true);
+      bool standalone_p
+	= directives[i]->kind == C_OMP_DIR_STANDALONE
+	  || directives[i]->kind == C_OMP_DIR_UTILITY;
+      if (standalone_p)
+	{
+	  /* Parsing standalone directives will not consume the body
+	     tokens, so do that here.  */
+	  if (standalone_body == NULL_TREE)
+	    {
+	      standalone_body = push_stmt_list ();
+	      c_parser_statement (parser, if_p);
+	      standalone_body = pop_stmt_list (standalone_body);
+	    }
+	  else
+	    c_parser_skip_to_end_of_block_or_statement (parser);
+	}
+
+      tree body = standalone_p ? standalone_body : NULL_TREE;
+      tree variant = build_tree_list (ctx, build_tree_list (directive, body));
+      OMP_METADIRECTIVE_CLAUSES (ret)
+	= chainon (OMP_METADIRECTIVE_CLAUSES (ret), variant);
+
+      /* Check that all valid tokens have been consumed.  */
+      gcc_assert (parser->tokens_avail == 2);
+      gcc_assert (c_parser_next_token_is (parser, CPP_EOF));
+      gcc_assert (c_parser_peek_2nd_token (parser)->type == CPP_EOF);
+
+      parser->tokens = old_tokens;
+      parser->tokens_avail = old_tokens_avail;
+    }
+
+  /* Try to resolve the metadirective early.  */
+  candidates = omp_resolve_metadirective (ret);
+  if (!candidates.is_empty ())
+    ret = c_omp_expand_metadirective (candidates);
+
+  add_stmt (ret);
+
+  return ret;
+
+error:
+  if (parser->in_pragma)
+    c_parser_skip_to_pragma_eol (parser);
+  c_parser_skip_to_end_of_block_or_statement (parser);
+
+  return NULL_TREE;
+}
+
 /* Main entry point to parsing most OpenMP pragmas.  */
 
 static void
@@ -23003,6 +23388,11 @@  c_parser_omp_construct (c_parser *parser, bool *if_p)
       strcpy (p_name, "#pragma omp");
       stmt = c_parser_omp_master (loc, parser, p_name, mask, NULL, if_p);
       break;
+    case PRAGMA_OMP_METADIRECTIVE:
+      strcpy (p_name, "#pragma omp");
+      stmt = c_parser_omp_metadirective (loc, parser, p_name, mask, NULL,
+					 if_p);
+      break;
     case PRAGMA_OMP_PARALLEL:
       strcpy (p_name, "#pragma omp");
       stmt = c_parser_omp_parallel (loc, parser, p_name, mask, NULL, if_p);
@@ -23043,7 +23433,6 @@  c_parser_omp_construct (c_parser *parser, bool *if_p)
     gcc_assert (EXPR_LOCATION (stmt) != UNKNOWN_LOCATION);
 }
 
-
 /* OpenMP 2.5:
    # pragma omp threadprivate (variable-list) */
 
diff --git a/gcc/omp-general.c b/gcc/omp-general.c
index 8fcca730471..9926cfd9d5f 100644
--- a/gcc/omp-general.c
+++ b/gcc/omp-general.c
@@ -1260,7 +1260,7 @@  omp_context_name_list_prop (tree prop)
    IPA, others until vectorization.  */
 
 int
-omp_context_selector_matches (tree ctx)
+omp_context_selector_matches (tree ctx, bool)
 {
   int ret = 1;
   for (tree t1 = ctx; t1; t1 = TREE_CHAIN (t1))
@@ -2624,6 +2624,18 @@  omp_lto_input_declare_variant_alt (lto_input_block *ib, cgraph_node *node,
 						 INSERT) = entryp;
 }
 
+/* Return a vector of dynamic replacement candidates for the metadirective
+   statement in METADIRECTIVE.  Return an empty vector if the metadirective
+   cannot be resolved.  */
+
+vec<struct omp_metadirective_variant>
+omp_resolve_metadirective (tree)
+{
+  vec<struct omp_metadirective_variant> variants = {};
+
+  return variants;
+}
+
 /* Encode an oacc launch argument.  This matches the GOMP_LAUNCH_PACK
    macro on gomp-constants.h.  We do not check for overflow.  */
 
diff --git a/gcc/omp-general.h b/gcc/omp-general.h
index a0c7c71148c..8c6009e9854 100644
--- a/gcc/omp-general.h
+++ b/gcc/omp-general.h
@@ -89,6 +89,12 @@  struct omp_for_data
   tree adjn1;
 };
 
+/* A structure describing a variant in a metadirective.  */
+
+struct omp_metadirective_variant
+{
+};
+
 #define OACC_FN_ATTRIB "oacc function"
 
 extern tree omp_find_clause (tree clauses, enum omp_clause_code kind);
@@ -108,10 +114,11 @@  extern int omp_constructor_traits_to_codes (tree, enum tree_code *);
 extern tree omp_check_context_selector (location_t loc, tree ctx);
 extern void omp_mark_declare_variant (location_t loc, tree variant,
 				      tree construct);
-extern int omp_context_selector_matches (tree);
+extern int omp_context_selector_matches (tree, bool = false);
 extern int omp_context_selector_set_compare (const char *, tree, tree);
 extern tree omp_get_context_selector (tree, const char *, const char *);
 extern tree omp_resolve_declare_variant (tree);
+extern vec<struct omp_metadirective_variant> omp_resolve_metadirective (tree);
 extern tree oacc_launch_pack (unsigned code, tree device, unsigned op);
 extern tree oacc_replace_fn_attrib_attr (tree attribs, tree dims);
 extern void oacc_replace_fn_attrib (tree fn, tree dims);
diff --git a/gcc/tree.def b/gcc/tree.def
index e27bc3e2b1f..91f8c4db1e3 100644
--- a/gcc/tree.def
+++ b/gcc/tree.def
@@ -1274,6 +1274,11 @@  DEFTREECODE (OMP_TARGET_ENTER_DATA, "omp_target_enter_data", tcc_statement, 1)
    Operand 0: OMP_TARGET_EXIT_DATA_CLAUSES: List of clauses.  */
 DEFTREECODE (OMP_TARGET_EXIT_DATA, "omp_target_exit_data", tcc_statement, 1)
 
+/* OpenMP - #pragma omp metadirective [clause1 ... clauseN]
+   Operand 0: OMP_METADIRECTIVE_CLAUSES: List of selectors and directive
+	variants.  */
+DEFTREECODE (OMP_METADIRECTIVE, "omp_metadirective", tcc_statement, 1)
+
 /* OMP_ATOMIC through OMP_ATOMIC_CAPTURE_NEW must be consecutive,
    or OMP_ATOMIC_SEQ_CST needs adjusting.  */
 
diff --git a/gcc/tree.h b/gcc/tree.h
index 094501bd9b1..06c8140e011 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -1471,6 +1471,9 @@  class auto_suppress_location_wrappers
 #define OMP_TARGET_EXIT_DATA_CLAUSES(NODE)\
   TREE_OPERAND (OMP_TARGET_EXIT_DATA_CHECK (NODE), 0)
 
+#define OMP_METADIRECTIVE_CLAUSES(NODE) \
+  TREE_OPERAND (OMP_METADIRECTIVE_CHECK (NODE), 0)
+
 #define OMP_SCAN_BODY(NODE)	TREE_OPERAND (OMP_SCAN_CHECK (NODE), 0)
 #define OMP_SCAN_CLAUSES(NODE)	TREE_OPERAND (OMP_SCAN_CHECK (NODE), 1)