diff mbox series

[v3,2/7] OpenMP: middle-end support for dispatch + adjust_args

Message ID 20240807115012.3632947-3-parras@baylibre.com
State New
Headers show
Series OpenMP: dispatch + adjust_args support | expand

Commit Message

Paul-Antoine Arras Aug. 7, 2024, 11:50 a.m. UTC
This patch adds middle-end support for the `dispatch` construct and the
`adjust_args` clause. The heavy lifting is done in `gimplify_omp_dispatch` and
`gimplify_call_expr` respectively. For `adjust_args`, this mostly consists in
emitting a call to `gomp_get_mapped_ptr` for the adequate device.

For dispatch, the following steps are performed:

* Handle the device clause, if any: set the default-device ICV at the top of the
dispatch region and restore its previous value at the end.

* Handle novariants and nocontext clauses, if any. Evaluate compile-time
constants and select a variant, if possible. Otherwise, emit code to handle all
possible cases at run time.

* If depend clauses are present, add a taskwait construct before the dispatch
region and move them there.

gcc/ChangeLog:

	* gimple-low.cc (lower_stmt): Handle GIMPLE_OMP_DISPATCH.
	* gimple-pretty-print.cc (dump_gimple_omp_dispatch): New function.
	(pp_gimple_stmt_1): Handle GIMPLE_OMP_DISPATCH.
	* gimple-walk.cc (walk_gimple_stmt): Likewise.
	* gimple.cc (gimple_build_omp_dispatch): New function.
	(gimple_copy): Handle GIMPLE_OMP_DISPATCH.
	* gimple.def (GIMPLE_OMP_DISPATCH): Define.
	* gimple.h (gimple_build_omp_dispatch): Declare.
	(gimple_has_substatements): Handle GIMPLE_OMP_DISPATCH.
	(gimple_omp_dispatch_clauses): New function.
	(gimple_omp_dispatch_clauses_ptr): Likewise.
	(gimple_omp_dispatch_set_clauses): Likewise.
	(gimple_return_set_retval): Handle GIMPLE_OMP_DISPATCH.
	* gimplify.cc (enum omp_region_type): Add ORT_DISPATCH.
	(gimplify_call_expr): Handle need_device_ptr arguments.
	(is_gimple_stmt): Handle OMP_DISPATCH.
	(gimplify_scan_omp_clauses): Handle OMP_CLAUSE_DEVICE in a dispatch
	construct. Handle OMP_CLAUSE_NOVARIANTS and OMP_CLAUSE_NOCONTEXT.
	(gimplify_adjust_omp_clauses): Handle OMP_CLAUSE_NOVARIANTS and
	OMP_CLAUSE_NOCONTEXT.
	(omp_construct_selector_matches): Handle OMP_DISPATCH with nocontext
	clause.
	(omp_has_novariants): New function.
	(omp_has_nocontext): Likewise.
	(gimplify_omp_dispatch): Likewise.
	(gimplify_expr): Handle OMP_DISPATCH.
	* gimplify.h (omp_has_novariants): Declare.
	(omp_has_nocontext): Declare.
	* omp-builtins.def (BUILT_IN_OMP_GET_MAPPED_PTR): Define.
	(BUILT_IN_OMP_GET_DEFAULT_DEVICE): Define.
	(BUILT_IN_OMP_SET_DEFAULT_DEVICE): Define.
	* omp-expand.cc (expand_omp_dispatch): New function.
	(expand_omp): Handle GIMPLE_OMP_DISPATCH.
	(omp_make_gimple_edges): Likewise.
	* omp-general.cc (omp_construct_traits_to_codes): Add OMP_DISPATCH.
	(struct omp_ts_info): Add dispatch.
	(omp_context_selector_matches): Handle OMP_TRAIT_SET_NEED_DEVICE_PTR.
	(omp_resolve_declare_variant): Handle novariants. Adjust
	DECL_ASSEMBLER_NAME.
---
 gcc/gimple-low.cc          |   1 +
 gcc/gimple-pretty-print.cc |  33 +++
 gcc/gimple-walk.cc         |   1 +
 gcc/gimple.cc              |  20 ++
 gcc/gimple.def             |   5 +
 gcc/gimple.h               |  33 ++-
 gcc/gimplify.cc            | 421 ++++++++++++++++++++++++++++++++++++-
 gcc/gimplify.h             |   2 +
 gcc/omp-builtins.def       |   6 +
 gcc/omp-expand.cc          |  18 ++
 gcc/omp-general.cc         |  14 +-
 gcc/omp-low.cc             |  35 +++
 gcc/tree-inline.cc         |   7 +
 13 files changed, 585 insertions(+), 11 deletions(-)

Comments

Tobias Burnus Aug. 9, 2024, 11:51 a.m. UTC | #1
Paul-Antoine Arras wrote:
> This patch adds middle-end support for the `dispatch` construct and the
> `adjust_args` clause. The heavy lifting is done in `gimplify_omp_dispatch` and
> `gimplify_call_expr` respectively. For `adjust_args`, this mostly consists in
> emitting a call to `gomp_get_mapped_ptr` for the adequate device.

...

>         * gimplify.h (omp_has_novariants): Declare.
>         (omp_has_nocontext): Declare.

As those two functions are only used in gimplify.cc,
please make them 'static' and remove them from gimplify.h.

* * *

I have a testcase which is rejected with the bogus:

    17 |   !$omp end dispatch
       |         1
Error: Unclassifiable OpenMP directive at (1)

That's at least valid in OpenMP 6.0 previews as those have:
   "For a dispatch directive, the paired 'end' directive is optional."

In 5.2, it is implied via "3.1 Directive Format" and that 'dispatch'
has "Association: block (function dispatch structured block)"

Note: That 'nowait' is an 'end-clause' and may also appear as
'!$omp end dispatch nowait'.
(but either at 'dispatch' or at 'end dispatch'; the current code should 
be able to handle this.)

* * *

But the main reason that I created the testcase was a comment which 
looked wrong in gimplify_omp_dispatch – and indeed, the attached
testcase gives an ICE:

internal compiler error: in gimplify_omp_dispatch, at gimplify.cc:18064

See attached Fortran testcase + comment below at gimplify_omp_dispatch.

* * *

> --- a/gcc/gimplify.cc
> +++ b/gcc/gimplify.cc

...

