[v3,01/12] OpenMP: metadirective tree data structures and front-end interfaces

Message ID 20240720204231.2229891-2-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 the OMP_METADIRECTIVE tree node and shared tree-level
support for manipulating metadirectives.  It defines/exposes
interfaces that will be used in subsequent patches that add front-end
and middle-end support, but nothing generates these nodes yet.

This patch also adds compile-time support for dynamic context
selectors (the target_device selector set and the condition selector
of the user selector set) for metadirectives only.  The "declare
variant" directive still supports only static selectors.

gcc/ChangeLog
	* Makefile.in (GTFILES): Move omp-general.h earlier in the list.
	* builtin-types.def (BT_FN_BOOL_INT_CONST_PTR_CONST_PTR_CONST_PTR):
	New.
	* doc/generic.texi (OpenMP): Document OMP_METADIRECTIVE and
	context selector interfaces.
	* omp-builtins.def (BUILT_IN_GOMP_EVALUATE_TARGET_DEVICE): New.
	* omp-general.cc (omp_check_context_selector): Add metadirective_p
	parameter, use it to conditionalize target_device support.
	(make_omp_metadirective_variant): New.
	(omp_context_selector_matches): Add metadirective_p and delay_p
	parameters, use them to control things that can only be matched
	late.  Handle OMP_TRAIT_SET_TARGET_DEVICE.
	(score_wide_int): Move definition to omp-general.h.
	(omp_encode_kind_arch_isa_props): New.
	(omp_dynamic_cond): New.
	(omp_context_compute_score): Handle OMP_TRAIT_SET_TARGET_DEVICE.
	(omp_resolve_late_declare_variant, omp_resolve_declare_variant):
	Adjust calls to omp_context_selector_matches.
	(sort_variant): New.
	(omp_get_dynamic_candidates): New.
	(omp_early_resolve_metadirective): New.
	* omp-general.h (score_wide_int): Moved here from omp-general.cc.
	(struct omp_variant): New.
	(OMP_METADIRECTIVE_VARIANT_SELECTOR): New.
	(OMP_METADIRECTIVE_VARIANT_DIRECTIVE): New.
	(OMP_METADIRECTIVE_VARIANT_BODY): New.
	(make_omp_metadirective_variant): Declare.
	(omp_check_context_selector): Adjust to match definition.
	(omp_context_selector_matches): Likewise.
	(omp_early_resolve_metadirective): New.
	* tree-pretty-print.cc (dump_omp_context_selector): Remove
	static qualifier.
	(dump_generic_node): Handle OMP_METADIRECTIVE.
	* tree-pretty-print.h (dump_omp_context_selector): Declare.
	* tree.def (OMP_METADIRECTIVE): New.
	* tree.h (OMP_METADIRECTIVE_VARIANTS): New.

gcc/c/ChangeLog
	* c-parser.cc (c_finish_omp_declare_variant): Update calls to
	omp_check_context_selector and omp_context_selector_matches.

gcc/cp/ChangeLog
	* decl.cc (omp_declare_variant_finalize_one):  Update call to
	omp_context_selector_matches to pass additional arguments.
	* parser.cc (cp_finish_omp_declare_variant): Likewise for
	omp_check_context_selector.

gcc/fortran/ChangeLog
	* trans-openmp.cc (gfc_trans_omp_declare_variant):  Update calls to
	omp_check_context_selector and omp_context_selector_matches.
	* types.def (BT_FN_BOOL_INT_CONST_PTR_CONST_PTR_CONST_PTR): New.

Co-Authored-By: Kwok Cheung Yeung <kcy@codesourcery.com>
Co-Authored-By: Sandra Loosemore <sandra@codesourcery.com>
---
 gcc/Makefile.in             |   2 +-
 gcc/builtin-types.def       |   2 +
 gcc/c/c-parser.cc           |   4 +-
 gcc/cp/decl.cc              |   2 +-
 gcc/cp/parser.cc            |   2 +-
 gcc/doc/generic.texi        |  32 ++++
 gcc/fortran/trans-openmp.cc |   4 +-
 gcc/fortran/types.def       |   2 +
 gcc/omp-builtins.def        |   3 +
 gcc/omp-general.cc          | 364 ++++++++++++++++++++++++++++++++++--
 gcc/omp-general.h           |  31 ++-
 gcc/tree-pretty-print.cc    |  36 +++-
 gcc/tree-pretty-print.h     |   2 +
 gcc/tree.def                |   6 +
 gcc/tree.h                  |   3 +
 15 files changed, 468 insertions(+), 27 deletions(-)
  

Comments

Tobias Burnus July 25, 2024, 2 p.m. UTC | #1
Hi Sandra,

thanks for your patch. (Disclaimer: I have not finished reading through 
your patch.)

Some upfront generic remarks:

[* When first compiling it (incremental build), I did run into the issue 
that OMP_METADIRECTIVE_CHECK wasn't declared. Thus, there seems to be a 
dependency issue causing that tree-check.h might generated after code 
that includes tree.h is processed. (Unrelated to your patch itself, but 
for completeness …)]

* Not required right now, but eventually we need to check whether 
https://gcc.gnu.org/PR112779 is fully fixed by this patch set or whether 
follow-up work is required (and if so which). There is also PR107067 for 
a Fortran ICE.

* There are some not-implemented/FIXME comments in the patches for 
missing features. I think we should ensure that those won't get 
forgotten, e.g. by filing PRs for those. – For declare variant, some PRs 
might already exist.

Can you eventually take care of the last two items?

(For the last item: e.g. 'target_device' for declare_variant, for which 
'sorry' already existed.)

* * *

I might have asked the following question before – and you might have 
answered it already:

Sandra Loosemore wrote:

> This patch adds the OMP_METADIRECTIVE tree node and shared tree-level
> support for manipulating metadirectives.  It defines/exposes
> interfaces that will be used in subsequent patches that add front-end
> and middle-end support, but nothing generates these nodes yet.

I have to admit that I do not understand the part:

> +      else if (set == OMP_TRAIT_SET_TARGET_DEVICE)
> +/* The target_device set is dynamic, so treat it as always
> +   resolvable.  */
> +continue;
> +

The current code has 3 states:

* 0 - if a trait is false; this directly returns as it cannot be fixed later

* 1 - if the all traits are known to match (initial value)

* -1 - if one trait cannot be evaluated, either because it is too early 
(e.g. during parsing) or because it is a dynamic context selector.

Thus, I had expected:

(a) ret = -1 as default in this case (not known)

(b) for cases where it is known, a 'return 0' / not-setting -1. In 
particular:

* n == const → device_num(n) – false if '< -1' and, for 
'!ENABLE_OFFLOADING || offload_targets == NULL' either false for n > 0 
or otherwise false.

* Checks similar to OMP_TRAIT_DEVICE_{KIND,ARCH,ISA}, i.e. kind(any) → 
true, kind(fpga) → false, arch(something_unknown) → false if not true 
for any device. With '!ENABLE_OFFLOADING || offload_targets == NULL', 
the kind_arch_isa check can be done as for the host.

* * *

Have I missed something and is it sensible to return 1 instead of -1 here?

* * *


> @@ -1804,6 +1834,12 @@ omp_context_selector_matches (tree ctx)
----<cut>----
            case OMP_TRAIT_USER_CONDITION:
              if (set == OMP_TRAIT_SET_USER)
>   		for (tree p = OMP_TS_PROPERTIES (ts); p; p = TREE_CHAIN (p))
>   		  if (OMP_TP_NAME (p) == NULL_TREE)
>   		    {
> +		      /* OpenMP 5.1 allows non-constant conditions for
> +			 metadirectives.  */
> +		      if (metadirective_p
> +			  && !tree_fits_shwi_p (OMP_TP_VALUE (p)))
> +			break;
> +
                      if (integer_zerop (OMP_TP_VALUE (p)))
                        return 0;
                      if (integer_nonzerop (OMP_TP_VALUE (p)))
                        break;
                      ret = -1;
                    }

----</cut>----

* Comment wording: Please change to imply >= 5.1 not == 5.0 * Comment: I 
don't see why the non-const only applies to metadirectives; the OpenMP 
 >= 5.1 seems to imply that it is also valid for declare variant. Thus, 
I would change the wording. * The current code seems to already handle 
non-const values as expected. ... except that it changes "res" to -1, 
while the idea seems to be not to modify 'ret' in this case for 
metadirectives. (Why? Same question as above).
* * *

Quotes from the specifications regarding the expressions:

The current spec has:

"Restrictions to context selectors are as follows:" …

"A variable or procedure that is referenced in an expression that 
appears in a context selector
must be visible at the location of the directive on which the context 
selector appears unless
the directive is a declare_variant directive and the variable is an 
argument of the
associated base function."

5.1 wording is the following (approx. same except for argument bit):

"All variables that are referenced in an expression that appears in
the context selector of a match clause must be accessible at a call site 
to the base function
according to the base language rules."

5.0 had (e.g. for C): "The condition(boolean-expr) selector defines a 
constant expression that must evaluate to true for the selector to be true."

* * *

> +		      if (metadirective_p
> +			  && !tree_fits_shwi_p (OMP_TP_VALUE (p)))
> +			break;
> +
>   		      if (integer_zerop (OMP_TP_VALUE (p)))
>   			return 0;
>   		      if (integer_nonzerop (OMP_TP_VALUE (p)))
> @@ -2202,9 +2238,114 @@ omp_lookup_ts_code (enum omp_tss_code set, const char *s)
>     return OMP_TRAIT_INVALID;
>   }
>   
> -/* Needs to be a GC-friendly widest_int variant, but precision is
> -   desirable to be the same on all targets.  */
> -typedef generic_wide_int <fixed_wide_int_storage <1024> > score_wide_int;
> +/* Helper for omp_dynamic_cond: encode the kind/arch/isa property-lists
> +   into strings for GOMP_evaluate_target_device.  The property-list
> +   strings are encoded similarly to those in omp_offload_kind_arch_isa,
> +   above: each trait is passed as a string, with each property for the
> +   string separated by '\0', and an extra '\0' at the end of the string.  */
> +static tree
> +omp_encode_kind_arch_isa_props (tree props)
> +{
> +  if (!props)
> +    return NULL_TREE;
> +  size_t length = 1;
> +  for (tree p = props; p; p = TREE_CHAIN (p))
> +    length += strlen (omp_context_name_list_prop (p)) + 1;
> +  char *buffer = (char *) alloca (length);
> +  size_t n = 0;
> +  for (tree p = props; p; p = TREE_CHAIN (p))
> +    {
> +      const char *str = omp_context_name_list_prop (p);
> +      strcpy (buffer + n, str);
> +      n += strlen (str) + 1;
> +    }
> +  *(buffer + n) = '\0';
> +  return build_string_literal (length, buffer);
> +}
> +
> +/* Return a tree expression representing the dynamic part of the context
> +   selector CTX.  */
> +static tree
> +omp_dynamic_cond (tree ctx)
> +{
> +  tree expr = NULL_TREE;
> +
> +  tree user = omp_get_context_selector (ctx, OMP_TRAIT_SET_USER,
> +					OMP_TRAIT_USER_CONDITION);
> +  if (user)
> +    {
> +      tree expr_list = OMP_TS_PROPERTIES (user);
> +
> +      /* The user condition is not dynamic if it is constant.  */
> +      if (!tree_fits_shwi_p (OMP_TP_VALUE (expr_list)))
> +	expr = OMP_TP_VALUE (expr_list);
> +    }
> +
> +  tree target_device
> +    = omp_get_context_selector_list (ctx, OMP_TRAIT_SET_TARGET_DEVICE);
> +  if (target_device)
> +    {
> +      tree device_num = null_pointer_node;
> +      tree kind = null_pointer_node;
> +      tree arch = null_pointer_node;
> +      tree isa = null_pointer_node;
> +
> +      tree device_num_sel
> +	= omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
> +				    OMP_TRAIT_DEVICE_NUM);
> +      if (device_num_sel)
> +	/* Generate
> +	     devnum = (num == -1) ? GOMP_DEVICE_HOST_FALLBACK : num);
> +	   to remap -1 for GOMP_* functions.  */
> +	{
> +	  tree num = OMP_TP_VALUE (OMP_TS_PROPERTIES (device_num_sel));
> +	  location_t loc = EXPR_LOCATION (num);
> +	  tree icv = build_int_cst (integer_type_node, -1);
> +	  tree compare = fold_build2_loc (loc, EQ_EXPR, unsigned_type_node,
> +					  num, icv);
> +	  tree fallback = build_int_cst (integer_type_node,
> +					 GOMP_DEVICE_HOST_FALLBACK);
> +	  device_num = fold_build3_loc (loc, COND_EXPR, integer_type_node,
> +					compare, fallback, save_expr (num));
> +	}
> +      else
> +	device_num = build_int_cst (integer_type_node, GOMP_DEVICE_ICV);
> +      tree kind_sel
> +	= omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
> +				    OMP_TRAIT_DEVICE_KIND);
> +      /* "any" is equivalent to omitting this trait selector.  */
> +      if (kind_sel
> +	  && strcmp (omp_context_name_list_prop (OMP_TS_PROPERTIES (kind_sel)),
> +		     "any"))
> +	kind = omp_encode_kind_arch_isa_props (OMP_TS_PROPERTIES (kind_sel));
> +
> +
> +      tree arch_sel
> +	= omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
> +				    OMP_TRAIT_DEVICE_ARCH);
> +      if (arch_sel)
> +	arch = omp_encode_kind_arch_isa_props (OMP_TS_PROPERTIES (arch_sel));
> +
> +      tree isa_sel
> +	= omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
> +				    OMP_TRAIT_DEVICE_ISA);
> +      if (isa_sel)
> +	isa = omp_encode_kind_arch_isa_props (OMP_TS_PROPERTIES (isa_sel));
> +
> +      /* Generate a call to GOMP_evaluate_target_device.  */
> +      tree builtin_fn
> +	= builtin_decl_explicit (BUILT_IN_GOMP_EVALUATE_TARGET_DEVICE);
> +      tree call = build_call_expr (builtin_fn, 4, device_num, kind, arch, isa);
> +
> +      if (expr == NULL_TREE)
> +	expr = call;
> +      else
> +	expr = fold_build2 (TRUTH_ANDIF_EXPR, boolean_type_node, expr, call);
> +    }
> +
> +  return expr;
> +}
> +
>   
>   /* Compute *SCORE for context selector CTX.  Return true if the score
>      would be different depending on whether it is a declare simd clone or
> @@ -2216,12 +2357,21 @@ omp_context_compute_score (tree ctx, score_wide_int *score, bool declare_simd)
>   {
>     tree selectors
>       = omp_get_context_selector_list (ctx, OMP_TRAIT_SET_CONSTRUCT);
> -  bool has_kind = omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
> -					    OMP_TRAIT_DEVICE_KIND);
> -  bool has_arch = omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
> -					    OMP_TRAIT_DEVICE_ARCH);
> -  bool has_isa = omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
> -					   OMP_TRAIT_DEVICE_ISA);
> +  bool has_kind
> +    = (omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
> +				 OMP_TRAIT_DEVICE_KIND)
> +       || omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
> +				    OMP_TRAIT_DEVICE_KIND));
> +  bool has_arch
> +    = (omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
> +				 OMP_TRAIT_DEVICE_ARCH)
> +       || omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
> +				    OMP_TRAIT_DEVICE_ARCH));
> +  bool has_isa
> +    = (omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
> +				 OMP_TRAIT_DEVICE_ISA)
> +       || omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
> +				    OMP_TRAIT_DEVICE_ISA));
>     bool ret = false;
>     *score = 1;
>     for (tree tss = ctx; tss; tss = TREE_CHAIN (tss))
> @@ -2406,7 +2556,7 @@ omp_resolve_late_declare_variant (tree alt)
>   	  nmatches++;
>   	  continue;
>   	}
> -      switch (omp_context_selector_matches (varentry1->ctx))
> +      switch (omp_context_selector_matches (varentry1->ctx, false, true))
>   	{
>   	case 0:
>             matches.safe_push (false);
> @@ -2510,7 +2660,8 @@ omp_resolve_declare_variant (tree base)
>   	 don't process it again.  */
>         if (node && node->declare_variant_alt)
>   	return base;
> -      switch (omp_context_selector_matches (TREE_VALUE (TREE_VALUE (attr))))
> +      switch (omp_context_selector_matches (TREE_VALUE (TREE_VALUE (attr)),
> +					    false, true))
>   	{
>   	case 0:
>   	  /* No match, ignore.  */
> @@ -2875,6 +3026,185 @@ omp_lto_input_declare_variant_alt (lto_input_block *ib, cgraph_node *node,
>   						 INSERT) = entryp;
>   }
>   
> +/* Comparison function for sorting routines, to sort OpenMP metadirective
> +   variants by decreasing score.  */
> +
> +static int
> +sort_variant (const void * a, const void *b, void *)
> +{
> +  score_wide_int score1
> +    = ((const struct omp_variant *) a)->score;
> +  score_wide_int score2
> +    = ((const struct omp_variant *) b)->score;
> +
> +  if (score1 > score2)
> +    return -1;
> +  else if (score1 < score2)
> +    return 1;
> +  else
> +    return 0;
> +}
> +
> +/* Return a vector of dynamic replacement candidates for the directive
> +   candidates in ALL_VARIANTS.  Return an empty vector if the metadirective
> +   cannot be resolved.  */
> +
> +static vec<struct omp_variant>
> +omp_get_dynamic_candidates (vec <struct omp_variant> &all_variants,
> +			    bool delay_p)
> +{
> +  auto_vec <struct omp_variant> variants;
> +  struct omp_variant default_variant;
> +  bool default_found = false;
> +
> +  for (unsigned int i = 0; i < all_variants.length (); i++)
> +    {
> +      struct omp_variant variant = all_variants[i];
> +
> +      if (variant.selector == NULL_TREE)
> +	{
> +	  gcc_assert (!default_found);
> +	  default_found = true;
> +	  default_variant = variant;
> +	  default_variant.score = 0;
> +	  default_variant.resolvable_p = true;
> +	  default_variant.dynamic_selector = NULL_TREE;
> +	  if (dump_file)
> +	    fprintf (dump_file,
> +		     "Considering default selector as candidate\n");
> +	  continue;
> +	}
> +
> +      variant.resolvable_p = true;
> +
> +      if (dump_file)
> +	{
> +	  fprintf (dump_file, "Considering selector ");
> +	  print_omp_context_selector (dump_file, variant.selector, TDF_NONE);
> +	  fprintf (dump_file, " as candidate - ");
> +	}
> +
> +      switch (omp_context_selector_matches (variant.selector, true, delay_p))
> +	{
> +	case -1:
> +	  variant.resolvable_p = false;
> +	  if (dump_file)
> +	    fprintf (dump_file, "unresolvable");
> +	  /* FALLTHRU */
> +	case 1:
> +	  /* TODO: Handle SIMD score?.  */
> +	  omp_context_compute_score (variant.selector, &variant.score, false);
> +	  variant.dynamic_selector = omp_dynamic_cond (variant.selector);
> +	  variants.safe_push (variant);
> +	  if (dump_file && variant.resolvable_p)
> +	    {
> +	      if (variant.dynamic_selector)
> +		fprintf (dump_file, "matched, dynamic");
> +	      else
> +		fprintf (dump_file, "matched, non-dynamic");
> +	    }
> +	  break;
> +	case 0:
> +	  if (dump_file)
> +	    fprintf (dump_file, "no match");
> +	  break;
> +	}
> +
> +      if (dump_file)
> +	fprintf (dump_file, "\n");
> +    }
> +
> +  /* There must be one default variant.  */
> +  gcc_assert (default_found);
> +
> +  /* A context selector that is a strict subset of another context selector
> +     has a score of zero.  */
> +  for (unsigned int i = 0; i < variants.length (); i++)
> +    for (unsigned int j = i + 1; j < variants.length (); j++)
> +      {
> +	int r = omp_context_selector_compare (variants[i].selector,
> +					      variants[j].selector);
> +	if (r == -1)
> +	  {
> +	    /* variant1 is a strict subset of variant2.  */
> +	    variants[i].score = 0;
> +	    break;
> +	  }
> +	else if (r == 1)
> +	  /* variant2 is a strict subset of variant1.  */
> +	  variants[j].score = 0;
> +      }
> +
> +  /* Sort the variants by decreasing score, preserving the original order
> +     in case of a tie.  */
> +  variants.stablesort (sort_variant, NULL);
> +
> +  /* Add the default as a final choice.  */
> +  variants.safe_push (default_variant);
> +
> +  /* Build the dynamic candidate list.  */
> +  for (unsigned i = 0; i < variants.length (); i++)
> +    {
> +      /* If one of the candidates is unresolvable, give up for now.  */
> +      if (!variants[i].resolvable_p)
> +	{
> +	  variants.truncate (0);
> +	  break;
> +	}
> +
> +      if (dump_file)
> +	{
> +	  fprintf (dump_file, "Adding directive variant with ");
> +
> +	  if (variants[i].selector)
> +	    {
> +	      fprintf (dump_file, "selector ");
> +	      print_omp_context_selector (dump_file, variants[i].selector,
> +					  TDF_NONE);
> +	    }
> +	  else
> +	    fprintf (dump_file, "default selector");
> +
> +	  fprintf (dump_file, " as candidate.\n");
> +	}
> +
> +      /* The last of the candidates is ended by a static selector.  */
> +      if (!variants[i].dynamic_selector)
> +	{
> +	  variants.truncate (i + 1);
> +	  break;
> +	}
> +    }
> +
> +  return variants.copy ();
> +}
> +
> +/* 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_variant>
> +omp_early_resolve_metadirective (tree metadirective)
> +{
> +  auto_vec <struct omp_variant> candidates;
> +  tree variant = OMP_METADIRECTIVE_VARIANTS (metadirective);
> +
> +  gcc_assert (variant);
> +  while (variant)
> +    {
> +      struct omp_variant candidate;
> +
> +      candidate.selector = OMP_METADIRECTIVE_VARIANT_SELECTOR (variant);
> +      candidate.alternative = OMP_METADIRECTIVE_VARIANT_DIRECTIVE (variant);
> +      candidate.body = OMP_METADIRECTIVE_VARIANT_BODY (variant);
> +
> +      candidates.safe_push (candidate);
> +      variant = TREE_CHAIN (variant);
> +    }
> +
> +  return omp_get_dynamic_candidates (candidates, true);
> +}
> +
>   /* 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 37f0548befd..8fe25b3108f 100644
> --- a/gcc/omp-general.h
> +++ b/gcc/omp-general.h
> @@ -91,6 +91,22 @@ struct omp_for_data
>     tree adjn1;
>   };
>   
> +/* Needs to be a GC-friendly widest_int variant, but precision is
> +   desirable to be the same on all targets.  */
> +typedef generic_wide_int <fixed_wide_int_storage <1024> > score_wide_int;
> +
> +/* A structure describing a variant in a metadirective.  */
> +
> +struct GTY(()) omp_variant
> +{
> +  score_wide_int score;
> +  tree selector;
> +  tree alternative;
> +  tree body;
> +  tree dynamic_selector;
> +  bool resolvable_p : 1;
> +};
> +
>   #define OACC_FN_ATTRIB "oacc function"
>   
>   /* Accessors for OMP context selectors, used by variant directives.
> @@ -150,6 +166,15 @@ extern tree make_trait_set_selector (enum omp_tss_code, tree, tree);
>   extern tree make_trait_selector (enum omp_ts_code, tree, tree, tree);
>   extern tree make_trait_property (tree, tree, tree);
>   
> +/* Accessors and constructor for metadirective variants.  */
> +#define OMP_METADIRECTIVE_VARIANT_SELECTOR(v) \
> +  TREE_PURPOSE (v)
> +#define OMP_METADIRECTIVE_VARIANT_DIRECTIVE(v) \
> +  TREE_PURPOSE (TREE_VALUE (v))
> +#define OMP_METADIRECTIVE_VARIANT_BODY(v) \
> +  TREE_VALUE (TREE_VALUE (v))
> +extern tree make_omp_metadirective_variant (tree, tree, tree);
> +
>   extern tree omp_find_clause (tree clauses, enum omp_clause_code kind);
>   extern bool omp_is_allocatable_or_ptr (tree decl);
>   extern tree omp_check_optional_argument (tree decl, bool for_present_check);
> @@ -166,15 +191,17 @@ extern poly_uint64 omp_max_vf (void);
>   extern int omp_max_simt_vf (void);
>   extern const char *omp_context_name_list_prop (tree);
>   extern void omp_construct_traits_to_codes (tree, int, enum tree_code *);
> -extern tree omp_check_context_selector (location_t loc, tree ctx);
> +extern tree omp_check_context_selector (location_t loc, tree ctx,
> +					bool metadirective_p);
>   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, bool);
>   extern int omp_context_selector_set_compare (enum omp_tss_code, tree, tree);
>   extern tree omp_get_context_selector (tree, enum omp_tss_code,
>   				      enum omp_ts_code);
>   extern tree omp_get_context_selector_list (tree, enum omp_tss_code);
>   extern tree omp_resolve_declare_variant (tree);
> +extern vec<struct omp_variant> omp_early_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-pretty-print.cc b/gcc/tree-pretty-print.cc
> index 4bb946bb0e8..59db597c10d 100644
> --- a/gcc/tree-pretty-print.cc
> +++ b/gcc/tree-pretty-print.cc
> @@ -1522,7 +1522,7 @@ dump_omp_clauses (pretty_printer *pp, tree clause, int spc, dump_flags_t flags,
>   }
>   
>   /* Dump an OpenMP context selector CTX to PP.  */
> -static void
> +void
>   dump_omp_context_selector (pretty_printer *pp, tree ctx, int spc,
>   			   dump_flags_t flags)
>   {
> @@ -4048,6 +4048,40 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
>         is_expr = false;
>         break;
>   
> +    case OMP_METADIRECTIVE:
> +      {
> +	pp_string (pp, "#pragma omp metadirective");
> +	newline_and_indent (pp, spc + 2);
> +	pp_left_brace (pp);
> +
> +	tree variant = OMP_METADIRECTIVE_VARIANTS (node);
> +	while (variant != NULL_TREE)
> +	  {
> +	    tree selector = OMP_METADIRECTIVE_VARIANT_SELECTOR (variant);
> +	    tree directive = OMP_METADIRECTIVE_VARIANT_DIRECTIVE (variant);
> +	    tree body = OMP_METADIRECTIVE_VARIANT_BODY (variant);
> +
> +	    newline_and_indent (pp, spc + 4);
> +	    if (selector == NULL_TREE)
> +	      pp_string (pp, "otherwise:");
> +	    else
> +	      {
> +		pp_string (pp, "when (");
> +		dump_omp_context_selector (pp, selector, spc + 4, flags);
> +		pp_string (pp, "):");
> +	      }
> +	    newline_and_indent (pp, spc + 6);
> +
> +	    dump_generic_node (pp, directive, spc + 6, flags, false);
> +	    newline_and_indent (pp, spc + 6);
> +	    dump_generic_node (pp, body, spc + 6, flags, false);
> +	    variant = TREE_CHAIN (variant);
> +	  }
> +	newline_and_indent (pp, spc + 2);
> +	pp_right_brace (pp);
> +      }
> +      break;
> +
>       case TRANSACTION_EXPR:
>         if (TRANSACTION_EXPR_OUTER (node))
>   	pp_string (pp, "__transaction_atomic [[outer]]"); diff --git a/gcc/tree-pretty-print.h b/gcc/tree-pretty-print.h 
> index c5089f82cf6..d9d4f82909f 100644 --- a/gcc/tree-pretty-print.h 
> +++ b/gcc/tree-pretty-print.h @@ -45,6 +45,8 @@ extern void 
> dump_omp_atomic_memory_order (pretty_printer *, enum 
> omp_memory_order); extern void dump_omp_loop_non_rect_expr 
> (pretty_printer *, tree, int, dump_flags_t); +extern void 
> dump_omp_context_selector (pretty_printer *, tree, int, + 
> dump_flags_t); extern void print_omp_context_selector (FILE *, tree, 
> dump_flags_t); extern int dump_generic_node (pretty_printer *, tree, 
> int, dump_flags_t, bool); extern void print_declaration 
> (pretty_printer *, tree, int, dump_flags_t); diff --git a/gcc/tree.def 
> b/gcc/tree.def index 85ab182c6f5..155a508f0d3 100644 --- 
> a/gcc/tree.def +++ b/gcc/tree.def @@ -1348,6 +1348,12 @@ 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 [variant1 ... variantN]
> +   Operand 0: OMP_METADIRECTIVE_VARIANTS: List of selectors and directive
> +   variants.  Use the interface in omp-general.h to construct variants
> +   and access their fields.  */
> +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 28e8e71b036..dfd738353b8 100644
> --- a/gcc/tree.h
> +++ b/gcc/tree.h
> @@ -1600,6 +1600,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_VARIANTS(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)
>   
> -- 2.25.1
  
Sandra Loosemore July 25, 2024, 7:13 p.m. UTC | #2
On 7/25/24 08:00, Tobias Burnus wrote:
> Hi Sandra,
> 
> thanks for your patch. (Disclaimer: I have not finished reading through 
> your patch.)
> 
> Some upfront generic remarks:
> 
> [* When first compiling it (incremental build), I did run into the issue 
> that OMP_METADIRECTIVE_CHECK wasn't declared. Thus, there seems to be a 
> dependency issue causing that tree-check.h might generated after code 
> that includes tree.h is processed. (Unrelated to your patch itself, but 
> for completeness …)]

I've never run into this.  Are you saying some .cc file is missing a 
makefile dependency on tree-check.h?  Which one?  Or is it tree-check.h 
that is missing a dependency on something else and failing to get 
regenerated?  Or both?

> * Not required right now, but eventually we need to check whether 
> https://gcc.gnu.org/PR112779 is fully fixed by this patch set or whether 
> follow-up work is required (and if so which). There is also PR107067 for 
> a Fortran ICE.
> 
> * There are some not-implemented/FIXME comments in the patches for 
> missing features. I think we should ensure that those won't get 
> forgotten, e.g. by filing PRs for those. – For declare variant, some PRs 
> might already exist.
> 
> Can you eventually take care of the last two items?

Yes.

> 
> (For the last item: e.g. 'target_device' for declare_variant, for which 
> 'sorry' already existed.)
> 
> * * *
> 
> I might have asked the following question before – and you might have 
> answered it already:
> 
> Sandra Loosemore wrote:
> 
>> This patch adds the OMP_METADIRECTIVE tree node and shared tree-level
>> support for manipulating metadirectives.  It defines/exposes
>> interfaces that will be used in subsequent patches that add front-end
>> and middle-end support, but nothing generates these nodes yet.
> 
> I have to admit that I do not understand the part:
> 
>> +      else if (set == OMP_TRAIT_SET_TARGET_DEVICE)
>> +/* The target_device set is dynamic, so treat it as always
>> +   resolvable.  */
>> +continue;
>> +
> 
> The current code has 3 states:
> 
> * 0 - if a trait is false; this directly returns as it cannot be fixed 
> later
> 
> * 1 - if the all traits are known to match (initial value)
> 
> * -1 - if one trait cannot be evaluated, either because it is too early 
> (e.g. during parsing) or because it is a dynamic context selector.

Your last assertion is wrong.  The comments on the top of 
omp_context_selector_matches explicitly *say* "Dynamic properties (which 
are evaluated at run-time) should always return 1."  This is because 
dynamic selectors are *always* candidates, which are then scored and 
sorted according to the rules in the spec for the metadirective and 
declare variant constructs.

Maybe the problem is the name of the function....  Would it help if I 
renamed it from "omp_context_selector_matches" to 
"omp_context_selector_is_candidate"?

>> @@ -1804,6 +1834,12 @@ omp_context_selector_matches (tree ctx)
> ----<cut>----
>             case OMP_TRAIT_USER_CONDITION:
>               if (set == OMP_TRAIT_SET_USER)
>>           for (tree p = OMP_TS_PROPERTIES (ts); p; p = TREE_CHAIN (p))
>>             if (OMP_TP_NAME (p) == NULL_TREE)
>>               {
>> +              /* OpenMP 5.1 allows non-constant conditions for
>> +             metadirectives.  */
>> +              if (metadirective_p
>> +              && !tree_fits_shwi_p (OMP_TP_VALUE (p)))
>> +            break;
>> +
>                       if (integer_zerop (OMP_TP_VALUE (p)))
>                         return 0;
>                       if (integer_nonzerop (OMP_TP_VALUE (p)))
>                         break;
>                       ret = -1;
>                     }
> 
> ----</cut>----
> 
> * Comment wording: Please change to imply >= 5.1 not == 5.0 * Comment: I 
> don't see why the non-const only applies to metadirectives; the OpenMP 
>  >= 5.1 seems to imply that it is also valid for declare variant. Thus, 
> I would change the wording. 

The first 7 patches in the posted set implement support for dynamic 
selectors in metadirectives.  Dynamic selector support for declare 
variant comes in the later patches, which further modify this code.

If you'd find it easier to review, I can smash everything together into 
one gigantic patch (or some smaller number of patches), but I fear it 
would make everything harder to review given the amount of removed or 
moved around.  Alternatively, trying to split it into more but smaller 
incremental patches is problematical due to inter-dependencies and, 
again, multiple patches touching the same bits of code, adding and 
removing temporary stubs, etc.

> * The current code seems to already handle 
> non-const values as expected. ... except that it changes "res" to -1, 
> while the idea seems to be not to modify 'ret' in this case for 
> metadirectives. (Why? Same question as above).

This gets back to same point as earlier, dynamic selectors are always 
candidates.  According to the spec, the "user" selector is dynamic if 
the "condition" is not a constant, so the function should return 1.  The 
original code, without dynamic selector support, returned -1 because it 
actually required a constant expression but didn't have one yet -- e.g. 
I recall an issue with the Fortran front end not substituting the value 
of symbolic constants or doing any constant-folding on them.  So it was 
trying simply to defer a decision about that selector until later in 
compilation.

-Sandra
  
Jakub Jelinek Aug. 9, 2024, 4:42 p.m. UTC | #3
On Sat, Jul 20, 2024 at 02:42:20PM -0600, Sandra Loosemore wrote:
> +static tree
> +omp_encode_kind_arch_isa_props (tree props)
> +{
> +  if (!props)
> +    return NULL_TREE;
> +  size_t length = 1;
> +  for (tree p = props; p; p = TREE_CHAIN (p))
> +    length += strlen (omp_context_name_list_prop (p)) + 1;
> +  char *buffer = (char *) alloca (length);

This should be
  char *buffer = XALLOCAVEC (char, length);

> +	/* Generate
> +	     devnum = (num == -1) ? GOMP_DEVICE_HOST_FALLBACK : num);
> +	   to remap -1 for GOMP_* functions.  */

In the comment there are 2 closing parens vs. one opening, should
the one before ; be dropped?

> @@ -150,6 +166,15 @@ extern tree make_trait_set_selector (enum omp_tss_code, tree, tree);
>  extern tree make_trait_selector (enum omp_ts_code, tree, tree, tree);
>  extern tree make_trait_property (tree, tree, tree);
>  
> +/* Accessors and constructor for metadirective variants.  */
> +#define OMP_METADIRECTIVE_VARIANT_SELECTOR(v) \
> +  TREE_PURPOSE (v)
> +#define OMP_METADIRECTIVE_VARIANT_DIRECTIVE(v) \
> +  TREE_PURPOSE (TREE_VALUE (v))
> +#define OMP_METADIRECTIVE_VARIANT_BODY(v) \
> +  TREE_VALUE (TREE_VALUE (v))

I think the above macros should go to tree.h after OMP_METADIRECTIVE_VARIANTS
definition, not in this header (plus the docs adjusted not to talk about
omp-general.h).

Why needs omp-general.h the move in GTFILES etc.?  Will just moving
those definitions be ok?

Otherwise LGTM.

	Jakub
  
Sandra Loosemore Aug. 19, 2024, 7:12 p.m. UTC | #4
On 8/9/24 10:42, Jakub Jelinek wrote:
> 
> Why needs omp-general.h the move in GTFILES etc.?  Will just moving
> those definitions be ok?

I just double-checked that.  The Makefile.in change to reorder 
omp-general.h in GTFILES is because of moving the definition of 
score_wide_int there from omp-general.cc.  Otherwise the gtype 
processing seems to be confused by getting the two files in the wrong order:

/path/to/gcc/omp-general.h:96: type `score_wide_int' previously defined
/path/to/gcc/omp-general.cc:2436: previously defined here
make[3]: *** [Makefile:2979: s-gtype] Error 1

I'll add a note to the changelog line for that to explain what it's for.

> Otherwise LGTM.

Thanks for the review!

-Sandra
  

Patch

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index f4bb4a88cf3..55641fffcaa 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -2880,6 +2880,7 @@  GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
   $(srcdir)/tree-ssa-operands.h \
   $(srcdir)/tree-profile.cc $(srcdir)/tree-nested.cc \
   $(srcdir)/omp-offload.h \
+  $(srcdir)/omp-general.h \
   $(srcdir)/omp-general.cc \
   $(srcdir)/omp-low.cc \
   $(srcdir)/targhooks.cc $(out_file) $(srcdir)/passes.cc \
@@ -2906,7 +2907,6 @@  GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
   $(srcdir)/ipa-strub.cc \
   $(srcdir)/internal-fn.h \
   $(srcdir)/calls.cc \
-  $(srcdir)/omp-general.h \
   $(srcdir)/analyzer/analyzer-language.cc \
   @all_gtfiles@
 
diff --git a/gcc/builtin-types.def b/gcc/builtin-types.def
index c97d6bad1de..605a38ab84d 100644
--- a/gcc/builtin-types.def
+++ b/gcc/builtin-types.def
@@ -878,6 +878,8 @@  DEF_FUNCTION_TYPE_4 (BT_FN_VOID_UINT_PTR_INT_PTR, BT_VOID, BT_INT, BT_PTR,
 		     BT_INT, BT_PTR)
 DEF_FUNCTION_TYPE_4 (BT_FN_BOOL_UINT_UINT_UINT_BOOL,
 		     BT_BOOL, BT_UINT, BT_UINT, BT_UINT, BT_BOOL)
+DEF_FUNCTION_TYPE_4 (BT_FN_BOOL_INT_CONST_PTR_CONST_PTR_CONST_PTR,
+		     BT_BOOL, BT_INT, BT_CONST_PTR, BT_CONST_PTR, BT_CONST_PTR)
 
 DEF_FUNCTION_TYPE_5 (BT_FN_INT_STRING_INT_SIZE_CONST_STRING_VALIST_ARG,
 		     BT_INT, BT_STRING, BT_INT, BT_SIZE, BT_CONST_STRING,
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 12c5ed5d92c..184260f3d45 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -25216,7 +25216,7 @@  c_finish_omp_declare_variant (c_parser *parser, tree fndecl, tree parms)
   tree ctx = c_parser_omp_context_selector_specification (parser, parms);
   if (ctx == error_mark_node)
     goto fail;
-  ctx = omp_check_context_selector (match_loc, ctx);
+  ctx = omp_check_context_selector (match_loc, ctx, false);
   if (ctx != error_mark_node && variant != error_mark_node)
     {
       if (TREE_CODE (variant) != FUNCTION_DECL)
@@ -25249,7 +25249,7 @@  c_finish_omp_declare_variant (c_parser *parser, tree fndecl, tree parms)
 	  tree construct
 	    = omp_get_context_selector_list (ctx, OMP_TRAIT_SET_CONSTRUCT);
 	  omp_mark_declare_variant (match_loc, variant, construct);
-	  if (omp_context_selector_matches (ctx))
+	  if (omp_context_selector_matches (ctx, false, true))
 	    {
 	      tree attr
 		= tree_cons (get_identifier ("omp declare variant base"),
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 6b686d75a49..4594da909f6 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -8380,7 +8380,7 @@  omp_declare_variant_finalize_one (tree decl, tree attr)
 	  tree construct
 	    = omp_get_context_selector_list (ctx, OMP_TRAIT_SET_CONSTRUCT);
 	  omp_mark_declare_variant (match_loc, variant, construct);
-	  if (!omp_context_selector_matches (ctx))
+	  if (!omp_context_selector_matches (ctx, false, true))
 	    return true;
 	  TREE_PURPOSE (TREE_VALUE (attr)) = variant;
 	}
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index efd5d6f29a7..045b3fe7a5b 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -48978,7 +48978,7 @@  cp_finish_omp_declare_variant (cp_parser *parser, cp_token *pragma_tok,
   tree ctx = cp_parser_omp_context_selector_specification (parser, true);
   if (ctx == error_mark_node)
     goto fail;
-  ctx = omp_check_context_selector (match_loc, ctx);
+  ctx = omp_check_context_selector (match_loc, ctx, false);
   if (ctx != error_mark_node && variant != error_mark_node)
     {
       tree match_loc_node = maybe_wrap_with_location (integer_zero_node,
diff --git a/gcc/doc/generic.texi b/gcc/doc/generic.texi
index c596b7d44b2..e869203df0e 100644
--- a/gcc/doc/generic.texi
+++ b/gcc/doc/generic.texi
@@ -2335,6 +2335,7 @@  edge.  Rethrowing the exception is represented using @code{RESX_EXPR}.
 @tindex OMP_CONTINUE
 @tindex OMP_ATOMIC
 @tindex OMP_CLAUSE
+@tindex OMP_METADIRECTIVE
 
 All the statements starting with @code{OMP_} represent directives and
 clauses used by the OpenMP API @w{@uref{https://www.openmp.org}}.
@@ -2552,6 +2553,37 @@  same clause @code{C} need to be represented as multiple @code{C} clauses
 chained together.  This facilitates adding new clauses during
 compilation.
 
+@item OMP_METADIRECTIVE
+
+Represents @code{#pragma omp metadirective}.  This node has one field,
+accessed by the @code{OMP_METADIRECTIVE_VARIANTS (@var{node})} macro.
+
+Metadirective variants are represented internally as @code{TREE_LIST} nodes
+but you should use the interface provided in @file{omp-general.h} to
+access their components.
+
+@code{OMP_METADIRECTIVE_VARIANT_SELECTOR (@var{variant})}
+is the selector associated with the variant; this is null for the
+@samp{otherwise}/@samp{default} alternative.
+
+@code{OMP_METADIRECTIVE_VARIANT_DIRECTIVE (@var{variant})} is the
+nested directive for the variant.
+
+@code{OMP_METADIRECTIVE_VARIANT_BODY (@var{variant})} represents
+statements following a nested standalone or utility directive.
+In other cases, this field is null and the body is part of the
+nested directive instead.
+
+Metadirective context selectors (as well as context selectors for
+@code{#pragma omp declare variant}) are also represented internally using
+a @code{TREE_LIST} representation but with accessors and constructors
+declared in @file{omp-general.h}.  A complete context selector is a list of
+trait-set selectors, which are in turn composed of a list of trait selectors,
+each of which may have a list of trait properties.
+Identifiers for trait-set selectors and trait selectors are enums
+defined in @file{omp-selectors.h}, while trait property identifiers are
+string constants.
+
 @end table
 
 @node OpenACC
diff --git a/gcc/fortran/trans-openmp.cc b/gcc/fortran/trans-openmp.cc
index df1bf144e23..ceeb9e366b1 100644
--- a/gcc/fortran/trans-openmp.cc
+++ b/gcc/fortran/trans-openmp.cc
@@ -8555,7 +8555,7 @@  gfc_trans_omp_declare_variant (gfc_namespace *ns)
 	  continue;
 	}
       set_selectors = omp_check_context_selector
-	  (gfc_get_location (&odv->where), set_selectors);
+	(gfc_get_location (&odv->where), set_selectors, false);
       if (set_selectors != error_mark_node)
 	{
 	  if (!variant_proc_sym->attr.implicit_type
@@ -8592,7 +8592,7 @@  gfc_trans_omp_declare_variant (gfc_namespace *ns)
 	      omp_mark_declare_variant (gfc_get_location (&odv->where),
 					gfc_get_symbol_decl (variant_proc_sym),
 					construct);
-	      if (omp_context_selector_matches (set_selectors))
+	      if (omp_context_selector_matches (set_selectors, false, true))
 		{
 		  tree id = get_identifier ("omp declare variant base");
 		  tree variant = gfc_get_symbol_decl (variant_proc_sym);
diff --git a/gcc/fortran/types.def b/gcc/fortran/types.def
index 390cc9542f7..f1e2973e5ff 100644
--- a/gcc/fortran/types.def
+++ b/gcc/fortran/types.def
@@ -176,6 +176,8 @@  DEF_FUNCTION_TYPE_4 (BT_FN_VOID_UINT_PTR_INT_PTR, BT_VOID, BT_INT, BT_PTR,
 		     BT_INT, BT_PTR)
 DEF_FUNCTION_TYPE_4 (BT_FN_BOOL_UINT_UINT_UINT_BOOL,
 		     BT_BOOL, BT_UINT, BT_UINT, BT_UINT, BT_BOOL)
+DEF_FUNCTION_TYPE_4 (BT_FN_BOOL_INT_CONST_PTR_CONST_PTR_CONST_PTR,
+		     BT_BOOL, BT_INT, BT_CONST_PTR, BT_CONST_PTR, BT_CONST_PTR)
 
 DEF_FUNCTION_TYPE_5 (BT_FN_VOID_OMPFN_PTR_UINT_UINT_UINT,
 		     BT_VOID, BT_PTR_FN_VOID_PTR, BT_PTR, BT_UINT, BT_UINT,
diff --git a/gcc/omp-builtins.def b/gcc/omp-builtins.def
index 044d5d087b6..ecaace8de31 100644
--- a/gcc/omp-builtins.def
+++ b/gcc/omp-builtins.def
@@ -476,3 +476,6 @@  DEF_GOMP_BUILTIN (BUILT_IN_GOMP_WARNING, "GOMP_warning",
 		  BT_FN_VOID_CONST_PTR_SIZE, ATTR_NOTHROW_LEAF_LIST)
 DEF_GOMP_BUILTIN (BUILT_IN_GOMP_ERROR, "GOMP_error",
 		  BT_FN_VOID_CONST_PTR_SIZE, ATTR_COLD_NORETURN_NOTHROW_LEAF_LIST)
+DEF_GOMP_BUILTIN (BUILT_IN_GOMP_EVALUATE_TARGET_DEVICE, "GOMP_evaluate_target_device",
+		  BT_FN_BOOL_INT_CONST_PTR_CONST_PTR_CONST_PTR,
+		  ATTR_NOTHROW_LEAF_LIST)
diff --git a/gcc/omp-general.cc b/gcc/omp-general.cc
index 0b61335dba4..6fd142d1757 100644
--- a/gcc/omp-general.cc
+++ b/gcc/omp-general.cc
@@ -1280,7 +1280,7 @@  omp_context_name_list_prop (tree prop)
    it is correct or error_mark_node otherwise.  */
 
 tree
-omp_check_context_selector (location_t loc, tree ctx)
+omp_check_context_selector (location_t loc, tree ctx, bool metadirective_p)
 {
   bool tss_seen[OMP_TRAIT_SET_LAST], ts_seen[OMP_TRAIT_LAST];
 
@@ -1289,9 +1289,10 @@  omp_check_context_selector (location_t loc, tree ctx)
     {
       enum omp_tss_code tss_code = OMP_TSS_CODE (tss);
 
-      /* We can parse this, but not handle it yet.  */
-      if (tss_code == OMP_TRAIT_SET_TARGET_DEVICE)
-	sorry_at (loc, "%<target_device%> selector set is not supported yet");
+      /* FIXME: not implemented yet.  */
+      if (!metadirective_p && tss_code == OMP_TRAIT_SET_TARGET_DEVICE)
+       sorry_at (loc, "%<target_device%> selector set is not supported "
+		 "yet for %<declare variant%>");
 
       /* Each trait-set-selector-name can only be specified once.  */
       if (tss_seen[tss_code])
@@ -1443,14 +1444,29 @@  make_trait_property (tree name, tree value, tree chain)
   return tree_cons (name, value, chain);
 }
 
+/* Constructor for metadirective variants.  */
+tree
+make_omp_metadirective_variant (tree selector, tree directive, tree body)
+{
+  return build_tree_list (selector, build_tree_list (directive, body));
+}
+
+
 /* Return 1 if context selector matches the current OpenMP context, 0
    if it does not and -1 if it is unknown and need to be determined later.
    Some properties can be checked right away during parsing (this routine),
    others need to wait until the whole TU is parsed, others need to wait until
-   IPA, others until vectorization.  */
+   IPA, others until vectorization.
+
+   METADIRECTIVE_P is true if this is a metadirective context, and DELAY_P
+   is true if it's too early in compilation to determine whether some
+   properties match.
+
+   Dynamic properties (which are evaluated at run-time) should always
+   return 1.  */
 
 int
-omp_context_selector_matches (tree ctx)
+omp_context_selector_matches (tree ctx, bool metadirective_p, bool delay_p)
 {
   int ret = 1;
   for (tree tss = ctx; tss; tss = TREE_CHAIN (tss))
@@ -1523,6 +1539,11 @@  omp_context_selector_matches (tree ctx)
 	    ret = -1;
 	  continue;
 	}
+      else if (set == OMP_TRAIT_SET_TARGET_DEVICE)
+	/* The target_device set is dynamic, so treat it as always
+	   resolvable.  */
+	continue;
+
       for (tree ts = selectors; ts; ts = TREE_CHAIN (ts))
 	{
 	  enum omp_ts_code sel = OMP_TS_CODE (ts);
@@ -1592,6 +1613,9 @@  omp_context_selector_matches (tree ctx)
 		    const char *arch = omp_context_name_list_prop (p);
 		    if (arch == NULL)
 		      return 0;
+		    if (metadirective_p && delay_p)
+		      return -1;
+
 		    int r = 0;
 		    if (targetm.omp.device_kind_arch_isa != NULL)
 		      r = targetm.omp.device_kind_arch_isa (omp_device_arch,
@@ -1714,6 +1738,9 @@  omp_context_selector_matches (tree ctx)
 #endif
 			continue;
 		      }
+		    if (metadirective_p && delay_p)
+		      return -1;
+
 		    int r = 0;
 		    if (targetm.omp.device_kind_arch_isa != NULL)
 		      r = targetm.omp.device_kind_arch_isa (omp_device_kind,
@@ -1753,6 +1780,9 @@  omp_context_selector_matches (tree ctx)
 		    const char *isa = omp_context_name_list_prop (p);
 		    if (isa == NULL)
 		      return 0;
+		    if (metadirective_p && delay_p)
+		      return -1;
+
 		    int r = 0;
 		    if (targetm.omp.device_kind_arch_isa != NULL)
 		      r = targetm.omp.device_kind_arch_isa (omp_device_isa,
@@ -1804,6 +1834,12 @@  omp_context_selector_matches (tree ctx)
 		for (tree p = OMP_TS_PROPERTIES (ts); p; p = TREE_CHAIN (p))
 		  if (OMP_TP_NAME (p) == NULL_TREE)
 		    {
+		      /* OpenMP 5.1 allows non-constant conditions for
+			 metadirectives.  */
+		      if (metadirective_p
+			  && !tree_fits_shwi_p (OMP_TP_VALUE (p)))
+			break;
+
 		      if (integer_zerop (OMP_TP_VALUE (p)))
 			return 0;
 		      if (integer_nonzerop (OMP_TP_VALUE (p)))
@@ -2202,9 +2238,114 @@  omp_lookup_ts_code (enum omp_tss_code set, const char *s)
   return OMP_TRAIT_INVALID;
 }
 
-/* Needs to be a GC-friendly widest_int variant, but precision is
-   desirable to be the same on all targets.  */
-typedef generic_wide_int <fixed_wide_int_storage <1024> > score_wide_int;
+/* Helper for omp_dynamic_cond: encode the kind/arch/isa property-lists
+   into strings for GOMP_evaluate_target_device.  The property-list
+   strings are encoded similarly to those in omp_offload_kind_arch_isa,
+   above: each trait is passed as a string, with each property for the
+   string separated by '\0', and an extra '\0' at the end of the string.  */
+static tree
+omp_encode_kind_arch_isa_props (tree props)
+{
+  if (!props)
+    return NULL_TREE;
+  size_t length = 1;
+  for (tree p = props; p; p = TREE_CHAIN (p))
+    length += strlen (omp_context_name_list_prop (p)) + 1;
+  char *buffer = (char *) alloca (length);
+  size_t n = 0;
+  for (tree p = props; p; p = TREE_CHAIN (p))
+    {
+      const char *str = omp_context_name_list_prop (p);
+      strcpy (buffer + n, str);
+      n += strlen (str) + 1;
+    }
+  *(buffer + n) = '\0';
+  return build_string_literal (length, buffer);
+}
+
+/* Return a tree expression representing the dynamic part of the context
+   selector CTX.  */
+static tree
+omp_dynamic_cond (tree ctx)
+{
+  tree expr = NULL_TREE;
+
+  tree user = omp_get_context_selector (ctx, OMP_TRAIT_SET_USER,
+					OMP_TRAIT_USER_CONDITION);
+  if (user)
+    {
+      tree expr_list = OMP_TS_PROPERTIES (user);
+
+      /* The user condition is not dynamic if it is constant.  */
+      if (!tree_fits_shwi_p (OMP_TP_VALUE (expr_list)))
+	expr = OMP_TP_VALUE (expr_list);
+    }
+
+  tree target_device
+    = omp_get_context_selector_list (ctx, OMP_TRAIT_SET_TARGET_DEVICE);
+  if (target_device)
+    {
+      tree device_num = null_pointer_node;
+      tree kind = null_pointer_node;
+      tree arch = null_pointer_node;
+      tree isa = null_pointer_node;
+
+      tree device_num_sel
+	= omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
+				    OMP_TRAIT_DEVICE_NUM);
+      if (device_num_sel)
+	/* Generate
+	     devnum = (num == -1) ? GOMP_DEVICE_HOST_FALLBACK : num);
+	   to remap -1 for GOMP_* functions.  */
+	{
+	  tree num = OMP_TP_VALUE (OMP_TS_PROPERTIES (device_num_sel));
+	  location_t loc = EXPR_LOCATION (num);
+	  tree icv = build_int_cst (integer_type_node, -1);
+	  tree compare = fold_build2_loc (loc, EQ_EXPR, unsigned_type_node,
+					  num, icv);
+	  tree fallback = build_int_cst (integer_type_node,
+					 GOMP_DEVICE_HOST_FALLBACK);
+	  device_num = fold_build3_loc (loc, COND_EXPR, integer_type_node,
+					compare, fallback, save_expr (num));
+	}
+      else
+	device_num = build_int_cst (integer_type_node, GOMP_DEVICE_ICV);
+      tree kind_sel
+	= omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
+				    OMP_TRAIT_DEVICE_KIND);
+      /* "any" is equivalent to omitting this trait selector.  */
+      if (kind_sel
+	  && strcmp (omp_context_name_list_prop (OMP_TS_PROPERTIES (kind_sel)),
+		     "any"))
+	kind = omp_encode_kind_arch_isa_props (OMP_TS_PROPERTIES (kind_sel));
+
+
+      tree arch_sel
+	= omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
+				    OMP_TRAIT_DEVICE_ARCH);
+      if (arch_sel)
+	arch = omp_encode_kind_arch_isa_props (OMP_TS_PROPERTIES (arch_sel));
+
+      tree isa_sel
+	= omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
+				    OMP_TRAIT_DEVICE_ISA);
+      if (isa_sel)
+	isa = omp_encode_kind_arch_isa_props (OMP_TS_PROPERTIES (isa_sel));
+
+      /* Generate a call to GOMP_evaluate_target_device.  */
+      tree builtin_fn
+	= builtin_decl_explicit (BUILT_IN_GOMP_EVALUATE_TARGET_DEVICE);
+      tree call = build_call_expr (builtin_fn, 4, device_num, kind, arch, isa);
+
+      if (expr == NULL_TREE)
+	expr = call;
+      else
+	expr = fold_build2 (TRUTH_ANDIF_EXPR, boolean_type_node, expr, call);
+    }
+
+  return expr;
+}
+
 
 /* Compute *SCORE for context selector CTX.  Return true if the score
    would be different depending on whether it is a declare simd clone or
@@ -2216,12 +2357,21 @@  omp_context_compute_score (tree ctx, score_wide_int *score, bool declare_simd)
 {
   tree selectors
     = omp_get_context_selector_list (ctx, OMP_TRAIT_SET_CONSTRUCT);
-  bool has_kind = omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
-					    OMP_TRAIT_DEVICE_KIND);
-  bool has_arch = omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
-					    OMP_TRAIT_DEVICE_ARCH);
-  bool has_isa = omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
-					   OMP_TRAIT_DEVICE_ISA);
+  bool has_kind
+    = (omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
+				 OMP_TRAIT_DEVICE_KIND)
+       || omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
+				    OMP_TRAIT_DEVICE_KIND));
+  bool has_arch
+    = (omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
+				 OMP_TRAIT_DEVICE_ARCH)
+       || omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
+				    OMP_TRAIT_DEVICE_ARCH));
+  bool has_isa
+    = (omp_get_context_selector (ctx, OMP_TRAIT_SET_DEVICE,
+				 OMP_TRAIT_DEVICE_ISA)
+       || omp_get_context_selector (ctx, OMP_TRAIT_SET_TARGET_DEVICE,
+				    OMP_TRAIT_DEVICE_ISA));
   bool ret = false;
   *score = 1;
   for (tree tss = ctx; tss; tss = TREE_CHAIN (tss))
@@ -2406,7 +2556,7 @@  omp_resolve_late_declare_variant (tree alt)
 	  nmatches++;
 	  continue;
 	}
-      switch (omp_context_selector_matches (varentry1->ctx))
+      switch (omp_context_selector_matches (varentry1->ctx, false, true))
 	{
 	case 0:
           matches.safe_push (false);
@@ -2510,7 +2660,8 @@  omp_resolve_declare_variant (tree base)
 	 don't process it again.  */
       if (node && node->declare_variant_alt)
 	return base;
-      switch (omp_context_selector_matches (TREE_VALUE (TREE_VALUE (attr))))
+      switch (omp_context_selector_matches (TREE_VALUE (TREE_VALUE (attr)),
+					    false, true))
 	{
 	case 0:
 	  /* No match, ignore.  */
@@ -2875,6 +3026,185 @@  omp_lto_input_declare_variant_alt (lto_input_block *ib, cgraph_node *node,
 						 INSERT) = entryp;
 }
 
+/* Comparison function for sorting routines, to sort OpenMP metadirective
+   variants by decreasing score.  */
+
+static int
+sort_variant (const void * a, const void *b, void *)
+{
+  score_wide_int score1
+    = ((const struct omp_variant *) a)->score;
+  score_wide_int score2
+    = ((const struct omp_variant *) b)->score;
+
+  if (score1 > score2)
+    return -1;
+  else if (score1 < score2)
+    return 1;
+  else
+    return 0;
+}
+
+/* Return a vector of dynamic replacement candidates for the directive
+   candidates in ALL_VARIANTS.  Return an empty vector if the metadirective
+   cannot be resolved.  */
+
+static vec<struct omp_variant>
+omp_get_dynamic_candidates (vec <struct omp_variant> &all_variants,
+			    bool delay_p)
+{
+  auto_vec <struct omp_variant> variants;
+  struct omp_variant default_variant;
+  bool default_found = false;
+
+  for (unsigned int i = 0; i < all_variants.length (); i++)
+    {
+      struct omp_variant variant = all_variants[i];
+
+      if (variant.selector == NULL_TREE)
+	{
+	  gcc_assert (!default_found);
+	  default_found = true;
+	  default_variant = variant;
+	  default_variant.score = 0;
+	  default_variant.resolvable_p = true;
+	  default_variant.dynamic_selector = NULL_TREE;
+	  if (dump_file)
+	    fprintf (dump_file,
+		     "Considering default selector as candidate\n");
+	  continue;
+	}
+
+      variant.resolvable_p = true;
+
+      if (dump_file)
+	{
+	  fprintf (dump_file, "Considering selector ");
+	  print_omp_context_selector (dump_file, variant.selector, TDF_NONE);
+	  fprintf (dump_file, " as candidate - ");
+	}
+
+      switch (omp_context_selector_matches (variant.selector, true, delay_p))
+	{
+	case -1:
+	  variant.resolvable_p = false;
+	  if (dump_file)
+	    fprintf (dump_file, "unresolvable");
+	  /* FALLTHRU */
+	case 1:
+	  /* TODO: Handle SIMD score?.  */
+	  omp_context_compute_score (variant.selector, &variant.score, false);
+	  variant.dynamic_selector = omp_dynamic_cond (variant.selector);
+	  variants.safe_push (variant);
+	  if (dump_file && variant.resolvable_p)
+	    {
+	      if (variant.dynamic_selector)
+		fprintf (dump_file, "matched, dynamic");
+	      else
+		fprintf (dump_file, "matched, non-dynamic");
+	    }
+	  break;
+	case 0:
+	  if (dump_file)
+	    fprintf (dump_file, "no match");
+	  break;
+	}
+
+      if (dump_file)
+	fprintf (dump_file, "\n");
+    }
+
+  /* There must be one default variant.  */
+  gcc_assert (default_found);
+
+  /* A context selector that is a strict subset of another context selector
+     has a score of zero.  */
+  for (unsigned int i = 0; i < variants.length (); i++)
+    for (unsigned int j = i + 1; j < variants.length (); j++)
+      {
+	int r = omp_context_selector_compare (variants[i].selector,
+					      variants[j].selector);
+	if (r == -1)
+	  {
+	    /* variant1 is a strict subset of variant2.  */
+	    variants[i].score = 0;
+	    break;
+	  }
+	else if (r == 1)
+	  /* variant2 is a strict subset of variant1.  */
+	  variants[j].score = 0;
+      }
+
+  /* Sort the variants by decreasing score, preserving the original order
+     in case of a tie.  */
+  variants.stablesort (sort_variant, NULL);
+
+  /* Add the default as a final choice.  */
+  variants.safe_push (default_variant);
+
+  /* Build the dynamic candidate list.  */
+  for (unsigned i = 0; i < variants.length (); i++)
+    {
+      /* If one of the candidates is unresolvable, give up for now.  */
+      if (!variants[i].resolvable_p)
+	{
+	  variants.truncate (0);
+	  break;
+	}
+
+      if (dump_file)
+	{
+	  fprintf (dump_file, "Adding directive variant with ");
+
+	  if (variants[i].selector)
+	    {
+	      fprintf (dump_file, "selector ");
+	      print_omp_context_selector (dump_file, variants[i].selector,
+					  TDF_NONE);
+	    }
+	  else
+	    fprintf (dump_file, "default selector");
+
+	  fprintf (dump_file, " as candidate.\n");
+	}
+
+      /* The last of the candidates is ended by a static selector.  */
+      if (!variants[i].dynamic_selector)
+	{
+	  variants.truncate (i + 1);
+	  break;
+	}
+    }
+
+  return variants.copy ();
+}
+
+/* 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_variant>
+omp_early_resolve_metadirective (tree metadirective)
+{
+  auto_vec <struct omp_variant> candidates;
+  tree variant = OMP_METADIRECTIVE_VARIANTS (metadirective);
+
+  gcc_assert (variant);
+  while (variant)
+    {
+      struct omp_variant candidate;
+
+      candidate.selector = OMP_METADIRECTIVE_VARIANT_SELECTOR (variant);
+      candidate.alternative = OMP_METADIRECTIVE_VARIANT_DIRECTIVE (variant);
+      candidate.body = OMP_METADIRECTIVE_VARIANT_BODY (variant);
+
+      candidates.safe_push (candidate);
+      variant = TREE_CHAIN (variant);
+    }
+
+  return omp_get_dynamic_candidates (candidates, true);
+}
+
 /* 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 37f0548befd..8fe25b3108f 100644
--- a/gcc/omp-general.h
+++ b/gcc/omp-general.h
@@ -91,6 +91,22 @@  struct omp_for_data
   tree adjn1;
 };
 
+/* Needs to be a GC-friendly widest_int variant, but precision is
+   desirable to be the same on all targets.  */
+typedef generic_wide_int <fixed_wide_int_storage <1024> > score_wide_int;
+
+/* A structure describing a variant in a metadirective.  */
+
+struct GTY(()) omp_variant
+{
+  score_wide_int score;
+  tree selector;
+  tree alternative;
+  tree body;
+  tree dynamic_selector;
+  bool resolvable_p : 1;
+};
+
 #define OACC_FN_ATTRIB "oacc function"
 
 /* Accessors for OMP context selectors, used by variant directives.
@@ -150,6 +166,15 @@  extern tree make_trait_set_selector (enum omp_tss_code, tree, tree);
 extern tree make_trait_selector (enum omp_ts_code, tree, tree, tree);
 extern tree make_trait_property (tree, tree, tree);
 
+/* Accessors and constructor for metadirective variants.  */
+#define OMP_METADIRECTIVE_VARIANT_SELECTOR(v) \
+  TREE_PURPOSE (v)
+#define OMP_METADIRECTIVE_VARIANT_DIRECTIVE(v) \
+  TREE_PURPOSE (TREE_VALUE (v))
+#define OMP_METADIRECTIVE_VARIANT_BODY(v) \
+  TREE_VALUE (TREE_VALUE (v))
+extern tree make_omp_metadirective_variant (tree, tree, tree);
+
 extern tree omp_find_clause (tree clauses, enum omp_clause_code kind);
 extern bool omp_is_allocatable_or_ptr (tree decl);
 extern tree omp_check_optional_argument (tree decl, bool for_present_check);
@@ -166,15 +191,17 @@  extern poly_uint64 omp_max_vf (void);
 extern int omp_max_simt_vf (void);
 extern const char *omp_context_name_list_prop (tree);
 extern void omp_construct_traits_to_codes (tree, int, enum tree_code *);
-extern tree omp_check_context_selector (location_t loc, tree ctx);
+extern tree omp_check_context_selector (location_t loc, tree ctx,
+					bool metadirective_p);
 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, bool);
 extern int omp_context_selector_set_compare (enum omp_tss_code, tree, tree);
 extern tree omp_get_context_selector (tree, enum omp_tss_code,
 				      enum omp_ts_code);
 extern tree omp_get_context_selector_list (tree, enum omp_tss_code);
 extern tree omp_resolve_declare_variant (tree);
+extern vec<struct omp_variant> omp_early_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-pretty-print.cc b/gcc/tree-pretty-print.cc
index 4bb946bb0e8..59db597c10d 100644
--- a/gcc/tree-pretty-print.cc
+++ b/gcc/tree-pretty-print.cc
@@ -1522,7 +1522,7 @@  dump_omp_clauses (pretty_printer *pp, tree clause, int spc, dump_flags_t flags,
 }
 
 /* Dump an OpenMP context selector CTX to PP.  */
-static void
+void
 dump_omp_context_selector (pretty_printer *pp, tree ctx, int spc,
 			   dump_flags_t flags)
 {
@@ -4048,6 +4048,40 @@  dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
       is_expr = false;
       break;
 
+    case OMP_METADIRECTIVE:
+      {
+	pp_string (pp, "#pragma omp metadirective");
+	newline_and_indent (pp, spc + 2);
+	pp_left_brace (pp);
+
+	tree variant = OMP_METADIRECTIVE_VARIANTS (node);
+	while (variant != NULL_TREE)
+	  {
+	    tree selector = OMP_METADIRECTIVE_VARIANT_SELECTOR (variant);
+	    tree directive = OMP_METADIRECTIVE_VARIANT_DIRECTIVE (variant);
+	    tree body = OMP_METADIRECTIVE_VARIANT_BODY (variant);
+
+	    newline_and_indent (pp, spc + 4);
+	    if (selector == NULL_TREE)
+	      pp_string (pp, "otherwise:");
+	    else
+	      {
+		pp_string (pp, "when (");
+		dump_omp_context_selector (pp, selector, spc + 4, flags);
+		pp_string (pp, "):");
+	      }
+	    newline_and_indent (pp, spc + 6);
+
+	    dump_generic_node (pp, directive, spc + 6, flags, false);
+	    newline_and_indent (pp, spc + 6);
+	    dump_generic_node (pp, body, spc + 6, flags, false);
+	    variant = TREE_CHAIN (variant);
+	  }
+	newline_and_indent (pp, spc + 2);
+	pp_right_brace (pp);
+      }
+      break;
+
     case TRANSACTION_EXPR:
       if (TRANSACTION_EXPR_OUTER (node))
 	pp_string (pp, "__transaction_atomic [[outer]]");
diff --git a/gcc/tree-pretty-print.h b/gcc/tree-pretty-print.h
index c5089f82cf6..d9d4f82909f 100644
--- a/gcc/tree-pretty-print.h
+++ b/gcc/tree-pretty-print.h
@@ -45,6 +45,8 @@  extern void dump_omp_atomic_memory_order (pretty_printer *,
 					  enum omp_memory_order);
 extern void dump_omp_loop_non_rect_expr (pretty_printer *, tree, int,
 					 dump_flags_t);
+extern void dump_omp_context_selector (pretty_printer *, tree, int,
+				       dump_flags_t);
 extern void print_omp_context_selector (FILE *, tree, dump_flags_t);
 extern int dump_generic_node (pretty_printer *, tree, int, dump_flags_t, bool);
 extern void print_declaration (pretty_printer *, tree, int, dump_flags_t);
diff --git a/gcc/tree.def b/gcc/tree.def
index 85ab182c6f5..155a508f0d3 100644
--- a/gcc/tree.def
+++ b/gcc/tree.def
@@ -1348,6 +1348,12 @@  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 [variant1 ... variantN]
+   Operand 0: OMP_METADIRECTIVE_VARIANTS: List of selectors and directive
+   variants.  Use the interface in omp-general.h to construct variants
+   and access their fields.  */
+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 28e8e71b036..dfd738353b8 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -1600,6 +1600,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_VARIANTS(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)