[v3,04/12] OpenMP: C front end support for metadirectives

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

Commit Message

Sandra Loosemore July 20, 2024, 8:42 p.m. UTC
  This patch adds support to the C front end to parse OpenMP metadirective
constructs.  It includes support for early parse-time resolution
of metadirectives (when possible) that will also be used by the C++ front
end.

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

gcc/c-family/ChangeLog
	* c-common.h (enum c_omp_directive_kind): Add C_OMP_DIR_META.
	(c_omp_expand_metadirective): Declare.
	* c-gimplify.cc: Include omp-general.h.
	(genericize_omp_metadirective_stmt): New.
	(c_genericize_control_stmt): Call it.
	* c-omp.cc (c_omp_directives): Add "metadirective" and fix
	commented-out stubs for the begin/end form.
	(c_omp_expand_metadirective_r): New.
	(c_omp_expand_metadirective): New.
	* c-pragma.cc (omp_pragmas): Add "metadirective".
	* c-pragma.h (enum pragma_kind): Add PRAGMA_OMP_METADIRECTIVE.

gcc/c/ChangeLog
	* c-parser.cc (struct c_parser): Add save_tokens and
	save_tokens_avail fields for saving lookahead state and fields
	for metadirective parsing.
	(c_parser_skip_until_found): Restore state using new fields.
	(c_parser_skip_to_pragma_eol): Likewise.
	(c_parser_skip_to_end_of_block_or_statement):  Add metadirective_p
	parameter; use it to control brace and parentheses behavior.
	(c_parser_handle_statement_omp_attributes): Save state using
	new c_parser fields.
	(mangle_metadirective_region_label): New.
	(c_parser_label, c_parser_statement_after_labels): Use it.
	(c_parser_pragma): Handle metadirective.
	(c_parser_omp_context_selector): Add metadirective_p flag, use it
	to gate support for non-constant user condition.
	(c_parser_omp_context_selector_specification): Add metadirective_p
	argument.
	(c_parser_finish_omp_declare_variant): Adjust call to above.
	(c_maybe_parse_omp_decl): Save state using new c_parser fields.
	(analyze_metadirective_body): New.
	(c_parser_omp_metadirective): New.

gcc/testsuite/ChangeLog
	* gcc.dg/gomp/metadirective-1.c: New.

Co-Authored-By: Kwok Cheung Yeung <kcy@codesourcery.com>
Co-Authored-By: Sandra Loosemore <sandra@codesourcery.com>
---
 gcc/c-family/c-common.h                     |   4 +-
 gcc/c-family/c-gimplify.cc                  |  27 +
 gcc/c-family/c-omp.cc                       |  60 ++-
 gcc/c-family/c-pragma.cc                    |   1 +
 gcc/c-family/c-pragma.h                     |   1 +
 gcc/c/c-parser.cc                           | 524 +++++++++++++++++++-
 gcc/testsuite/gcc.dg/gomp/metadirective-1.c |  15 +
 7 files changed, 605 insertions(+), 27 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/gomp/metadirective-1.c
  

Comments

Jakub Jelinek Aug. 16, 2024, 10:30 a.m. UTC | #1
On Sat, Jul 20, 2024 at 02:42:23PM -0600, Sandra Loosemore wrote:
> --- a/gcc/c/c-parser.cc
> +++ b/gcc/c/c-parser.cc
> @@ -263,9 +263,24 @@ struct GTY(()) c_parser {
>       otherwise NULL.  */
>    vec<c_token, va_gc> *in_omp_attribute_pragma;
>  
> +  /* When in_omp_attribute_pragma is non-null, these fields save the values
> +     of the tokens and tokens_avail fields, so that they can be restored
> +     after parsing the attribute.  Note that parsing the body of a
> +     metadirective uses its own save/restore mechanism as those can be
> +     nested with or without the attribute pragmas in the body.  */
> +    c_token * GTY((skip)) save_tokens;
> +    unsigned int save_tokens_avail;

The indentation of the above 2 is wrong.
Plus if those members are for metadirective parsing, their names are too
generic.

> +
>    /* Set for omp::decl attribute parsing to the decl to which it
>       appertains.  */
>    tree in_omp_decl_attribute;
> +
> +  /* Set if we are processing a statement body associated with a
> +     metadirective variant.  */
> +  BOOL_BITFIELD in_metadirective_body : 1;

And the member ordering creates just too much padding.
Pointer, 32-bit int, pointer, 1-bit bitfield, pointer, 32-bit int,
reordering them slightly would get rid of that.

> +
> +  vec<tree> * GTY((skip)) metadirective_body_labels;
> +  unsigned int metadirective_region_num;

But more importantly, for something parsed really rarely, wouldn't it be
better to just add a single pointer to a new structure that contains
all you need for metadirective parsing?

> +  const char *old_name = IDENTIFIER_POINTER (name);
> +  char *new_name = (char *) alloca (strlen (old_name) + 32);

  char *new_name = XALLOCAVEC (char, strlen (old_name) + 32);
please.

> +  sprintf (new_name, "%s_MDR%u", old_name, parser->metadirective_region_num);
> +  return get_identifier (new_name);
> +}
> +
>  /* Parse a label (C90 6.6.1, C99 6.8.1, C11 6.8.1).
>  
>     label:
> @@ -7431,6 +7483,9 @@ c_parser_label (c_parser *parser, tree std_attrs)
>        gcc_assert (c_parser_next_token_is (parser, CPP_COLON));
>        c_parser_consume_token (parser);
>        attrs = c_parser_gnu_attributes (parser);
> +      if (parser->in_metadirective_body
> +	  && parser->metadirective_body_labels->contains (name))
> +	name = mangle_metadirective_region_label (parser, name);
>        tlab = define_label (loc2, name);
>        if (tlab)
>  	{
> @@ -7658,8 +7713,11 @@ c_parser_statement_after_labels (c_parser *parser, bool *if_p,
>  	  c_parser_consume_token (parser);
>  	  if (c_parser_next_token_is (parser, CPP_NAME))
>  	    {
> -	      stmt = c_finish_goto_label (loc,
> -					  c_parser_peek_token (parser)->value);
> +	      tree name = c_parser_peek_token (parser)->value;
> +	      if (parser->in_metadirective_body
> +		  && parser->metadirective_body_labels->contains (name))
> +		name = mangle_metadirective_region_label (parser, name);
> +	      stmt = c_finish_goto_label (loc, name);
>  	      c_parser_consume_token (parser);
>  	    }
>  	  else if (c_parser_next_token_is (parser, CPP_MULT))
> @@ -14736,6 +14794,10 @@ c_parser_pragma (c_parser *parser, enum pragma_context context, bool *if_p)
>        c_parser_omp_nothing (parser);
>        return false;
>  
> +    case PRAGMA_OMP_METADIRECTIVE:
> +      c_parser_omp_metadirective (parser, if_p);
> +      return true;
> +
>      case PRAGMA_OMP_ERROR:
>        return c_parser_omp_error (parser, context);
>  
> @@ -24879,7 +24941,7 @@ c_parser_omp_declare_simd (c_parser *parser, enum pragma_context context)
>  
>  static tree
>  c_parser_omp_context_selector (c_parser *parser, enum omp_tss_code set,
> -			       tree parms)
> +			       tree parms, bool metadirective_p)
>  {
>    tree ret = NULL_TREE;
>    do
> @@ -25026,12 +25088,18 @@ c_parser_omp_context_selector (c_parser *parser, enum omp_tss_code set,
>  		{
>  		  mark_exp_read (t);
>  		  t = c_fully_fold (t, false, NULL);
> -		  /* FIXME: this is bogus, both device_num and
> -		     condition selectors allow arbitrary expressions.  */

Not in 5.0, that is a 5.1 feature that hasn't been implemented in the
initial declare variant implementation (and target_device set with
device_num didn't exist there at all).

> -		  if (!INTEGRAL_TYPE_P (TREE_TYPE (t))
> -		      || !tree_fits_shwi_p (t))
> -		    error_at (token->location, "property must be "
> -			      "constant integer expression");
> +		  /* FIXME: I believe it is an unimplemented feature rather
> +		     than a user error to have non-constant expressions
> +		     inside "declare variant".  */
> +		  if (!metadirective_p
> +		      && (!INTEGRAL_TYPE_P (TREE_TYPE (t))
> +			  || !tree_fits_shwi_p  (t)))
> +		    error_at (token->location,
> +			      "property must be constant integer expression");
> +		  else 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);

> +	  /* Remove the selector from further consideration if can be
> +	     evaluated as a non-match at this point.  */

if it can be ?

	Jakub
  
Sandra Loosemore Aug. 31, 2024, 10:33 p.m. UTC | #2
On 8/16/24 04:30, Jakub Jelinek wrote:
> On Sat, Jul 20, 2024 at 02:42:23PM -0600, Sandra Loosemore wrote:
>> --- a/gcc/c/c-parser.cc
>> +++ b/gcc/c/c-parser.cc
>> @@ -263,9 +263,24 @@ struct GTY(()) c_parser {
>>        otherwise NULL.  */
>>     vec<c_token, va_gc> *in_omp_attribute_pragma;
>>   
>> +  /* When in_omp_attribute_pragma is non-null, these fields save the values
>> +     of the tokens and tokens_avail fields, so that they can be restored
>> +     after parsing the attribute.  Note that parsing the body of a
>> +     metadirective uses its own save/restore mechanism as those can be
>> +     nested with or without the attribute pragmas in the body.  */
>> +    c_token * GTY((skip)) save_tokens;
>> +    unsigned int save_tokens_avail;
> 
> The indentation of the above 2 is wrong.
> Plus if those members are for metadirective parsing, their names are too
> generic.

They are not for metadirective parsing, they are to generalize the 
state-saving for OpenMP attribute-syntax directives that are converted 
to pragmas.  It's related to this patch hunk:

@@ -6846,7 +6884,6 @@ c_parser_handle_statement_omp_attributes (c_parser 
*parser
, tree &attrs,
      return false;

    unsigned int tokens_avail = parser->tokens_avail;
-  gcc_assert (parser->tokens == &parser->tokens_buf[0]);

    tokens++;
    vec<c_token, va_gc> *toks = NULL;

and the multiple instances like this:

@@ -1307,8 +1322,10 @@ c_parser_skip_until_found (c_parser *parser,
           c_token *token = c_parser_peek_token (parser);
           if (token->type == CPP_EOF)
             {
-             parser->tokens = &parser->tokens_buf[0];
-             parser->tokens_avail = token->flags;
+             parser->tokens = parser->save_tokens;
+             parser->save_tokens = NULL;
+             parser->tokens_avail = parser->save_tokens_avail;
+             parser->save_tokens_avail = 0;
               parser->in_omp_attribute_pragma = NULL;
             }
         }

where there presently is a hard-wired assumption that omp attribute 
pragma parsing is the *only* thing that messes with what parser->tokens 
points to.  Metadirectives need to maintain a separate token stream too, 
and attribute-syntax pragma directives can appear inside a 
metadirective, so fixing this somehow is necessary.  Due to the control 
flow of parsing the attribute-syntax pragmas it's not possible to save 
the state it needs in local variables on the stack, so hanging it off 
the parser object is about the only choice.

> But more importantly, for something parsed really rarely, wouldn't it be
> better to just add a single pointer to a new structure that contains
> all you need for metadirective parsing?

Do you want me to create separate new data structures for both the 
attribute-syntax pragma parsing, and the metadirective-specific bits? 
And maybe submit the attribute-syntax pragma parsing state changes as 
its own patch separate from the metadirective pieces?

-Sandra
  

Patch

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index ccaea27c2b9..588a298a9a2 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -1399,7 +1399,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 {
@@ -1413,6 +1414,7 @@  extern const struct c_omp_directive c_omp_directives[];
 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_variant> &);
 
 /* Return next tree in the chain for chain_next walking of tree nodes.  */
 inline tree
diff --git a/gcc/c-family/c-gimplify.cc b/gcc/c-family/c-gimplify.cc
index 3e29766e092..897c3328f8e 100644
--- a/gcc/c-family/c-gimplify.cc
+++ b/gcc/c-family/c-gimplify.cc
@@ -43,6 +43,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "context.h"
 #include "tree-pass.h"
 #include "internal-fn.h"
+#include "omp-general.h"
 
 /*  The gimplification pass converts the language-dependent trees
     (ld-trees) emitted by the parser into language-independent trees
@@ -485,6 +486,27 @@  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 variant = OMP_METADIRECTIVE_VARIANTS (stmt);
+       variant != NULL_TREE;
+       variant = TREE_CHAIN (variant))
+    {
+      walk_tree_1 (&OMP_METADIRECTIVE_VARIANT_DIRECTIVE (variant),
+		   func, data, NULL, lh);
+      walk_tree_1 (&OMP_METADIRECTIVE_VARIANT_BODY (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
@@ -535,6 +557,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.cc b/gcc/c-family/c-omp.cc
index b5ce1466e5d..8c55120bc6b 100644
--- a/gcc/c-family/c-omp.cc
+++ b/gcc/c-family/c-omp.cc
@@ -4280,7 +4280,7 @@  const struct c_omp_directive c_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,
@@ -4310,7 +4310,7 @@  const struct c_omp_directive c_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 },
@@ -4328,8 +4328,8 @@  const struct c_omp_directive c_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.  */
@@ -4412,3 +4412,55 @@  c_omp_categorize_directive (const char *first, const char *second,
     }
   return NULL;
 }
+
+/* Auxilliary helper function for c_omp_expand_metadirective.  */
+
+static tree
+c_omp_expand_metadirective_r (vec<struct omp_variant> &candidates,
+			      hash_map<tree, tree> &body_labels,
+			      unsigned index)
+{
+  struct omp_variant &candidate = candidates[index];
+  tree if_block = push_stmt_list ();
+  if (candidate.alternative != NULL_TREE)
+    add_stmt (candidate.alternative);
+  if (candidate.body != NULL_TREE)
+    {
+      tree *label = body_labels.get (candidate.body);
+      if (label != NULL)
+	add_stmt (build1 (GOTO_EXPR, void_type_node, *label));
+      else
+	{
+	  tree body_label = create_artificial_label (UNKNOWN_LOCATION);
+	  add_stmt (build1 (LABEL_EXPR, void_type_node, body_label));
+	  add_stmt (candidate.body);
+	  body_labels.put (candidate.body, body_label);
+	}
+    }
+  if_block = pop_stmt_list (if_block);
+
+  if (index == candidates.length () - 1)
+    return if_block;
+
+  tree cond = candidate.dynamic_selector;
+  gcc_assert (cond != NULL_TREE);
+
+  tree else_block = c_omp_expand_metadirective_r (candidates, body_labels,
+						  index + 1);
+  tree ret = push_stmt_list ();
+  tree stmt = build3 (COND_EXPR, void_type_node, cond, if_block, else_block);
+  add_stmt (stmt);
+  ret = pop_stmt_list (ret);
+
+  return ret;
+}
+
+/* Resolve the vector of metadirective variant CANDIDATES to a parse tree
+   structure.  */
+
+tree
+c_omp_expand_metadirective (vec<struct omp_variant> &candidates)
+{
+  hash_map<tree, tree> body_labels;
+  return c_omp_expand_metadirective_r (candidates, body_labels, 0);
+}
diff --git a/gcc/c-family/c-pragma.cc b/gcc/c-family/c-pragma.cc
index 25251c2b69f..6548ff45d08 100644
--- a/gcc/c-family/c-pragma.cc
+++ b/gcc/c-family/c-pragma.cc
@@ -1529,6 +1529,7 @@  static const struct omp_pragma_def omp_pragmas[] = {
   { "error", PRAGMA_OMP_ERROR },
   { "end", PRAGMA_OMP_END },
   { "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 2ebde06c471..305e829d875 100644
--- a/gcc/c-family/c-pragma.h
+++ b/gcc/c-family/c-pragma.h
@@ -64,6 +64,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.cc b/gcc/c/c-parser.cc
index 184260f3d45..dfdb8a2e963 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -263,9 +263,24 @@  struct GTY(()) c_parser {
      otherwise NULL.  */
   vec<c_token, va_gc> *in_omp_attribute_pragma;
 
+  /* When in_omp_attribute_pragma is non-null, these fields save the values
+     of the tokens and tokens_avail fields, so that they can be restored
+     after parsing the attribute.  Note that parsing the body of a
+     metadirective uses its own save/restore mechanism as those can be
+     nested with or without the attribute pragmas in the body.  */
+    c_token * GTY((skip)) save_tokens;
+    unsigned int save_tokens_avail;
+
   /* Set for omp::decl attribute parsing to the decl to which it
      appertains.  */
   tree in_omp_decl_attribute;
+
+  /* Set if we are processing a statement body associated with a
+     metadirective variant.  */
+  BOOL_BITFIELD in_metadirective_body : 1;
+
+  vec<tree> * GTY((skip)) metadirective_body_labels;
+  unsigned int metadirective_region_num;
 };
 
 /* Return a pointer to the Nth token in PARSERs tokens_buf.  */
@@ -1307,8 +1322,10 @@  c_parser_skip_until_found (c_parser *parser,
 	  c_token *token = c_parser_peek_token (parser);
 	  if (token->type == CPP_EOF)
 	    {
-	      parser->tokens = &parser->tokens_buf[0];
-	      parser->tokens_avail = token->flags;
+	      parser->tokens = parser->save_tokens;
+	      parser->save_tokens = NULL;
+	      parser->tokens_avail = parser->save_tokens_avail;
+	      parser->save_tokens_avail = 0;
 	      parser->in_omp_attribute_pragma = NULL;
 	    }
 	}
@@ -1330,8 +1347,10 @@  c_parser_skip_until_found (c_parser *parser,
 	      c_token *token = c_parser_peek_token (parser);
 	      if (token->type == CPP_EOF)
 		{
-		  parser->tokens = &parser->tokens_buf[0];
-		  parser->tokens_avail = token->flags;
+		  parser->tokens = parser->save_tokens;
+		  parser->save_tokens = NULL;
+		  parser->tokens_avail = parser->save_tokens_avail;
+		  parser->save_tokens_avail = 0;
 		  parser->in_omp_attribute_pragma = NULL;
 		}
 	    }
@@ -1424,8 +1443,10 @@  c_parser_skip_to_pragma_eol (c_parser *parser, bool error_if_not_eol = true)
       c_token *token = c_parser_peek_token (parser);
       if (token->type == CPP_EOF)
 	{
-	  parser->tokens = &parser->tokens_buf[0];
-	  parser->tokens_avail = token->flags;
+	  parser->tokens = parser->save_tokens;
+	  parser->save_tokens = NULL;
+	  parser->tokens_avail = parser->save_tokens_avail;
+	  parser->save_tokens_avail = 0;
 	  parser->in_omp_attribute_pragma = NULL;
 	}
     }
@@ -1437,9 +1458,11 @@  c_parser_skip_to_pragma_eol (c_parser *parser, bool error_if_not_eol = true)
    have consumed a non-nested ';'.  */
 
 static void
-c_parser_skip_to_end_of_block_or_statement (c_parser *parser)
+c_parser_skip_to_end_of_block_or_statement (c_parser *parser,
+					    bool metadirective_p = false)
 {
   unsigned nesting_depth = 0;
+  int bracket_depth = 0;
   bool save_error = parser->error;
 
   while (true)
@@ -1462,7 +1485,7 @@  c_parser_skip_to_end_of_block_or_statement (c_parser *parser)
 	case CPP_SEMICOLON:
 	  /* If the next token is a ';', we have reached the
 	     end of the statement.  */
-	  if (!nesting_depth)
+	  if (!nesting_depth && (!metadirective_p || bracket_depth <= 0))
 	    {
 	      /* Consume the ';'.  */
 	      c_parser_consume_token (parser);
@@ -1473,7 +1496,8 @@  c_parser_skip_to_end_of_block_or_statement (c_parser *parser)
 	case CPP_CLOSE_BRACE:
 	  /* If the next token is a non-nested '}', then we have
 	     reached the end of the current block.  */
-	  if (nesting_depth == 0 || --nesting_depth == 0)
+	  if ((nesting_depth == 0 || --nesting_depth == 0)
+	      && (!metadirective_p || bracket_depth <= 0))
 	    {
 	      c_parser_consume_token (parser);
 	      goto finished;
@@ -1486,6 +1510,19 @@  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.  */
+	  if (metadirective_p && nesting_depth == 0)
+	    ++bracket_depth;
+	  break;
+
+	case CPP_CLOSE_PAREN:
+	  if (metadirective_p && nesting_depth == 0)
+	    --bracket_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.
@@ -1725,6 +1762,7 @@  static void c_parser_omp_taskwait (c_parser *);
 static void c_parser_omp_taskyield (c_parser *);
 static void c_parser_omp_cancel (c_parser *);
 static void c_parser_omp_nothing (c_parser *);
+static void c_parser_omp_metadirective (c_parser *, bool *);
 
 enum pragma_context { pragma_external, pragma_struct, pragma_param,
 		      pragma_stmt, pragma_compound };
@@ -6839,7 +6877,6 @@  c_parser_handle_statement_omp_attributes (c_parser *parser, tree &attrs,
     return false;
 
   unsigned int tokens_avail = parser->tokens_avail;
-  gcc_assert (parser->tokens == &parser->tokens_buf[0]);
 
   tokens++;
   vec<c_token, va_gc> *toks = NULL;
@@ -6874,7 +6911,10 @@  c_parser_handle_statement_omp_attributes (c_parser *parser, tree &attrs,
   tok.flags = tokens_avail;
   toks->quick_push (tok);
 
+  gcc_assert (!parser->save_tokens);
+  parser->save_tokens = parser->tokens;
   parser->tokens = toks->address ();
+  parser->save_tokens_avail = tokens_avail;
   parser->tokens_avail = tokens;
   parser->in_omp_attribute_pragma = toks;
   return true;
@@ -7360,6 +7400,18 @@  c_parser_all_labels (c_parser *parser)
     c_parser_error (parser, "expected statement");
 }
 
+
+/* Helper function for c_parser_label: mangle a metadirective region
+   label NAME.  */
+static tree
+mangle_metadirective_region_label (c_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 a label (C90 6.6.1, C99 6.8.1, C11 6.8.1).
 
    label:
@@ -7431,6 +7483,9 @@  c_parser_label (c_parser *parser, tree std_attrs)
       gcc_assert (c_parser_next_token_is (parser, CPP_COLON));
       c_parser_consume_token (parser);
       attrs = c_parser_gnu_attributes (parser);
+      if (parser->in_metadirective_body
+	  && parser->metadirective_body_labels->contains (name))
+	name = mangle_metadirective_region_label (parser, name);
       tlab = define_label (loc2, name);
       if (tlab)
 	{
@@ -7658,8 +7713,11 @@  c_parser_statement_after_labels (c_parser *parser, bool *if_p,
 	  c_parser_consume_token (parser);
 	  if (c_parser_next_token_is (parser, CPP_NAME))
 	    {
-	      stmt = c_finish_goto_label (loc,
-					  c_parser_peek_token (parser)->value);
+	      tree name = c_parser_peek_token (parser)->value;
+	      if (parser->in_metadirective_body
+		  && parser->metadirective_body_labels->contains (name))
+		name = mangle_metadirective_region_label (parser, name);
+	      stmt = c_finish_goto_label (loc, name);
 	      c_parser_consume_token (parser);
 	    }
 	  else if (c_parser_next_token_is (parser, CPP_MULT))
@@ -14736,6 +14794,10 @@  c_parser_pragma (c_parser *parser, enum pragma_context context, bool *if_p)
       c_parser_omp_nothing (parser);
       return false;
 
+    case PRAGMA_OMP_METADIRECTIVE:
+      c_parser_omp_metadirective (parser, if_p);
+      return true;
+
     case PRAGMA_OMP_ERROR:
       return c_parser_omp_error (parser, context);
 
@@ -24879,7 +24941,7 @@  c_parser_omp_declare_simd (c_parser *parser, enum pragma_context context)
 
 static tree
 c_parser_omp_context_selector (c_parser *parser, enum omp_tss_code set,
-			       tree parms)
+			       tree parms, bool metadirective_p)
 {
   tree ret = NULL_TREE;
   do
@@ -25026,12 +25088,18 @@  c_parser_omp_context_selector (c_parser *parser, enum omp_tss_code set,
 		{
 		  mark_exp_read (t);
 		  t = c_fully_fold (t, false, NULL);
-		  /* FIXME: this is bogus, both device_num and
-		     condition selectors allow arbitrary expressions.  */
-		  if (!INTEGRAL_TYPE_P (TREE_TYPE (t))
-		      || !tree_fits_shwi_p (t))
-		    error_at (token->location, "property must be "
-			      "constant integer expression");
+		  /* FIXME: I believe it is an unimplemented feature rather
+		     than a user error to have non-constant expressions
+		     inside "declare variant".  */
+		  if (!metadirective_p
+		      && (!INTEGRAL_TYPE_P (TREE_TYPE (t))
+			  || !tree_fits_shwi_p  (t)))
+		    error_at (token->location,
+			      "property must be constant integer expression");
+		  else 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);
@@ -25112,7 +25180,8 @@  c_parser_omp_context_selector (c_parser *parser, enum omp_tss_code set,
      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)
 {
   tree ret = NULL_TREE;
   do
@@ -25137,7 +25206,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)
@@ -25213,7 +25283,8 @@  c_finish_omp_declare_variant (c_parser *parser, tree fndecl, tree parms)
   if (parms == NULL_TREE)
     parms = error_mark_node;
 
-  tree ctx = c_parser_omp_context_selector_specification (parser, parms);
+  tree ctx = c_parser_omp_context_selector_specification (parser,
+							  parms, false);
   if (ctx == error_mark_node)
     goto fail;
   ctx = omp_check_context_selector (match_loc, ctx, false);
@@ -25414,7 +25485,10 @@  c_maybe_parse_omp_decl (tree decl, tree d)
   tok.flags = tokens_avail;
   toks->quick_push (tok);
   parser->in_omp_decl_attribute = decl;
+  gcc_assert (!parser->save_tokens);
+  parser->save_tokens = parser->tokens;
   parser->tokens = toks->address ();
+  parser->save_tokens_avail = parser->tokens_avail;
   parser->tokens_avail = toks->length ();
   parser->in_omp_attribute_pragma = toks;
   c_parser_pragma (parser, pragma_external, NULL);
@@ -26890,6 +26964,412 @@  c_parser_omp_assumes (c_parser *parser)
   c_parser_omp_assumption_clauses (parser, 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 && 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:
+	  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 void
+c_parser_omp_metadirective (c_parser *parser, bool *if_p)
+{
+  static unsigned int metadirective_region_count = 0;
+
+  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_variant> candidates;
+  bool default_seen = false;
+  int directive_token_idx = 0;
+  tree standalone_body = NULL_TREE;
+  location_t pragma_loc = c_parser_peek_token (parser)->location;
+  bool requires_body = false;
+
+  ret = make_node (OMP_METADIRECTIVE);
+  SET_EXPR_LOCATION (ret, pragma_loc);
+  TREE_TYPE (ret) = void_type_node;
+  OMP_METADIRECTIVE_VARIANTS (ret) = NULL_TREE;
+
+  c_parser_consume_pragma (parser);
+  while (c_parser_next_token_is_not (parser, CPP_PRAGMA_EOL))
+    {
+      if (c_parser_next_token_is (parser, CPP_COMMA))
+	c_parser_consume_token (parser);
+      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%>, "
+			  "%<otherwise%>, or %<default%> clause");
+	  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 || strcmp (p, "otherwise") == 0;
+      if (default_p)
+	{
+	  if (default_seen)
+	    {
+	      error_at (match_loc, "too many %<otherwise%> or %<default%> "
+			"clauses in %<metadirective%>");
+	      c_parser_skip_to_end_of_block_or_statement (parser, true);
+	      goto error;
+	    }
+	  default_seen = true;
+	}
+      if (!(strcmp (p, "when") == 0 || default_p))
+	{
+	  error_at (match_loc, "%qs is not valid for %qs",
+		    p, "metadirective");
+	  c_parser_skip_to_end_of_block_or_statement (parser, true);
+	  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, true);
+	  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, true) == 0;
+
+	  if (c_parser_next_token_is_not (parser, CPP_COLON))
+	    {
+	      c_parser_require (parser, CPP_COLON, "expected %<:%>");
+	      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;
+
+      const char *directive[3] = {};
+      int i;
+      for (i = 0; i < 3; i++)
+	{
+	  tree id;
+	  if (c_parser_peek_nth_token (parser, i + 1)->type
+	      == CPP_CLOSE_PAREN)
+	    {
+	      if (i == 0)
+		directive[i++] = "nothing";
+	      break;
+	    }
+	  else if (c_parser_peek_nth_token (parser, i + 1)->type
+		   == CPP_NAME)
+	    id = c_parser_peek_nth_token (parser, i + 1)->value;
+	  else if (c_parser_peek_nth_token (parser, i + 1)->keyword
+		   != RID_MAX)
+	    {
+	      enum rid rid
+		= c_parser_peek_nth_token (parser, i + 1)->keyword;
+	      id = ridpointers[rid];
+	    }
+	  else
+	    break;
+
+	  directive[i] = IDENTIFIER_POINTER (id);
+	}
+      if (i == 0)
+	{
+	  error_at (loc, "expected directive name");
+	  c_parser_skip_to_end_of_block_or_statement (parser, true);
+	  goto error;
+	}
+
+      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++)
+	    c_parser_consume_token (parser);
+	  c_parser_error (parser, "unknown directive name");
+	  goto error;
+	}
+      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++)
+	    c_parser_consume_token (parser);
+	}
+      if (omp_directive->id == PRAGMA_OMP_METADIRECTIVE)
+	{
+	  c_parser_error (parser,
+			  "metadirectives cannot be used as variants of a "
+			  "%<metadirective%>");
+	  goto error;
+	}
+      if (omp_directive->kind == C_OMP_DIR_DECLARATIVE)
+	{
+	  sorry_at (loc, "declarative directive variants of a "
+			 "%<metadirective%> are not supported");
+	  goto error;
+	}
+      if (omp_directive->kind == C_OMP_DIR_CONSTRUCT)
+	requires_body = true;
+
+      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);
+    }
+
+  if (requires_body)
+    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.  */
+      gcc_assert (requires_body || body_tokens.is_empty ());
+      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;
+      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->tokens = tokens.address ();
+      parser->tokens_avail = tokens.length ();
+      parser->in_metadirective_body = true;
+      parser->metadirective_body_labels = &body_labels;
+      parser->metadirective_region_num = ++metadirective_region_count;
+
+      int prev_errorcount = errorcount;
+      tree directive = c_begin_compound_stmt (true);
+
+      c_parser_pragma (parser, pragma_compound, if_p);
+      directive = c_end_compound_stmt (pragma_loc, directive, true);
+      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 ();
+	      c_parser_statement (parser, if_p);
+	      standalone_body = pop_stmt_list (standalone_body);
+	    }
+	  else
+	    c_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.  */
+      if (errorcount == prev_errorcount)
+	{
+	  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;
+      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.  */
+  candidates = omp_early_resolve_metadirective (ret);
+  if (!candidates.is_empty ())
+    ret = c_omp_expand_metadirective (candidates);
+
+  add_stmt (ret);
+  return;
+
+error:
+  if (parser->in_pragma)
+    c_parser_skip_to_pragma_eol (parser);
+  c_parser_skip_to_end_of_block_or_statement (parser, true);
+}
+
 /* Main entry point to parsing most OpenMP pragmas.  */
 
 static void
diff --git a/gcc/testsuite/gcc.dg/gomp/metadirective-1.c b/gcc/testsuite/gcc.dg/gomp/metadirective-1.c
new file mode 100644
index 00000000000..2ac81bfde75
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/gomp/metadirective-1.c
@@ -0,0 +1,15 @@ 
+int main (void)
+{
+  int x, y;
+
+  /* Test nested functions inside statement body.  */
+  #pragma omp metadirective \
+    when (device={arch("nvptx")}: teams num_teams(512)) \
+    when (device={arch("gcn")}: teams num_teams(256)) \
+    default (teams num_teams(4))
+  {
+    int f (int x) { return x * 3; }
+
+    y = f (x);
+  }
+}