> @@ -4052,6 +4053,7 @@ gimplify_call_expr (tree *expr_p, gimple_seq *pre_p, bool want_value)
>     /* Gimplify the function arguments.  */
>     if (nargs > 0)
>       {
> +    tree device_num = NULL_TREE;

Indentation issue: Indented by 4 instead of 6 spaces.

> @@ -4062,8 +4064,111 @@ gimplify_call_expr (tree *expr_p, gimple_seq *pre_p, bool want_value)

...

> +	      if (flag_openmp && EXPR_P (CALL_EXPR_FN (*expr_p))
> +		  && DECL_P (TREE_OPERAND (CALL_EXPR_FN (*expr_p), 0))
> +		  && (adjust_args_list = lookup_attribute (
> +			"omp declare variant variant adjust_args",
> +			DECL_ATTRIBUTES (
> +			  TREE_OPERAND (CALL_EXPR_FN (*expr_p), 0))))
> +		       != NULL_TREE)
> +		{
...
> +		      if (gimplify_omp_ctxp != NULL
> +			  && gimplify_omp_ctxp->code == OMP_DISPATCH)
> +			{

The OpenMP spec only supports append_args/adjust_args "when a specified
function variant is selected for replacement in the context of a
function *dispatch* structured block.

Thus, IMHO, you can merge the two if conditions.


> +			  for (tree c = gimplify_omp_ctxp->clauses; c;
> +			       c = TREE_CHAIN (c))
> +			    {
> +			      if (OMP_CLAUSE_CODE (c)
> +				  == OMP_CLAUSE_IS_DEVICE_PTR)
> +				{
> +				  tree decl1 = DECL_NAME (OMP_CLAUSE_DECL (c));
> +				  tree decl2
> +				    = tree_strip_nop_conversions (*arg_p);
> +				  if (TREE_CODE (decl2) == ADDR_EXPR)
> +				    decl2 = TREE_OPERAND (decl2, 0);
> +				  gcc_assert (TREE_CODE (decl2) == VAR_DECL
> +					      || TREE_CODE (decl2)
> +						   == PARM_DECL);
> +				  decl2 = DECL_NAME (decl2);
> +				  if (decl1 == decl2)
> +				    {
> +				      is_device_ptr = true;
> +				      break;
> +				    }
> +				}
> +			      else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_DEVICE)
> +				device_num = OMP_CLAUSE_OPERAND (c, 0);
> +			    }

Assume(*) you have:

#pragma omp dispatch is_device_ptr(p) device_num(6)
   foo(p);

If I read the code correctly, this will use the default device as the 
"break" will prevent finding the device clause.

(* Or other way round, if new clauses are internally added at the 
beginning of the list.)


> +			  if (build_int_cst (integer_type_node, i)
> +			      == TREE_VALUE (arg))

I think

if (wi::eq_p (i, tree_strip_any_location_wrapper (
	TREE_VALUE (arg)))

is better and avoids creating new tree values that might en up being 
unused. (I am assuming that TREE_CODE(TREE_VALUE (arg)) == INTEGER_CST, 
if not, some additional checks might be needed.)

(The tree_strip_any_location_wrapper call, I have taken from
integer_nonzerop (etc.) and it might not be needed.)

> +		      if (need_device_ptr && !is_device_ptr)
> +			{
> +			  if (device_num == NULL_TREE)
> +			    {
> +			      // device_num = omp_get_default_device();

I'd remove the ';' to make it more looking like a comment (and not 
comment-out line) and add a space before the '(' for coding style.

> +			  // mapped_arg = omp_get_mapped_ptr(arg, device_num);

Likewise.

* * *

> +gimplify_omp_dispatch (tree *expr_p, gimple_seq *pre_p)
> +{

...

> +  // If the novariants and nocontext clauses are not compile-time constants,
> +  // we need to generate code for all possible cases:
> +  //   if (novariants) // implies nocontext
> +  //       base()
> +  //   else if (nocontext)
> +  //       variant1()
> +  //   else
> +  //       variant2()
> +  tree dispatch_body = OMP_DISPATCH_BODY (expr);
> +  if (TREE_CODE (dispatch_body) == BIND_EXPR)
> +    dispatch_body = BIND_EXPR_BODY (dispatch_body);
> +  if (TREE_CODE (dispatch_body) == STATEMENT_LIST)
> +    {
> +      // Fortran FE may insert some pre-call code, for instance when an
> +      // array is passed as argument. Skip to the actual call.
> +      dispatch_body = expr_last (dispatch_body);
> +    }

One case where Fortran adds additional code is when an array that is or 
might be noncontiguous is passed as actual argument to a dummy argument 
that requires a contiguous array.

If the argument is not 'intent(in)', it also needs to copy afterwards 
the values back to the non-contiguous array.

And regarding pre-call code, that's not different to C for code like:

f2 (p, arr, pp()[dd()]);

Except that in Fortran also post calls can exist.

In principle, I would just search for the decl attributes, except that 
'pp' and 'dd' can be - in principle - also variant procedures.

* * *

I have now created a testcase for this - see attachment.

It seems as if 'novariants(1)' applies to all variant functions but 
OpenMP (quoting TR13, addmittedly, I haven't checked 5.1 nor 5.2):

"If do-not-use-variant evaluates to true, no function variant is 
selected for the target-call of the dispatch region associated with the 
novariants clause even if one would be selected normally."

This explicitly talks about "target-call' and that is in C/C++:

[lvalue-expression =] target-call ( [expression-list] );

and it does not talk about the expression list.

For 'nocontext', it is clear that it applies to everything as:
"If do-not-update-context evaluates to true, the construct on which the 
nocontext clause appears is not added to the construct trait set of the 
OpenMP context."

And just to avoid any doubt: append_args/adjust_args only apply to the 
arguments of 'target-call', everything else doesn't make sense, albeit 
this does not seem to be spelled out explicitly.

* * *

> +
> +  gimplify_adjust_omp_clauses (pre_p, bind, &OMP_DISPATCH_CLAUSES (expr),
> +			       OMP_DISPATCH);

My impression is that this is no longer needed since no 'task' are 
generated. This code seems walk the clauses and the body and then (now) 
pointless 'SHARED' clauses for all variables it finds.

Or is there is hidden use I missed? If not, I think it can be removed.
Besides, it comes a bit late otherwise as not much is done afterwards.

* * *

> --- a/gcc/omp-expand.cc
> +++ b/gcc/omp-expand.cc
> @@ -8636,6 +8636,19 @@ expand_omp_single (struct omp_region *region)
>     single_succ_edge (exit_bb)->flags = EDGE_FALLTHRU;
>   }
>   
> +/* Expand code for an OpenMP dispatch directive...  */
> +
> +static void
> +expand_omp_dispatch (struct omp_region *region)
> +{
> +  basic_block entry_bb = region->entry;
> +  gimple_stmt_iterator si = gsi_last_nondebug_bb (entry_bb);
> +  enum gimple_code code = gimple_code (gsi_stmt (si));
> +  gcc_assert (code == GIMPLE_OMP_DISPATCH);
> +  gsi_remove (&si, true);
> +  single_succ_edge (entry_bb)->flags = EDGE_FALLTHRU;
> +}

I wonder whether this is really needed. It looks as if you could
already get rit of GIMPLE_OMP_DISPATCH inside lower_omp_1, similar
to other functions. For instance, GIMPLE_OMP_STRUCTURED_BLOCK uses:

  /* We have already done error checking at this point, so these nodes
     can be completely removed and replaced with their body.  */
...
  gsi_replace_with_seq (gsi_p, gimple_omp_body (stmt), true);

Such that by the tie of omp-expand.cc, no work is left to do.

Tobias
Paul-Antoine Arras Aug. 20, 2024, 3:56 p.m. UTC | #2
Hi Tobias,

Thanks for the review. Please find attached an updated patch following 
your comments.
Since further adjustments to other patches of the series were required, 
I'll post them shortly.
See also my replies below.

On 09/08/2024 13:51, Tobias Burnus wrote:
> Paul-Antoine Arras wrote:
>> This patch adds middle-end support for the `dispatch` construct and the
>> `adjust_args` clause. The heavy lifting is done in 
>> `gimplify_omp_dispatch` and
>> `gimplify_call_expr` respectively. For `adjust_args`, this mostly 
>> consists in
>> emitting a call to `gomp_get_mapped_ptr` for the adequate device.
> 
> ...
> 
>>         * gimplify.h (omp_has_novariants): Declare.
>>         (omp_has_nocontext): Declare.
> 
> As those two functions are only used in gimplify.cc,
> please make them 'static' and remove them from gimplify.h.

Done for omp_has_nocontext. However, omp_has_novariants is called by 
omp_resolve_declare_variant in omp-general.cc.

> I have a testcase which is rejected with the bogus:
> 
>     17 |   !$omp end dispatch
>        |         1
> Error: Unclassifiable OpenMP directive at (1)
> 
> That's at least valid in OpenMP 6.0 previews as those have:
>    "For a dispatch directive, the paired 'end' directive is optional."
> 
> In 5.2, it is implied via "3.1 Directive Format" and that 'dispatch'
> has "Association: block (function dispatch structured block)"
> 
> Note: That 'nowait' is an 'end-clause' and may also appear as
> '!$omp end dispatch nowait'.
> (but either at 'dispatch' or at 'end dispatch'; the current code should 
> be able to handle this.)

Added support for optional end directive.

> But the main reason that I created the testcase was a comment which 
> looked wrong in gimplify_omp_dispatch – and indeed, the attached
> testcase gives an ICE:
> 
> internal compiler error: in gimplify_omp_dispatch, at gimplify.cc:18064
> 
> See attached Fortran testcase + comment below at gimplify_omp_dispatch.

Added testcase and fixed ICE.

>> --- a/gcc/gimplify.cc
>> +++ b/gcc/gimplify.cc
> 
> ...
> 
>> @@ -4052,6 +4053,7 @@ gimplify_call_expr (tree *expr_p, gimple_seq 
>> *pre_p, bool want_value)
>>     /* Gimplify the function arguments.  */
>>     if (nargs > 0)
>>       {
>> +    tree device_num = NULL_TREE;
> 
> Indentation issue: Indented by 4 instead of 6 spaces.

Fixed.

>> @@ -4062,8 +4064,111 @@ gimplify_call_expr (tree *expr_p, gimple_seq 
>> *pre_p, bool want_value)
> 
> ...
> 
>> +          if (flag_openmp && EXPR_P (CALL_EXPR_FN (*expr_p))
>> +          && DECL_P (TREE_OPERAND (CALL_EXPR_FN (*expr_p), 0))
>> +          && (adjust_args_list = lookup_attribute (
>> +            "omp declare variant variant adjust_args",
>> +            DECL_ATTRIBUTES (
>> +              TREE_OPERAND (CALL_EXPR_FN (*expr_p), 0))))
>> +               != NULL_TREE)
>> +        {
> ...
>> +              if (gimplify_omp_ctxp != NULL
>> +              && gimplify_omp_ctxp->code == OMP_DISPATCH)
>> +            {
> 
> The OpenMP spec only supports append_args/adjust_args "when a specified
> function variant is selected for replacement in the context of a
> function *dispatch* structured block.
> 
> Thus, IMHO, you can merge the two if conditions.

Done.

>> +              for (tree c = gimplify_omp_ctxp->clauses; c;
>> +                   c = TREE_CHAIN (c))
>> +                {
>> +                  if (OMP_CLAUSE_CODE (c)
>> +                  == OMP_CLAUSE_IS_DEVICE_PTR)
>> +                {
>> +                  tree decl1 = DECL_NAME (OMP_CLAUSE_DECL (c));
>> +                  tree decl2
>> +                    = tree_strip_nop_conversions (*arg_p);
>> +                  if (TREE_CODE (decl2) == ADDR_EXPR)
>> +                    decl2 = TREE_OPERAND (decl2, 0);
>> +                  gcc_assert (TREE_CODE (decl2) == VAR_DECL
>> +                          || TREE_CODE (decl2)
>> +                           == PARM_DECL);
>> +                  decl2 = DECL_NAME (decl2);
>> +                  if (decl1 == decl2)
>> +                    {
>> +                      is_device_ptr = true;
>> +                      break;
>> +                    }
>> +                }
>> +                  else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_DEVICE)
>> +                device_num = OMP_CLAUSE_OPERAND (c, 0);
>> +                }
> 
> Assume(*) you have:
> 
> #pragma omp dispatch is_device_ptr(p) device_num(6)
>    foo(p);
> 
> If I read the code correctly, this will use the default device as the 
> "break" will prevent finding the device clause.
> 
> (* Or other way round, if new clauses are internally added at the 
> beginning of the list.)

Fixed.

>> +              if (build_int_cst (integer_type_node, i)
>> +                  == TREE_VALUE (arg))
> 
> I think
> 
> if (wi::eq_p (i, tree_strip_any_location_wrapper (
>      TREE_VALUE (arg)))
> 
> is better and avoids creating new tree values that might en up being 
> unused. (I am assuming that TREE_CODE(TREE_VALUE (arg)) == INTEGER_CST, 
> if not, some additional checks might be needed.)
> 
> (The tree_strip_any_location_wrapper call, I have taken from
> integer_nonzerop (etc.) and it might not be needed.)

Replaced comparison with wi::eq_p. tree_strip_any_location_wrapper was 
not necessary but to_wide was.

>> +              if (need_device_ptr && !is_device_ptr)
>> +            {
>> +              if (device_num == NULL_TREE)
>> +                {
>> +                  // device_num = omp_get_default_device();
> 
> I'd remove the ';' to make it more looking like a comment (and not 
> comment-out line) and add a space before the '(' for coding style.
> 
>> +              // mapped_arg = omp_get_mapped_ptr(arg, device_num);
> 
> Likewise.

Done.

>> +gimplify_omp_dispatch (tree *expr_p, gimple_seq *pre_p)
>> +{
> 
> ...
> 
>> +  // If the novariants and nocontext clauses are not compile-time 
>> constants,
>> +  // we need to generate code for all possible cases:
>> +  //   if (novariants) // implies nocontext
>> +  //       base()
>> +  //   else if (nocontext)
>> +  //       variant1()
>> +  //   else
>> +  //       variant2()
>> +  tree dispatch_body = OMP_DISPATCH_BODY (expr);
>> +  if (TREE_CODE (dispatch_body) == BIND_EXPR)
>> +    dispatch_body = BIND_EXPR_BODY (dispatch_body);
>> +  if (TREE_CODE (dispatch_body) == STATEMENT_LIST)
>> +    {
>> +      // Fortran FE may insert some pre-call code, for instance when an
>> +      // array is passed as argument. Skip to the actual call.
>> +      dispatch_body = expr_last (dispatch_body);
>> +    }
> 
> One case where Fortran adds additional code is when an array that is or 
> might be noncontiguous is passed as actual argument to a dummy argument 
> that requires a contiguous array.
> 
> If the argument is not 'intent(in)', it also needs to copy afterwards 
> the values back to the non-contiguous array.
> 
> And regarding pre-call code, that's not different to C for code like:
> 
> f2 (p, arr, pp()[dd()]);
> 
> Except that in Fortran also post calls can exist.
> 
> In principle, I would just search for the decl attributes, except that 
> 'pp' and 'dd' can be - in principle - also variant procedures.

Fixed gimplify_omp_dispatch to correctly find the initial CALL_EXPR even 
when it is buried in the middle of a STATEMENT_LIST. This is not very 
elegant IMO though, so feel free to suggest a better way to handle it.

> I have now created a testcase for this - see attachment.
> 
> It seems as if 'novariants(1)' applies to all variant functions but 
> OpenMP (quoting TR13, addmittedly, I haven't checked 5.1 nor 5.2):
> 
> "If do-not-use-variant evaluates to true, no function variant is 
> selected for the target-call of the dispatch region associated with the 
> novariants clause even if one would be selected normally."
> 
> This explicitly talks about "target-call' and that is in C/C++:
> 
> [lvalue-expression =] target-call ( [expression-list] );
> 
> and it does not talk about the expression list.
> 
> For 'nocontext', it is clear that it applies to everything as:
> "If do-not-update-context evaluates to true, the construct on which the 
> nocontext clause appears is not added to the construct trait set of the 
> OpenMP context."
> 
> And just to avoid any doubt: append_args/adjust_args only apply to the 
> arguments of 'target-call', everything else doesn't make sense, albeit 
> this does not seem to be spelled out explicitly.

Added testcase and fixed gimplify_call_expr to properly implement 
nocontext and novariants semantics. See also in_call_args.

>> +
>> +  gimplify_adjust_omp_clauses (pre_p, bind, &OMP_DISPATCH_CLAUSES 
>> (expr),
>> +                   OMP_DISPATCH);
> 
> My impression is that this is no longer needed since no 'task' are 
> generated. This code seems walk the clauses and the body and then (now) 
> pointless 'SHARED' clauses for all variables it finds.
> 
> Or is there is hidden use I missed? If not, I think it can be removed.
> Besides, it comes a bit late otherwise as not much is done afterwards.

Correct, the call to gimplify_adjust_omp_clauses is now mostly useless, 
except for tearing down the current context, which is now done directly 
in gimplify_omp_dispatch.

>> --- a/gcc/omp-expand.cc
>> +++ b/gcc/omp-expand.cc
>> @@ -8636,6 +8636,19 @@ expand_omp_single (struct omp_region *region)
>>     single_succ_edge (exit_bb)->flags = EDGE_FALLTHRU;
>>   }
>> +/* Expand code for an OpenMP dispatch directive...  */
>> +
>> +static void
>> +expand_omp_dispatch (struct omp_region *region)
>> +{
>> +  basic_block entry_bb = region->entry;
>> +  gimple_stmt_iterator si = gsi_last_nondebug_bb (entry_bb);
>> +  enum gimple_code code = gimple_code (gsi_stmt (si));
>> +  gcc_assert (code == GIMPLE_OMP_DISPATCH);
>> +  gsi_remove (&si, true);
>> +  single_succ_edge (entry_bb)->flags = EDGE_FALLTHRU;
>> +}
> 
> I wonder whether this is really needed. It looks as if you could
> already get rit of GIMPLE_OMP_DISPATCH inside lower_omp_1, similar
> to other functions. For instance, GIMPLE_OMP_STRUCTURED_BLOCK uses:
> 
>   /* We have already done error checking at this point, so these nodes
>      can be completely removed and replaced with their body.  */
> ...
>   gsi_replace_with_seq (gsi_p, gimple_omp_body (stmt), true);
> 
> Such that by the tie of omp-expand.cc, no work is left to do.

Removed all modifications made to omp-expand.cc.
diff mbox series

Patch

diff --git a/gcc/gimple-low.cc b/gcc/gimple-low.cc
index e0371988705..712a1ebf776 100644
--- a/gcc/gimple-low.cc
+++ b/gcc/gimple-low.cc
@@ -746,6 +746,7 @@  lower_stmt (gimple_stmt_iterator *gsi, struct lower_data *data)
     case GIMPLE_EH_MUST_NOT_THROW:
     case GIMPLE_OMP_FOR:
     case GIMPLE_OMP_SCOPE:
+    case GIMPLE_OMP_DISPATCH:
     case GIMPLE_OMP_SECTIONS:
     case GIMPLE_OMP_SECTIONS_SWITCH:
     case GIMPLE_OMP_SECTION:
diff --git a/gcc/gimple-pretty-print.cc b/gcc/gimple-pretty-print.cc
index 08b823c84ef..e7b2df9a0ef 100644
--- a/gcc/gimple-pretty-print.cc
+++ b/gcc/gimple-pretty-print.cc
@@ -1726,6 +1726,35 @@  dump_gimple_omp_scope (pretty_printer *pp, const gimple *gs,
     }
 }
 
+/* Dump a GIMPLE_OMP_DISPATCH tuple on the pretty_printer BUFFER.  */
+
+static void
+dump_gimple_omp_dispatch (pretty_printer *buffer, const gimple *gs, int spc,
+			  dump_flags_t flags)
+{
+  if (flags & TDF_RAW)
+    {
+      dump_gimple_fmt (buffer, spc, flags, "%G <%+BODY <%S>%nCLAUSES <", gs,
+		       gimple_omp_body (gs));
+      dump_omp_clauses (buffer, gimple_omp_dispatch_clauses (gs), spc, flags);
+      dump_gimple_fmt (buffer, spc, flags, " >");
+    }
+  else
+    {
+      pp_string (buffer, "#pragma omp dispatch");
+      dump_omp_clauses (buffer, gimple_omp_dispatch_clauses (gs), spc, flags);
+      if (!gimple_seq_empty_p (gimple_omp_body (gs)))
+	{
+	  newline_and_indent (buffer, spc + 2);
+	  pp_left_brace (buffer);
+	  pp_newline (buffer);
+	  dump_gimple_seq (buffer, gimple_omp_body (gs), spc + 4, flags);
+	  newline_and_indent (buffer, spc + 2);
+	  pp_right_brace (buffer);
+	}
+    }
+}
+
 /* Dump a GIMPLE_OMP_TARGET tuple on the pretty_printer PP.  */
 
 static void
@@ -2805,6 +2834,10 @@  pp_gimple_stmt_1 (pretty_printer *pp, const gimple *gs, int spc,
       dump_gimple_omp_scope (pp, gs, spc, flags);
       break;
 
+    case GIMPLE_OMP_DISPATCH:
+      dump_gimple_omp_dispatch(pp, gs, spc, flags);
+      break;
+
     case GIMPLE_OMP_MASTER:
     case GIMPLE_OMP_SECTION:
     case GIMPLE_OMP_STRUCTURED_BLOCK:
diff --git a/gcc/gimple-walk.cc b/gcc/gimple-walk.cc
index 9f768ca20fd..1122713a98b 100644
--- a/gcc/gimple-walk.cc
+++ b/gcc/gimple-walk.cc
@@ -707,6 +707,7 @@  walk_gimple_stmt (gimple_stmt_iterator *gsi, walk_stmt_fn callback_stmt,
     case GIMPLE_OMP_PARALLEL:
     case GIMPLE_OMP_TASK:
     case GIMPLE_OMP_SCOPE:
+    case GIMPLE_OMP_DISPATCH:
     case GIMPLE_OMP_SECTIONS:
     case GIMPLE_OMP_SINGLE:
     case GIMPLE_OMP_TARGET:
diff --git a/gcc/gimple.cc b/gcc/gimple.cc
index a9f968cb038..3a26c74a105 100644
--- a/gcc/gimple.cc
+++ b/gcc/gimple.cc
@@ -1235,6 +1235,21 @@  gimple_build_omp_scope (gimple_seq body, tree clauses)
   return p;
 }
 
+/* Build a GIMPLE_OMP_DISPATCH statement.
+
+   BODY is the target function call to be dispatched.
+   CLAUSES are any of the OMP dispatch construct's clauses: ...  */
+
+gimple *
+gimple_build_omp_dispatch (gimple_seq body, tree clauses)
+{
+  gimple *p = gimple_alloc (GIMPLE_OMP_DISPATCH, 0);
+  gimple_omp_dispatch_set_clauses (p, clauses);
+  if (body)
+    gimple_omp_set_body (p, body);
+
+  return p;
+}
 
 /* Build a GIMPLE_OMP_TARGET statement.
 
@@ -2148,6 +2163,11 @@  gimple_copy (gimple *stmt)
 	  gimple_omp_scope_set_clauses (copy, t);
 	  goto copy_omp_body;
 
+	case GIMPLE_OMP_DISPATCH:
+	  t = unshare_expr (gimple_omp_dispatch_clauses (stmt));
+	  gimple_omp_dispatch_set_clauses (copy, t);
+	  goto copy_omp_body;
+
 	case GIMPLE_OMP_TARGET:
 	  {
 	    gomp_target *omp_target_stmt = as_a <gomp_target *> (stmt);
diff --git a/gcc/gimple.def b/gcc/gimple.def
index fbcd727f945..21c7405875d 100644
--- a/gcc/gimple.def
+++ b/gcc/gimple.def
@@ -350,6 +350,11 @@  DEFGSCODE(GIMPLE_OMP_SCAN, "gimple_omp_scan", GSS_OMP_SINGLE_LAYOUT)
    CLAUSES is an OMP_CLAUSE chain holding the associated clauses.  */
 DEFGSCODE(GIMPLE_OMP_SCOPE, "gimple_omp_scope", GSS_OMP_SINGLE_LAYOUT)
 
+/* GIMPLE_OMP_DISPATCH <BODY, CLAUSES> represents #pragma omp dispatch
+   BODY is the target function call to be dispatched.
+   CLAUSES is an OMP_CLAUSE chain holding the associated clauses.  */
+DEFGSCODE(GIMPLE_OMP_DISPATCH, "gimple_omp_dispatch", GSS_OMP_SINGLE_LAYOUT)
+
 /* OMP_SECTION <BODY> represents #pragma omp section.
    BODY is the sequence of statements in the section body.  */
 DEFGSCODE(GIMPLE_OMP_SECTION, "gimple_omp_section", GSS_OMP)
diff --git a/gcc/gimple.h b/gcc/gimple.h
index bd315ffc2dd..25590a22ffb 100644
--- a/gcc/gimple.h
+++ b/gcc/gimple.h
@@ -746,7 +746,7 @@  struct GTY((tag("GSS_OMP_CONTINUE")))
 };
 
 /* GIMPLE_OMP_SINGLE, GIMPLE_OMP_ORDERED, GIMPLE_OMP_TASKGROUP,
-   GIMPLE_OMP_SCAN, GIMPLE_OMP_MASKED, GIMPLE_OMP_SCOPE.  */
+   GIMPLE_OMP_SCAN, GIMPLE_OMP_MASKED, GIMPLE_OMP_SCOPE, GIMPLE_OMP_DISPATCH. */
 
 struct GTY((tag("GSS_OMP_SINGLE_LAYOUT")))
   gimple_statement_omp_single_layout : public gimple_statement_omp
@@ -1595,6 +1595,7 @@  gomp_task *gimple_build_omp_task (gimple_seq, tree, tree, tree, tree,
 gimple *gimple_build_omp_section (gimple_seq);
 gimple *gimple_build_omp_structured_block (gimple_seq);
 gimple *gimple_build_omp_scope (gimple_seq, tree);
+gimple *gimple_build_omp_dispatch (gimple_seq, tree);
 gimple *gimple_build_omp_master (gimple_seq);
 gimple *gimple_build_omp_masked (gimple_seq, tree);
 gimple *gimple_build_omp_taskgroup (gimple_seq, tree);
@@ -1886,6 +1887,7 @@  gimple_has_substatements (gimple *g)
     case GIMPLE_OMP_PARALLEL:
     case GIMPLE_OMP_TASK:
     case GIMPLE_OMP_SCOPE:
+    case GIMPLE_OMP_DISPATCH:
     case GIMPLE_OMP_SECTIONS:
     case GIMPLE_OMP_SINGLE:
     case GIMPLE_OMP_TARGET:
@@ -5437,6 +5439,34 @@  gimple_omp_scope_set_clauses (gimple *gs, tree clauses)
     = clauses;
 }
 
+/* Return the clauses associated with OMP_DISPATCH statement GS.  */
+
+inline tree
+gimple_omp_dispatch_clauses (const gimple *gs)
+{
+  GIMPLE_CHECK (gs, GIMPLE_OMP_DISPATCH);
+  return static_cast<const gimple_statement_omp_single_layout *> (gs)->clauses;
+}
+
+/* Return a pointer to the clauses associated with OMP dispatch statement
+   GS.  */
+
+inline tree *
+gimple_omp_dispatch_clauses_ptr (gimple *gs)
+{
+  GIMPLE_CHECK (gs, GIMPLE_OMP_DISPATCH);
+  return &static_cast<gimple_statement_omp_single_layout *> (gs)->clauses;
+}
+
+/* Set CLAUSES to be the clauses associated with OMP dispatch statement
+   GS.  */
+
+inline void
+gimple_omp_dispatch_set_clauses (gimple *gs, tree clauses)
+{
+  GIMPLE_CHECK (gs, GIMPLE_OMP_DISPATCH);
+  static_cast<gimple_statement_omp_single_layout *> (gs)->clauses = clauses;
+}
 
 /* Return the kind of the OMP_FOR statemement G.  */
 
@@ -6771,6 +6801,7 @@  gimple_return_set_retval (greturn *gs, tree retval)
     case GIMPLE_OMP_TARGET:			\
     case GIMPLE_OMP_TEAMS:			\
     case GIMPLE_OMP_SCOPE:			\
+    case GIMPLE_OMP_DISPATCH:			\
     case GIMPLE_OMP_SECTION:			\
     case GIMPLE_OMP_STRUCTURED_BLOCK:		\
     case GIMPLE_OMP_MASTER:			\
diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc
index 30bfecf67e5..cf41fa20e57 100644
--- a/gcc/gimplify.cc
+++ b/gcc/gimplify.cc
@@ -161,7 +161,8 @@  enum omp_region_type
 {
   ORT_WORKSHARE = 0x00,
   ORT_TASKGROUP = 0x01,
-  ORT_SIMD 	= 0x04,
+  ORT_DISPATCH	= 0x02,
+  ORT_SIMD	= 0x04,
 
   ORT_PARALLEL	= 0x08,
   ORT_COMBINED_PARALLEL = ORT_PARALLEL | 1,
@@ -4052,6 +4053,7 @@  gimplify_call_expr (tree *expr_p, gimple_seq *pre_p, bool want_value)
   /* Gimplify the function arguments.  */
   if (nargs > 0)
     {
+    tree device_num = NULL_TREE;
       for (i = (PUSH_ARGS_REVERSED ? nargs - 1 : 0);
            PUSH_ARGS_REVERSED ? i >= 0 : i < nargs;
            PUSH_ARGS_REVERSED ? i-- : i++)
@@ -4062,8 +4064,111 @@  gimplify_call_expr (tree *expr_p, gimple_seq *pre_p, bool want_value)
              be the plain PARM_DECL.  */
           if ((i != 1) || !builtin_va_start_p)
             {
-              t = gimplify_arg (&CALL_EXPR_ARG (*expr_p, i), pre_p,
-				EXPR_LOCATION (*expr_p), ! returns_twice);
+	      tree *arg_p = &CALL_EXPR_ARG (*expr_p, i);
+	      tree adjust_args_list;
+	      if (flag_openmp && EXPR_P (CALL_EXPR_FN (*expr_p))
+		  && DECL_P (TREE_OPERAND (CALL_EXPR_FN (*expr_p), 0))
+		  && (adjust_args_list = lookup_attribute (
+			"omp declare variant variant adjust_args",
+			DECL_ATTRIBUTES (
+			  TREE_OPERAND (CALL_EXPR_FN (*expr_p), 0))))
+		       != NULL_TREE)
+		{
+		  tree param
+		    = DECL_ARGUMENTS (TREE_OPERAND (CALL_EXPR_FN (*expr_p), 0));
+
+		  if (param != NULL_TREE)
+		    {
+		      for (int param_idx = 0; param_idx < i; param_idx++)
+			param = TREE_CHAIN (param);
+
+		      bool is_device_ptr = false;
+		      if (gimplify_omp_ctxp != NULL
+			  && gimplify_omp_ctxp->code == OMP_DISPATCH)
+			{
+			  for (tree c = gimplify_omp_ctxp->clauses; c;
+			       c = TREE_CHAIN (c))
+			    {
+			      if (OMP_CLAUSE_CODE (c)
+				  == OMP_CLAUSE_IS_DEVICE_PTR)
+				{
+				  tree decl1 = DECL_NAME (OMP_CLAUSE_DECL (c));
+				  tree decl2
+				    = tree_strip_nop_conversions (*arg_p);
+				  if (TREE_CODE (decl2) == ADDR_EXPR)
+				    decl2 = TREE_OPERAND (decl2, 0);
+				  gcc_assert (TREE_CODE (decl2) == VAR_DECL
+					      || TREE_CODE (decl2)
+						   == PARM_DECL);
+				  decl2 = DECL_NAME (decl2);
+				  if (decl1 == decl2)
+				    {
+				      is_device_ptr = true;
+				      break;
+				    }
+				}
+			      else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_DEVICE)
+				device_num = OMP_CLAUSE_OPERAND (c, 0);
+			    }
+			}
+
+		      bool need_device_ptr = false;
+		      for (tree arg
+			   = TREE_PURPOSE (TREE_VALUE (adjust_args_list));
+			   arg != NULL; arg = TREE_CHAIN (arg))
+			{
+			  if (build_int_cst (integer_type_node, i)
+			      == TREE_VALUE (arg))
+			    {
+			      need_device_ptr = true;
+			      break;
+			    }
+			}
+
+		      if (need_device_ptr && !is_device_ptr)
+			{
+			  if (device_num == NULL_TREE)
+			    {
+			      // device_num = omp_get_default_device();
+			      tree fn = builtin_decl_explicit (
+				BUILT_IN_OMP_GET_DEFAULT_DEVICE);
+			      gcall *call = gimple_build_call (fn, 0);
+			      device_num = create_tmp_var (
+				gimple_call_return_type (call));
+			      gimple_call_set_lhs (call, device_num);
+			      gimplify_seq_add_stmt (pre_p, call);
+			    }
+
+			  // mapped_arg = omp_get_mapped_ptr(arg, device_num);
+			  tree fn = builtin_decl_explicit (
+			    BUILT_IN_OMP_GET_MAPPED_PTR);
+			  *arg_p = (TREE_CODE (*arg_p) == NOP_EXPR)
+				     ? TREE_OPERAND (*arg_p, 0)
+				     : *arg_p;
+			  gimplify_arg (arg_p, pre_p, loc);
+			  gimplify_arg (&device_num, pre_p, loc);
+			  call = gimple_build_call (fn, 2, *arg_p, device_num);
+			  tree mapped_arg
+			    = create_tmp_var (gimple_call_return_type (call));
+			  gimple_call_set_lhs (call, mapped_arg);
+			  gimplify_seq_add_stmt (pre_p, call);
+
+			  *arg_p = mapped_arg;
+
+			  // Mark mapped argument as device pointer to ensure
+			  // idempotency in gimplification
+			  gcc_assert (gimplify_omp_ctxp->code == OMP_DISPATCH);
+			  tree c = build_omp_clause (input_location,
+						     OMP_CLAUSE_IS_DEVICE_PTR);
+			  OMP_CLAUSE_DECL (c) = *arg_p;
+			  OMP_CLAUSE_CHAIN (c) = gimplify_omp_ctxp->clauses;
+			  gimplify_omp_ctxp->clauses = c;
+			}
+		    }
+		}
+
+	      t = gimplify_arg (arg_p, pre_p, EXPR_LOCATION (*expr_p),
+				!returns_twice);
 
               if (t == GS_ERROR)
                 ret = GS_ERROR;
@@ -6310,6 +6415,7 @@  is_gimple_stmt (tree t)
     case OACC_LOOP:
     case OMP_SCAN:
     case OMP_SCOPE:
+    case OMP_DISPATCH:
     case OMP_SECTIONS:
     case OMP_SECTION:
     case OMP_STRUCTURED_BLOCK:
@@ -13130,6 +13236,21 @@  gimplify_scan_omp_clauses (tree *list_p, gimple_seq *pre_p,
 		    break;
 		  }
 	    }
+	  else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_DEVICE
+		   && code == OMP_DISPATCH)
+	    {
+	      bool saved_into_ssa = gimplify_ctxp->into_ssa;
+	      gimplify_ctxp->into_ssa = false;
+	      if (gimplify_expr (&OMP_CLAUSE_DEVICE_ID (c), pre_p, NULL,
+				 is_gimple_val, fb_rvalue)
+		  == GS_ERROR)
+		remove = true;
+	      else if (DECL_P (OMP_CLAUSE_DEVICE_ID (c)))
+		omp_add_variable (ctx, OMP_CLAUSE_DEVICE_ID (c),
+				  GOVD_SHARED | GOVD_SEEN);
+	      gimplify_ctxp->into_ssa = saved_into_ssa;
+	      break;
+	    }
 	  /* Fall through.  */
 
 	case OMP_CLAUSE_PRIORITY:
@@ -13359,6 +13480,14 @@  gimplify_scan_omp_clauses (tree *list_p, gimple_seq *pre_p,
 	  }
 	  break;
 
+	case OMP_CLAUSE_NOVARIANTS:
+	  OMP_CLAUSE_NOVARIANTS_EXPR (c)
+	    = gimple_boolify (OMP_CLAUSE_NOVARIANTS_EXPR (c));
+	  break;
+	case OMP_CLAUSE_NOCONTEXT:
+	  OMP_CLAUSE_NOCONTEXT_EXPR (c)
+	    = gimple_boolify (OMP_CLAUSE_NOCONTEXT_EXPR (c));
+	  break;
 	case OMP_CLAUSE_NOHOST:
 	default:
 	  gcc_unreachable ();
@@ -13813,7 +13942,9 @@  gimplify_adjust_omp_clauses (gimple_seq *pre_p, gimple_seq body, tree *list_p,
     {
       struct gimplify_omp_ctx *octx;
       for (octx = ctx; octx; octx = octx->outer_context)
-	if ((octx->region_type & (ORT_PARALLEL | ORT_TASK | ORT_TEAMS)) != 0)
+	if ((octx->region_type
+	     & (ORT_DISPATCH | ORT_PARALLEL | ORT_TASK | ORT_TEAMS))
+	    != 0)
 	  break;
       if (octx)
 	{
@@ -14624,6 +14755,8 @@  gimplify_adjust_omp_clauses (gimple_seq *pre_p, gimple_seq body, tree *list_p,
 	case OMP_CLAUSE_FINALIZE:
 	case OMP_CLAUSE_INCLUSIVE:
 	case OMP_CLAUSE_EXCLUSIVE:
+	case OMP_CLAUSE_NOVARIANTS:
+	case OMP_CLAUSE_NOCONTEXT:
 	  break;
 
 	case OMP_CLAUSE_NOHOST:
@@ -14713,9 +14846,9 @@  omp_construct_selector_matches (enum tree_code *constructs, int nconstructs,
 	      == ORT_TARGET && ctx->code == OMP_TARGET)
 	  || ((ctx->region_type & ORT_TEAMS) && ctx->code == OMP_TEAMS)
 	  || (ctx->region_type == ORT_WORKSHARE && ctx->code == OMP_FOR)
-	  || (ctx->region_type == ORT_SIMD
-	      && ctx->code == OMP_SIMD
-	      && !omp_find_clause (ctx->clauses, OMP_CLAUSE_BIND)))
+	  || (ctx->region_type == ORT_SIMD && ctx->code == OMP_SIMD
+	      && !omp_find_clause (ctx->clauses, OMP_CLAUSE_BIND))
+	  || (ctx->code == OMP_DISPATCH && omp_has_nocontext () != 1))
 	{
 	  ++cnt;
 	  if (scores)
@@ -14833,6 +14966,60 @@  omp_construct_selector_matches (enum tree_code *constructs, int nconstructs,
   return 0;
 }
 
+/* Try to evaluate a novariants clause. Return 1 if true, 0 if false or absent,
+ * -1 if run-time evaluation is needed. */
+
+int
+omp_has_novariants (void)
+{
+  for (struct gimplify_omp_ctx *ctx = gimplify_omp_ctxp; ctx;
+       ctx = ctx->outer_context)
+    {
+      if (ctx->code == OMP_DISPATCH)
+	{
+	  tree c = omp_find_clause (ctx->clauses, OMP_CLAUSE_NOVARIANTS);
+	  if (c != NULL_TREE)
+	    {
+	      if (integer_nonzerop (OMP_CLAUSE_NOVARIANTS_EXPR (c)))
+		return 1;
+	      else if (integer_zerop (OMP_CLAUSE_NOVARIANTS_EXPR (c)))
+		return 0;
+	      else
+		return -1;
+	    }
+	  return 0;
+	}
+    }
+  return 0;
+}
+
+/* Try to evaluate a nocontext clause. Return 1 if true, 0 if false or absent,
+ * -1 if run-time evaluation is needed. */
+
+int
+omp_has_nocontext (void)
+{
+  for (struct gimplify_omp_ctx *ctx = gimplify_omp_ctxp; ctx;
+       ctx = ctx->outer_context)
+    {
+      if (ctx->code == OMP_DISPATCH)
+	{
+	    tree c = omp_find_clause (ctx->clauses, OMP_CLAUSE_NOCONTEXT);
+	    if (c != NULL_TREE)
+	    {
+	      if (integer_nonzerop (OMP_CLAUSE_NOCONTEXT_EXPR (c)))
+		return 1;
+	      else if (integer_zerop (OMP_CLAUSE_NOCONTEXT_EXPR (c)))
+		return 0;
+	      else
+		return -1;
+	    }
+	    return 0;
+	}
+    }
+  return 0;
+}
+
 /* Gimplify OACC_CACHE.  */
 
 static void
@@ -17826,6 +18013,219 @@  gimplify_omp_ordered (tree expr, gimple_seq body)
   return gimple_build_omp_ordered (body, OMP_ORDERED_CLAUSES (expr));
 }
 
+/* Gimplify an OMP_DISPATCH construct.  */
+
+static enum gimplify_status
+gimplify_omp_dispatch (tree *expr_p, gimple_seq *pre_p)
+{
+  tree expr = *expr_p;
+  gimple_seq body = NULL;
+
+  gimplify_scan_omp_clauses (&OMP_DISPATCH_CLAUSES (expr), pre_p, ORT_DISPATCH,
+			     OMP_DISPATCH);
+  push_gimplify_context ();
+
+  // If device clause, adjust ICV
+  tree device
+    = omp_find_clause (OMP_DISPATCH_CLAUSES (expr), OMP_CLAUSE_DEVICE);
+  tree saved_device_icv;
+  if (device)
+    {
+      // Save current default-device-var ICV
+      saved_device_icv = create_tmp_var (integer_type_node);
+      tree fn = builtin_decl_explicit (BUILT_IN_OMP_GET_DEFAULT_DEVICE);
+      gcall *call = gimple_build_call (fn, 0);
+      gimple_call_set_lhs (call, saved_device_icv);
+      gimplify_seq_add_stmt (&body, call);
+
+      // Set default device
+      fn = builtin_decl_explicit (BUILT_IN_OMP_SET_DEFAULT_DEVICE);
+      call = gimple_build_call (fn, 1, OMP_CLAUSE_DEVICE_ID (device));
+      gimplify_seq_add_stmt (&body, call);
+    }
+
+  // If the novariants and nocontext clauses are not compile-time constants,
+  // we need to generate code for all possible cases:
+  //   if (novariants) // implies nocontext
+  //       base()
+  //   else if (nocontext)
+  //       variant1()
+  //   else
+  //       variant2()
+  tree dispatch_body = OMP_DISPATCH_BODY (expr);
+  if (TREE_CODE (dispatch_body) == BIND_EXPR)
+    dispatch_body = BIND_EXPR_BODY (dispatch_body);
+  if (TREE_CODE (dispatch_body) == STATEMENT_LIST)
+    {
+      // Fortran FE may insert some pre-call code, for instance when an
+      // array is passed as argument. Skip to the actual call.
+      dispatch_body = expr_last (dispatch_body);
+    }
+  gcc_assert (TREE_CODE (dispatch_body) == CALL_EXPR
+	      || TREE_CODE (dispatch_body) == MODIFY_EXPR);
+  tree base_call_expr = dispatch_body;
+  tree dst = base_call_expr;
+  if (TREE_CODE (base_call_expr) == MODIFY_EXPR)
+    {
+      dst = TREE_OPERAND (base_call_expr, 0);
+      base_call_expr = TREE_OPERAND (base_call_expr, 1);
+      while (TREE_CODE (base_call_expr) == FLOAT_EXPR
+	     || TREE_CODE (base_call_expr) == CONVERT_EXPR
+	     || TREE_CODE (base_call_expr) == COMPLEX_EXPR)
+	base_call_expr = TREE_OPERAND (base_call_expr, 0);
+    }
+
+  tree base_fndecl = get_callee_fndecl (STRIP_NOPS (base_call_expr));
+  if (base_fndecl != NULL_TREE)
+    {
+      if (DECL_VIRTUAL_P (base_fndecl))
+	{
+	  error_at (
+	    EXPR_LOCATION (base_call_expr),
+	    "%qD is a virtual function but only a direct call is allowed "
+	    "in a dispatch construct",
+	    DECL_NAME (base_fndecl));
+	}
+
+      tree variant_fndecl = omp_resolve_declare_variant (base_fndecl);
+      if (base_fndecl != variant_fndecl
+	  && (omp_has_novariants () == -1 || omp_has_nocontext () == -1))
+	{
+	  tree novariants_clause = NULL_TREE, nocontext_clause = NULL_TREE,
+	       novariants_cond = NULL_TREE, nocontext_cond = NULL_TREE;
+	  for (tree c = OMP_DISPATCH_CLAUSES (expr); c; c = TREE_CHAIN (c))
+	    {
+	      if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_NOVARIANTS)
+		{
+		  gcc_assert (novariants_cond == NULL_TREE);
+		  novariants_clause = c;
+		  novariants_cond = OMP_CLAUSE_NOVARIANTS_EXPR (c);
+		}
+	      else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_NOCONTEXT)
+		{
+		  gcc_assert (nocontext_cond == NULL_TREE);
+		  nocontext_clause = c;
+		  nocontext_cond = OMP_CLAUSE_NOCONTEXT_EXPR (c);
+		}
+	    }
+	  gcc_assert (novariants_cond != NULL_TREE
+		      || nocontext_cond != NULL_TREE);
+
+	  enum gimplify_status ret
+	    = gimplify_expr (&novariants_cond, &body, NULL, is_gimple_val,
+			     fb_rvalue);
+	  if (ret == GS_ERROR || ret == GS_UNHANDLED)
+	    return ret;
+	  ret = gimplify_expr (&nocontext_cond, &body, NULL, is_gimple_val,
+			       fb_rvalue);
+	  if (ret == GS_ERROR || ret == GS_UNHANDLED)
+	    return ret;
+
+	  tree base_label = create_artificial_label (UNKNOWN_LOCATION);
+	  tree variant1_label = create_artificial_label (UNKNOWN_LOCATION);
+	  tree cond_label = create_artificial_label (UNKNOWN_LOCATION);
+	  tree variant2_label = create_artificial_label (UNKNOWN_LOCATION);
+	  tree end_label = create_artificial_label (UNKNOWN_LOCATION);
+
+	  if (novariants_cond != NULL_TREE)
+	    {
+	      gcond *novariants_cond_stmt
+		= gimple_build_cond_from_tree (novariants_cond, base_label,
+					       cond_label);
+	      gimplify_seq_add_stmt (&body, novariants_cond_stmt);
+
+	      gimplify_seq_add_stmt (&body, gimple_build_label (base_label));
+	      tree base_call_expr2 = copy_node (base_call_expr);
+	      if (TREE_CODE (dispatch_body) == MODIFY_EXPR)
+		{
+		  base_call_expr2 = build2 (MODIFY_EXPR, TREE_TYPE (dst), dst,
+					    base_call_expr2);
+		}
+	      OMP_CLAUSE_NOVARIANTS_EXPR (novariants_clause)
+		= boolean_true_node;
+	      gimplify_and_add (base_call_expr2, &body);
+	      gimplify_seq_add_stmt (&body, gimple_build_goto (end_label));
+
+	      OMP_CLAUSE_NOVARIANTS_EXPR (novariants_clause)
+		= boolean_false_node;
+	    }
+
+	  gimplify_seq_add_stmt (&body, gimple_build_label (cond_label));
+	  if (nocontext_cond != NULL_TREE)
+	    {
+	      gcond *nocontext_cond_stmt
+		= gimple_build_cond_from_tree (nocontext_cond, variant1_label,
+					       variant2_label);
+	      gimplify_seq_add_stmt (&body, nocontext_cond_stmt);
+
+	      gimplify_seq_add_stmt (&body,
+				     gimple_build_label (variant1_label));
+	      tree variant_call_expr = copy_node (base_call_expr);
+	      if (TREE_CODE (dispatch_body) == MODIFY_EXPR)
+		{
+		  variant_call_expr = build2 (MODIFY_EXPR, TREE_TYPE (dst), dst,
+					      variant_call_expr);
+		}
+	      OMP_CLAUSE_NOCONTEXT_EXPR (nocontext_clause) = boolean_true_node;
+	      gimplify_and_add (variant_call_expr, &body);
+	      gimplify_seq_add_stmt (&body, gimple_build_goto (end_label));
+	      OMP_CLAUSE_NOCONTEXT_EXPR (nocontext_clause) = boolean_false_node;
+	    }
+
+	  gimplify_seq_add_stmt (&body, gimple_build_label (variant2_label));
+	  tree variant_call_expr = copy_node (base_call_expr);
+	  if (TREE_CODE (dispatch_body) == MODIFY_EXPR)
+	    {
+	      variant_call_expr
+		= build2 (MODIFY_EXPR, TREE_TYPE (dst), dst, variant_call_expr);
+	    }
+	  gimplify_and_add (variant_call_expr, &body);
+	  gimplify_seq_add_stmt (&body, gimple_build_goto (end_label));
+	  gimplify_seq_add_stmt (&body, gimple_build_label (end_label));
+	}
+      else
+	gimplify_and_add (OMP_DISPATCH_BODY (expr), &body);
+    }
+  else
+    gimplify_and_add (OMP_DISPATCH_BODY (expr), &body);
+
+  // Restore default-device-var ICV
+  if (device)
+    {
+      tree fn = builtin_decl_explicit (BUILT_IN_OMP_SET_DEFAULT_DEVICE);
+      gcall *call = gimple_build_call (fn, 1, saved_device_icv);
+      gimplify_seq_add_stmt (&body, call);
+    }
+
+  // Wrap dispatch body into a bind
+  gimple *bind = gimple_build_bind (NULL_TREE, body, NULL_TREE);
+  pop_gimplify_context (bind);
+
+  gimplify_adjust_omp_clauses (pre_p, bind, &OMP_DISPATCH_CLAUSES (expr),
+			       OMP_DISPATCH);
+
+  // Remove nowait as it has no effect on dispatch (OpenMP 5.2), and depend and
+  // device clauses as they have already been handled
+  tree *dispatch_clauses_ptr = &OMP_DISPATCH_CLAUSES (expr);
+  for (tree c = *dispatch_clauses_ptr; c; c = *dispatch_clauses_ptr)
+    {
+      if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_NOWAIT
+	  || OMP_CLAUSE_CODE (c) == OMP_CLAUSE_DEPEND
+	  || OMP_CLAUSE_CODE (c) == OMP_CLAUSE_DEVICE)
+	{
+	  *dispatch_clauses_ptr = OMP_CLAUSE_CHAIN (c);
+	  break;
+	}
+      else
+	dispatch_clauses_ptr = &OMP_CLAUSE_CHAIN (c);
+    }
+
+  gimple *stmt = gimple_build_omp_dispatch (bind, OMP_DISPATCH_CLAUSES (expr));
+  gimplify_seq_add_stmt (pre_p, stmt);
+  *expr_p = NULL_TREE;
+  return GS_ALL_DONE;
+}
+
 /* Convert the GENERIC expression tree *EXPR_P to GIMPLE.  If the
    expression produces a value to be used as an operand inside a GIMPLE
    statement, the value will be stored back in *EXPR_P.  This value will
@@ -18764,6 +19164,10 @@  gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p,
 	  ret = gimplify_omp_atomic (expr_p, pre_p);
 	  break;
 
+	case OMP_DISPATCH:
+	  ret = gimplify_omp_dispatch (expr_p, pre_p);
+	  break;
+
 	case TRANSACTION_EXPR:
 	  ret = gimplify_transaction (expr_p, pre_p);
 	  break;
@@ -19089,7 +19493,8 @@  gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p,
 		  && code != OMP_SECTION
 		  && code != OMP_STRUCTURED_BLOCK
 		  && code != OMP_SINGLE
-		  && code != OMP_SCOPE);
+		  && code != OMP_SCOPE
+		  && code != OMP_DISPATCH);
     }
 #endif
 
diff --git a/gcc/gimplify.h b/gcc/gimplify.h
index ac3cc8eb552..55aece2b65b 100644
--- a/gcc/gimplify.h
+++ b/gcc/gimplify.h
@@ -77,6 +77,8 @@  extern enum gimplify_status gimplify_expr (tree *, gimple_seq *, gimple_seq *,
 					   bool (*) (tree), fallback_t);
 
 int omp_construct_selector_matches (enum tree_code *, int, int *);
+int omp_has_novariants (void);
+int omp_has_nocontext (void);
 
 extern void gimplify_type_sizes (tree, gimple_seq *);
 extern void gimplify_one_sizepos (tree *, gimple_seq *);
diff --git a/gcc/omp-builtins.def b/gcc/omp-builtins.def
index 044d5d087b6..c83edabbcc3 100644
--- a/gcc/omp-builtins.def
+++ b/gcc/omp-builtins.def
@@ -76,6 +76,12 @@  DEF_GOMP_BUILTIN (BUILT_IN_OMP_GET_TEAM_NUM, "omp_get_team_num",
 		  BT_FN_INT, ATTR_CONST_NOTHROW_LEAF_LIST)
 DEF_GOMP_BUILTIN (BUILT_IN_OMP_GET_NUM_TEAMS, "omp_get_num_teams",
 		  BT_FN_INT, ATTR_CONST_NOTHROW_LEAF_LIST)
+DEF_GOMP_BUILTIN (BUILT_IN_OMP_GET_MAPPED_PTR, "omp_get_mapped_ptr",
+		  BT_FN_PTR_CONST_PTR_INT, ATTR_NOTHROW_LEAF_LIST)
+DEF_GOMP_BUILTIN (BUILT_IN_OMP_GET_DEFAULT_DEVICE, "omp_get_default_device",
+		  BT_FN_INT, ATTR_NOTHROW_LEAF_LIST)
+DEF_GOMP_BUILTIN (BUILT_IN_OMP_SET_DEFAULT_DEVICE, "omp_set_default_device",
+		  BT_FN_INT, ATTR_NOTHROW_LEAF_LIST)
 
 DEF_GOMP_BUILTIN (BUILT_IN_GOMP_ATOMIC_START, "GOMP_atomic_start",
 		  BT_FN_VOID, ATTR_NOTHROW_LEAF_LIST)
diff --git a/gcc/omp-expand.cc b/gcc/omp-expand.cc
index 24287826444..6fa372a550b 100644
--- a/gcc/omp-expand.cc
+++ b/gcc/omp-expand.cc
@@ -8636,6 +8636,19 @@  expand_omp_single (struct omp_region *region)
   single_succ_edge (exit_bb)->flags = EDGE_FALLTHRU;
 }
 
+/* Expand code for an OpenMP dispatch directive...  */
+
+static void
+expand_omp_dispatch (struct omp_region *region)
+{
+  basic_block entry_bb = region->entry;
+  gimple_stmt_iterator si = gsi_last_nondebug_bb (entry_bb);
+  enum gimple_code code = gimple_code (gsi_stmt (si));
+  gcc_assert (code == GIMPLE_OMP_DISPATCH);
+  gsi_remove (&si, true);
+  single_succ_edge (entry_bb)->flags = EDGE_FALLTHRU;
+}
+
 /* Generic expansion for OpenMP synchronization directives: master,
    ordered and critical.  All we need to do here is remove the entry
    and exit markers for REGION.  */
@@ -10654,6 +10667,10 @@  expand_omp (struct omp_region *region)
 	  expand_omp_single (region);
 	  break;
 
+	case GIMPLE_OMP_DISPATCH:
+	  expand_omp_dispatch (region);
+	  break;
+
 	case GIMPLE_OMP_ORDERED:
 	  {
 	    gomp_ordered *ord_stmt
@@ -11001,6 +11018,7 @@  omp_make_gimple_edges (basic_block bb, struct omp_region **region,
     case GIMPLE_OMP_MASTER:
     case GIMPLE_OMP_MASKED:
     case GIMPLE_OMP_SCOPE:
+    case GIMPLE_OMP_DISPATCH:
     case GIMPLE_OMP_CRITICAL:
     case GIMPLE_OMP_SECTION:
       cur_region = new_omp_region (bb, code, cur_region);
diff --git a/gcc/omp-general.cc b/gcc/omp-general.cc
index 0b61335dba4..2604ec4cd2b 100644
--- a/gcc/omp-general.cc
+++ b/gcc/omp-general.cc
@@ -1048,7 +1048,7 @@  omp_construct_traits_to_codes (tree ctx, int nconstructs,
   /* Order must match the OMP_TRAIT_CONSTRUCT_* enumerators in
      enum omp_ts_code.  */
   static enum tree_code code_map[]
-    = { OMP_TARGET, OMP_TEAMS, OMP_PARALLEL, OMP_FOR, OMP_SIMD };
+    = { OMP_TARGET, OMP_TEAMS, OMP_PARALLEL, OMP_FOR, OMP_SIMD, OMP_DISPATCH };
 
   for (tree ts = ctx; ts; ts = TREE_CHAIN (ts), i--)
     {
@@ -1141,6 +1141,7 @@  const char *omp_tss_map[] =
    "target_device",
    "implementation",
    "user",
+   "need_device_ptr",
    NULL
 };
 
@@ -1247,10 +1248,14 @@  struct omp_ts_info omp_ts_map[] =
      OMP_TRAIT_PROPERTY_CLAUSE_LIST,  false,
      NULL
    },
+   { "dispatch",
+     (1 << OMP_TRAIT_SET_CONSTRUCT),
+     OMP_TRAIT_PROPERTY_NONE,  false,
+     NULL
+   },
    { NULL, 0, OMP_TRAIT_PROPERTY_NONE, false, NULL }  /* OMP_TRAIT_LAST */
   };
 
-
 /* Return a name from PROP, a property in selectors accepting
    name lists.  */
 
@@ -2495,6 +2500,9 @@  omp_resolve_declare_variant (tree base)
   if (cfun && (cfun->curr_properties & PROP_gimple_any) != 0)
     return omp_resolve_late_declare_variant (base);
 
+  if (omp_has_novariants () == 1)
+    return base;
+
   auto_vec <tree, 16> variants;
   auto_vec <bool, 16> defer;
   bool any_deferred = false;
@@ -2641,6 +2649,8 @@  omp_resolve_declare_variant (tree base)
       (*slot)->variants = entry.variants;
       tree alt = build_decl (DECL_SOURCE_LOCATION (base), FUNCTION_DECL,
 			     DECL_NAME (base), TREE_TYPE (base));
+      if (DECL_ASSEMBLER_NAME_SET_P (base))
+	SET_DECL_ASSEMBLER_NAME (alt, DECL_ASSEMBLER_NAME (base));
       DECL_ARTIFICIAL (alt) = 1;
       DECL_IGNORED_P (alt) = 1;
       TREE_STATIC (alt) = 1;
diff --git a/gcc/omp-low.cc b/gcc/omp-low.cc
index 4d003f42098..693d8ca7d8d 100644
--- a/gcc/omp-low.cc
+++ b/gcc/omp-low.cc
@@ -4185,6 +4185,11 @@  scan_omp_1_stmt (gimple_stmt_iterator *gsi, bool *handled_ops_p,
       scan_omp (gimple_omp_body_ptr (stmt), ctx);
       break;
 
+    case GIMPLE_OMP_DISPATCH:
+      ctx = new_omp_context (stmt, ctx);
+      scan_omp (gimple_omp_body_ptr (stmt), ctx);
+      break;
+
     case GIMPLE_OMP_SECTIONS:
       scan_omp_sections (as_a <gomp_sections *> (stmt), ctx);
       break;
@@ -8926,6 +8931,31 @@  lower_omp_scope (gimple_stmt_iterator *gsi_p, omp_context *ctx)
   if (BLOCK_VARS (block))
     TREE_USED (block) = 1;
 }
+
+/* Lower code for an OMP dispatch directive.  */
+
+static void
+lower_omp_dispatch (gimple_stmt_iterator *gsi_p, omp_context *ctx)
+{
+  tree block;
+  gimple *stmt = gsi_stmt (*gsi_p);
+  gbind *bind;
+
+  push_gimplify_context ();
+
+  block = make_node (BLOCK);
+  bind = gimple_build_bind (NULL, NULL, block);
+  gsi_replace (gsi_p, bind, true);
+
+  lower_omp (gimple_omp_body_ptr (stmt), ctx);
+  gimple_bind_set_body (bind, maybe_catch_exception (gimple_omp_body (stmt)));
+
+  pop_gimplify_context (bind);
+
+  gimple_bind_append_vars (bind, ctx->block_vars);
+  BLOCK_VARS (block) = ctx->block_vars;
+}
+
 /* Expand code for an OpenMP master or masked directive.  */
 
 static void
@@ -14399,6 +14429,11 @@  lower_omp_1 (gimple_stmt_iterator *gsi_p, omp_context *ctx)
       gcc_assert (ctx);
       lower_omp_scope (gsi_p, ctx);
       break;
+    case GIMPLE_OMP_DISPATCH:
+      ctx = maybe_lookup_ctx (stmt);
+      gcc_assert (ctx);
+      lower_omp_dispatch (gsi_p, ctx);
+      break;
     case GIMPLE_OMP_SINGLE:
       ctx = maybe_lookup_ctx (stmt);
       gcc_assert (ctx);
diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
index f31a34ac410..2e06b706025 100644
--- a/gcc/tree-inline.cc
+++ b/gcc/tree-inline.cc
@@ -1679,6 +1679,12 @@  remap_gimple_stmt (gimple *stmt, copy_body_data *id)
 		   (s1, gimple_omp_scope_clauses (stmt));
 	  break;
 
+	case GIMPLE_OMP_DISPATCH:
+	  s1 = remap_gimple_seq (gimple_omp_body (stmt), id);
+	  copy = gimple_build_omp_dispatch (s1,
+					    gimple_omp_dispatch_clauses (stmt));
+	  break;
+
 	case GIMPLE_OMP_TASKGROUP:
 	  s1 = remap_gimple_seq (gimple_omp_body (stmt), id);
 	  copy = gimple_build_omp_taskgroup
@@ -4609,6 +4615,7 @@  estimate_num_insns (gimple *stmt, eni_weights *weights)
     case GIMPLE_OMP_MASTER:
     case GIMPLE_OMP_MASKED:
     case GIMPLE_OMP_SCOPE:
+    case GIMPLE_OMP_DISPATCH:
     case GIMPLE_OMP_TASKGROUP:
     case GIMPLE_OMP_ORDERED:
     case GIMPLE_OMP_SCAN: