diff mbox series

[3/3] OpenACC dynamic data lifetimes ending within structured blocks

Message ID 4673a5070087e465f6dd123715d409b35b875ca1.1579292772.git.julian@codesourcery.com
State New
Headers show
Series Mixed static/dynamic data lifetimes with OpenACC (PR92843) | expand

Commit Message

Julian Brown Jan. 17, 2020, 9:18 p.m. UTC
This patch adds a new function to logically decrement the "dynamic
reference counter" for a mapped OpenACC variable, and handles some cases
in which that counter drops to zero inside a structured data
block. Previously, it's likely that at least in some cases, ending a
dynamic data lifetime in this way could behave unpredictably.

Several new test cases are included.

This patch is strongly related to the previous two, but is somewhat of
a separate change, and those two patches can stand alone if this one
gets deferred.

Tested alongside the previous patches in the series with offloading to NVPTX.

OK?

Thanks,

Julian

ChangeLog

	libgomp/
	* oacc-mem.c (decr_dynamic_refcount): New function.
	(goacc_exit_datum): Call above function.
	(goacc_exit_data_internal): Call above function.
	* testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1.c: New
	test.
	* testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1-lib.c:
	Likewise.
	* testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6.c:
	Likewise.
	* testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6-lib.c:
	Likewise.
	* testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7.c:
	Likewise.
	* testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7-lib.c:
	Likewise.
	* testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8.c:
	Likewise.
	* testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8-lib.c:
	Likewise.
---
 libgomp/oacc-mem.c                            | 128 ++++++++++----
 .../static-dynamic-lifetimes-1-lib.c          |   3 +
 .../static-dynamic-lifetimes-1.c              | 160 ++++++++++++++++++
 .../static-dynamic-lifetimes-6-lib.c          |   5 +
 .../static-dynamic-lifetimes-6.c              |  46 +++++
 .../static-dynamic-lifetimes-7-lib.c          |   5 +
 .../static-dynamic-lifetimes-7.c              |  45 +++++
 .../static-dynamic-lifetimes-8-lib.c          |   5 +
 .../static-dynamic-lifetimes-8.c              |  50 ++++++
 9 files changed, 412 insertions(+), 35 deletions(-)
 create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1-lib.c
 create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1.c
 create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6-lib.c
 create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6.c
 create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7-lib.c
 create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7.c
 create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8-lib.c
 create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8.c

Comments

Thomas Schwinge May 11, 2020, 3:05 p.m. UTC | #1
Hi Julian!

On 2020-01-17T13:18:21-0800, Julian Brown <julian@codesourcery.com> wrote:
> This patch adds a new function to logically decrement the "dynamic
> reference counter" for a mapped OpenACC variable, and handles some cases
> in which that counter drops to zero inside a structured data
> block. Previously, it's likely that at least in some cases, ending a
> dynamic data lifetime in this way could behave unpredictably.
>
> Several new test cases are included.

As discussed before, all these test cases were already PASSing before any
of this thread's suggested patches (also for GCC 9), so "from a user's
point of view", all we get here are testsuite regressions:

  - 'libgomp.oacc-c-c++-common/structured-dynamic-lifetimes-6-lib.c'
  - 'libgomp.oacc-c-c++-common/structured-dynamic-lifetimes-6.c'
  - 'libgomp.oacc-c-c++-common/structured-dynamic-lifetimes-7-lib.c'
  - 'libgomp.oacc-c-c++-common/structured-dynamic-lifetimes-7.c'
  - 'libgomp.oacc-c-c++-common/structured-dynamic-lifetimes-8-lib.c'
  - 'libgomp.oacc-c-c++-common/structured-dynamic-lifetimes-8.c'

(Adjusted for the version of the test cases already committed; but
already XFAILed in your original patch submission, see below.)


And: the code changes proposed here are breaking compatibility with GCC
9, such that OpenACC/Fortran code compiled with GCC 9, but running with
recent runtime libraries (common case for users, distributions) would
then terminate with: 'libgomp: cannot handle 'exit data' within data
region'.  For example, half of all 'libgomp.oacc-fortran' test cases
using OpenACC 'exit data':

  - 'libgomp.oacc-fortran/data-2.f90'
  - 'libgomp.oacc-fortran/data-3.f90'
  - 'libgomp.oacc-fortran/data-4-2.f90'
  - 'libgomp.oacc-fortran/data-4.f90'
  - 'libgomp.oacc-fortran/if-1.f90'

Even though that "code generation problem" doesn't exist with GCC 10 and
newer, we still have to maintain ABI compatibility with existing binaries
compiled compiled with GCC 9.  (Or, as a less preferred solution, arrange
so that they use host-fallback execution insted of offloading.)


Grüße
 Thomas


> This patch is strongly related to the previous two, but is somewhat of
> a separate change, and those two patches can stand alone if this one
> gets deferred.
>
> Tested alongside the previous patches in the series with offloading to NVPTX.
>
> OK?
>
> Thanks,
>
> Julian
>
> ChangeLog
>
>       libgomp/
>       * oacc-mem.c (decr_dynamic_refcount): New function.
>       (goacc_exit_datum): Call above function.
>       (goacc_exit_data_internal): Call above function.
>       * testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1.c: New
>       test.
>       * testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1-lib.c:
>       Likewise.
>       * testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6.c:
>       Likewise.
>       * testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6-lib.c:
>       Likewise.
>       * testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7.c:
>       Likewise.
>       * testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7-lib.c:
>       Likewise.
>       * testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8.c:
>       Likewise.
>       * testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8-lib.c:
>       Likewise.
> ---
>  libgomp/oacc-mem.c                            | 128 ++++++++++----
>  .../static-dynamic-lifetimes-1-lib.c          |   3 +
>  .../static-dynamic-lifetimes-1.c              | 160 ++++++++++++++++++
>  .../static-dynamic-lifetimes-6-lib.c          |   5 +
>  .../static-dynamic-lifetimes-6.c              |  46 +++++
>  .../static-dynamic-lifetimes-7-lib.c          |   5 +
>  .../static-dynamic-lifetimes-7.c              |  45 +++++
>  .../static-dynamic-lifetimes-8-lib.c          |   5 +
>  .../static-dynamic-lifetimes-8.c              |  50 ++++++
>  9 files changed, 412 insertions(+), 35 deletions(-)
>  create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1-lib.c
>  create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1.c
>  create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6-lib.c
>  create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6.c
>  create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7-lib.c
>  create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7.c
>  create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8-lib.c
>  create mode 100644 libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8.c
>
> diff --git a/libgomp/oacc-mem.c b/libgomp/oacc-mem.c
> index 783e7f363fb..f34ffa67079 100644
> --- a/libgomp/oacc-mem.c
> +++ b/libgomp/oacc-mem.c
> @@ -725,6 +725,92 @@ acc_pcopyin (void *h, size_t s)
>  #endif
>
>
> +/* Perform actions necessary to decrement the dynamic reference counter for
> +   splay tree key N.  Returns TRUE on success, or FALSE on failure (e.g. if we
> +   hit a case we can't presently handle inside a data region).  */
> +
> +static bool
> +decr_dynamic_refcount (splay_tree_key n, bool finalize)
> +{
> +  if (finalize)
> +    {
> +      if (n->refcount != REFCOUNT_INFINITY)
> +     n->refcount -= n->virtual_refcount;
> +      n->virtual_refcount = 0;
> +    }
> +
> +  if (n->virtual_refcount > 0)
> +    {
> +      if (n->refcount != REFCOUNT_INFINITY)
> +     n->refcount--;
> +      n->virtual_refcount--;
> +    }
> +  /* An initial "enter data" mapping might create a target_mem_desc (in
> +     gomp_map_vars_async via goacc_enter_datum or
> +     goacc_enter_data_internal).  In that case we have a structural
> +     reference count but a zero virtual reference count: we nevertheless
> +     want to do the "exit data" operation here.  Detect the special case
> +     using a sentinel value stored in the "prev" field, which is otherwise
> +     unused for dynamic data mappings.  */
> +  else if (n->refcount > 0
> +        && n->refcount != REFCOUNT_INFINITY
> +        && n->tgt->prev == &dyn_tgt_sentinel)
> +    {
> +      n->refcount--;
> +      /* We know n->virtual_refcount is zero here, so if we still have a
> +      non-zero n->refcount we are ending a dynamically-scoped variable
> +      lifetime in the middle of a static lifetime for the same variable.
> +      If we're not careful this results in a dangling reference.  Attempt
> +      to handle this here, if only in simple cases.  E.g.:
> +
> +        #pragma acc enter data copyin(var)
> +        #pragma acc data copy(var{, ...})
> +        {
> +          #pragma acc exit data copyout(var)
> +        }
> +
> +      Here (the "exit data"), we reattach the relevant fields of the
> +      previously dynamically-scoped target_mem_desc to the static data
> +      region's target_mem_desc, hence merging the former into the latter.
> +      The old dynamic target_mem_desc can then be freed.
> +
> +      We can't deal with static data regions that refer to existing dynamic
> +      data mappings or that introduce new static lifetimes of their own.  */
> +      if (n->refcount > 0
> +       && n->tgt->list_count == 1
> +       && n->tgt->refcount == 1)
> +     {
> +       struct goacc_thread *thr = goacc_thread ();
> +       struct target_mem_desc *tgt, *static_tgt = NULL;
> +       for (tgt = thr->mapped_data;
> +            tgt != NULL && static_tgt == NULL;
> +            tgt = tgt->prev)
> +         for (int j = 0; j < tgt->list_count; j++)
> +           if (tgt->list[j].key == n)
> +             {
> +               static_tgt = tgt;
> +               break;
> +             }
> +       if (!static_tgt
> +           || static_tgt->to_free != NULL
> +           || static_tgt->array != NULL)
> +         return false;
> +       static_tgt->to_free = n->tgt->to_free;
> +       static_tgt->array = n->tgt->array;
> +       static_tgt->tgt_start = n->tgt->tgt_start;
> +       static_tgt->tgt_end = n->tgt->tgt_end;
> +       static_tgt->to_free = n->tgt->to_free;
> +       static_tgt->refcount++;
> +       free (n->tgt);
> +       n->tgt = static_tgt;
> +     }
> +      else if (n->refcount > 0)
> +     return false;
> +    }
> +
> +  return true;
> +}
> +
>  /* Exit a dynamic mapping for a single variable.  */
>
>  static void
> @@ -767,29 +853,12 @@ goacc_exit_datum (void *h, size_t s, unsigned short kind, int async)
>
>    bool finalize = (kind == GOMP_MAP_DELETE
>                  || kind == GOMP_MAP_FORCE_FROM);
> -  if (finalize)
> -    {
> -      if (n->refcount != REFCOUNT_INFINITY)
> -     n->refcount -= n->virtual_refcount;
> -      n->virtual_refcount = 0;
> -    }
>
> -  if (n->virtual_refcount > 0)
> +  if (!decr_dynamic_refcount (n, finalize))
>      {
> -      if (n->refcount != REFCOUNT_INFINITY)
> -     n->refcount--;
> -      n->virtual_refcount--;
> +      gomp_mutex_unlock (&acc_dev->lock);
> +      gomp_fatal ("cannot handle delete/copyout within data region");
>      }
> -  /* An initial "enter data" mapping might create a target_mem_desc (in
> -     gomp_map_vars_async via goacc_enter_datum).  In that case we have a
> -     structural reference count but a zero virtual reference count: we
> -     nevertheless want to do the "exit data" operation here.  Detect the
> -     special case using a sentinel value stored in the "prev" field, which is
> -     otherwise unused for dynamic data mappings.  */
> -  else if (n->refcount > 0
> -        && n->refcount != REFCOUNT_INFINITY
> -        && n->tgt->prev == &dyn_tgt_sentinel)
> -    n->refcount--;
>
>    if (n->refcount == 0)
>      {
> @@ -1216,23 +1285,12 @@ goacc_exit_data_internal (struct gomp_device_descr *acc_dev, size_t mapnum,
>           if (n == NULL)
>             continue;
>
> -         if (finalize)
> -           {
> -             if (n->refcount != REFCOUNT_INFINITY)
> -               n->refcount -= n->virtual_refcount;
> -             n->virtual_refcount = 0;
> -           }
> -
> -         if (n->virtual_refcount > 0)
> +         if (!decr_dynamic_refcount (n, finalize))
>             {
> -             if (n->refcount != REFCOUNT_INFINITY)
> -               n->refcount--;
> -             n->virtual_refcount--;
> +             /* The user is trying to do something too tricky for us.  */
> +             gomp_mutex_unlock (&acc_dev->lock);
> +             gomp_fatal ("cannot handle 'exit data' within data region");
>             }
> -         else if (n->refcount > 0
> -                  && n->refcount != REFCOUNT_INFINITY
> -                  && n->tgt->prev == &dyn_tgt_sentinel)
> -           n->refcount--;
>
>           if (copyfrom
>               && n->refcount != REFCOUNT_INFINITY
> diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1-lib.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1-lib.c
> new file mode 100644
> index 00000000000..23c20d4fab7
> --- /dev/null
> +++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1-lib.c
> @@ -0,0 +1,3 @@
> +/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
> +/* { dg-additional-options "-DOPENACC_API" } */
> +#include "static-dynamic-lifetimes-1.c"
> diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1.c
> new file mode 100644
> index 00000000000..a743660f53e
> --- /dev/null
> +++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1.c
> @@ -0,0 +1,160 @@
> +/* Test transitioning of data lifetimes between static and dynamic.  */
> +
> +/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
> +
> +#include <openacc.h>
> +#include <assert.h>
> +#include <stdlib.h>
> +
> +#define SIZE 1024
> +
> +void
> +f1 (void)
> +{
> +  char *block1 = (char *) malloc (SIZE);
> +
> +#ifdef OPENACC_API
> +  acc_copyin (block1, SIZE);
> +  acc_copyin (block1, SIZE);
> +#else
> +#pragma acc enter data copyin(block1[0:SIZE])
> +#pragma acc enter data copyin(block1[0:SIZE])
> +#endif
> +
> +#pragma acc data copy(block1[0:SIZE])
> +  {
> +#ifdef OPENACC_API
> +    acc_copyin (block1, SIZE);
> +#else
> +#pragma acc enter data copyin(block1[0:SIZE])
> +#endif
> +  }
> +
> +  assert (acc_is_present (block1, SIZE));
> +
> +#ifdef OPENACC_API
> +  acc_copyout (block1, SIZE);
> +  assert (acc_is_present (block1, SIZE));
> +  acc_copyout (block1, SIZE);
> +  assert (acc_is_present (block1, SIZE));
> +  acc_copyout (block1, SIZE);
> +  assert (!acc_is_present (block1, SIZE));
> +#else
> +#pragma acc exit data copyout(block1[0:SIZE])
> +  assert (acc_is_present (block1, SIZE));
> +#pragma acc exit data copyout(block1[0:SIZE])
> +  assert (acc_is_present (block1, SIZE));
> +#pragma acc exit data copyout(block1[0:SIZE])
> +  assert (!acc_is_present (block1, SIZE));
> +#endif
> +
> +  free (block1);
> +}
> +
> +void
> +f2 (void)
> +{
> +  char *block1 = (char *) malloc (SIZE);
> +
> +#ifdef OPENACC_API
> +  acc_copyin (block1, SIZE);
> +#else
> +#pragma acc enter data copyin(block1[0:SIZE])
> +#endif
> +
> +#pragma acc data copy(block1[0:SIZE])
> +  {
> +#ifdef OPENACC_API
> +    acc_copyout (block1, SIZE);
> +#else
> +#pragma acc exit data copyout(block1[0:SIZE])
> +#endif
> +    /* This should stay present until the end of the static data lifetime.  */
> +    assert (acc_is_present (block1, SIZE));
> +  }
> +
> +  assert (!acc_is_present (block1, SIZE));
> +
> +  free (block1);
> +}
> +
> +void
> +f3 (void)
> +{
> +  char *block1 = (char *) malloc (SIZE);
> +
> +#ifdef OPENACC_API
> +  acc_copyin (block1, SIZE);
> +#else
> +#pragma acc enter data copyin(block1[0:SIZE])
> +#endif
> +
> +#pragma acc data copy(block1[0:SIZE])
> +  {
> +#ifdef OPENACC_API
> +    acc_copyout (block1, SIZE);
> +    acc_copyin (block1, SIZE);
> +#else
> +#pragma acc exit data copyout(block1[0:SIZE])
> +#pragma acc enter data copyin(block1[0:SIZE])
> +#endif
> +    assert (acc_is_present (block1, SIZE));
> +  }
> +
> +  assert (acc_is_present (block1, SIZE));
> +#ifdef OPENACC_API
> +  acc_copyout (block1, SIZE);
> +#else
> +#pragma acc exit data copyout(block1[0:SIZE])
> +#endif
> +  assert (!acc_is_present (block1, SIZE));
> +
> +  free (block1);
> +}
> +
> +void
> +f4 (void)
> +{
> +  char *block1 = (char *) malloc (SIZE);
> +  char *block2 = (char *) malloc (SIZE);
> +  char *block3 = (char *) malloc (SIZE);
> +
> +#pragma acc data copy(block1[0:SIZE], block2[0:SIZE], block3[0:SIZE])
> +  {
> +  /* The first copyin of block2 is the enclosing data region.  This
> +     "enter data" should make it live beyond the end of this region.
> +     This works, though the on-target copies of block1, block2 and block3
> +     will stay allocated until block2 is unmapped because they are bound
> +     together in a single target_mem_desc.  */
> +#ifdef OPENACC_API
> +    acc_copyin (block2, SIZE);
> +#else
> +#pragma acc enter data copyin(block2[0:SIZE])
> +#endif
> +  }
> +
> +  assert (!acc_is_present (block1, SIZE));
> +  assert (acc_is_present (block2, SIZE));
> +  assert (!acc_is_present (block3, SIZE));
> +
> +#ifdef OPENACC_API
> +  acc_copyout (block2, SIZE);
> +#else
> +#pragma acc exit data copyout(block2[0:SIZE])
> +#endif
> +  assert (!acc_is_present (block2, SIZE));
> +
> +  free (block1);
> +  free (block2);
> +  free (block3);
> +}
> +
> +int
> +main (int argc, char *argv[])
> +{
> +  f1 ();
> +  f2 ();
> +  f3 ();
> +  f4 ();
> +  return 0;
> +}
> diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6-lib.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6-lib.c
> new file mode 100644
> index 00000000000..8507a0586a5
> --- /dev/null
> +++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6-lib.c
> @@ -0,0 +1,5 @@
> +/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
> +/* { dg-additional-options "-DOPENACC_API" } */
> +#include "static-dynamic-lifetimes-6.c"
> +/* { dg-output "libgomp: cannot handle delete/copyout within data region" } */
> +/* { dg-shouldfail "" } */
> diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6.c
> new file mode 100644
> index 00000000000..ca3b385fbcc
> --- /dev/null
> +++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6.c
> @@ -0,0 +1,46 @@
> +/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
> +
> +#include <openacc.h>
> +#include <assert.h>
> +#include <stdlib.h>
> +
> +#define SIZE 1024
> +
> +int
> +main (int argc, char *argv[])
> +{
> +  char *block1 = (char *) malloc (SIZE);
> +  char *block2 = (char *) malloc (SIZE);
> +
> +#ifdef OPENACC_API
> +  acc_copyin (block1, SIZE);
> +  acc_copyin (block2, SIZE);
> +#else
> +#pragma acc enter data copyin(block1[0:SIZE], block2[0:SIZE])
> +#endif
> +
> +#pragma acc data copy(block1[0:SIZE], block2[0:SIZE])
> +  {
> +#ifdef OPENACC_API
> +    acc_copyout (block1, SIZE);
> +    acc_copyout (block2, SIZE);
> +    /* Error output checked in static-dynamic-lifetimes-6-lib.c.  */
> +#else
> +#pragma acc exit data copyout(block1[0:SIZE], block2[0:SIZE])
> +/* We can only do this for a single dynamic data mapping at present.  */
> +/* { dg-output "libgomp: cannot handle .exit data. within data region" } */
> +/* { dg-shouldfail "" } */
> +#endif
> +    /* These should stay present until the end of the static data lifetime.  */
> +    assert (acc_is_present (block1, SIZE));
> +    assert (acc_is_present (block2, SIZE));
> +  }
> +
> +  assert (!acc_is_present (block1, SIZE));
> +  assert (!acc_is_present (block2, SIZE));
> +
> +  free (block1);
> +  free (block2);
> +
> +  return 0;
> +}
> diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7-lib.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7-lib.c
> new file mode 100644
> index 00000000000..962b5926f79
> --- /dev/null
> +++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7-lib.c
> @@ -0,0 +1,5 @@
> +/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
> +/* { dg-additional-options "-DOPENACC_API" } */
> +#include "static-dynamic-lifetimes-7.c"
> +/* { dg-output "libgomp: cannot handle delete/copyout within data region" } */
> +/* { dg-shouldfail "" } */
> diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7.c
> new file mode 100644
> index 00000000000..dfcc7cae961
> --- /dev/null
> +++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7.c
> @@ -0,0 +1,45 @@
> +/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
> +
> +#include <openacc.h>
> +#include <assert.h>
> +#include <stdlib.h>
> +
> +#define SIZE 1024
> +
> +int
> +main (int argc, char *argv[])
> +{
> +  char *block1 = (char *) malloc (SIZE);
> +  char *block2 = (char *) malloc (SIZE);
> +
> +#ifdef OPENACC_API
> +  acc_copyin (block1, SIZE);
> +#else
> +#pragma acc enter data copyin(block1[0:SIZE])
> +#endif
> +
> +#pragma acc data copy(block1[0:SIZE], block2[0:SIZE])
> +  {
> +/* We can't attach the dynamic data mapping's (block1) target_mem_desc to the
> +   enclosing static data region here, because that region maps block2 also.  */
> +#ifdef OPENACC_API
> +    acc_copyout (block1, SIZE);
> +    /* Error output checked in static-dynamic-lifetimes-7-lib.c.  */
> +#else
> +#pragma acc exit data copyout(block1[0:SIZE])
> +/* { dg-output "libgomp: cannot handle .exit data. within data region" } */
> +/* { dg-shouldfail "" } */
> +#endif
> +    /* These should stay present until the end of the static data lifetime.  */
> +    assert (acc_is_present (block1, SIZE));
> +    assert (acc_is_present (block2, SIZE));
> +  }
> +
> +  assert (!acc_is_present (block1, SIZE));
> +  assert (!acc_is_present (block2, SIZE));
> +
> +  free (block1);
> +  free (block2);
> +
> +  return 0;
> +}
> diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8-lib.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8-lib.c
> new file mode 100644
> index 00000000000..2581d7e2559
> --- /dev/null
> +++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8-lib.c
> @@ -0,0 +1,5 @@
> +/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
> +/* { dg-additional-options "-DOPENACC_API" } */
> +#include "static-dynamic-lifetimes-8.c"
> +/* { dg-output "libgomp: cannot handle delete/copyout within data region" } */
> +/* { dg-shouldfail "" } */
> diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8.c
> new file mode 100644
> index 00000000000..e3a64399fe9
> --- /dev/null
> +++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8.c
> @@ -0,0 +1,50 @@
> +/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
> +
> +#include <openacc.h>
> +#include <assert.h>
> +#include <stdlib.h>
> +
> +#define SIZE 1024
> +
> +int
> +main (int argc, char *argv[])
> +{
> +  char *block1 = (char *) malloc (SIZE);
> +  char *block2 = (char *) malloc (SIZE);
> +
> +#ifdef OPENACC_API
> +  acc_copyin (block1, SIZE);
> +#else
> +#pragma acc enter data copyin(block1[0:SIZE])
> +#endif
> +
> +#pragma acc data copy(block1[0:SIZE], block2[0:SIZE])
> +  {
> +#ifdef OPENACC_API
> +    acc_copyout (block1, SIZE);
> +    acc_copyin (block2, SIZE);
> +    /* Error output checked in static-dynamic-lifetimes-8-lib.c.  */
> +#else
> +#pragma acc exit data copyout(block1[0:SIZE])
> +/* { dg-output "libgomp: cannot handle .exit data. within data region" } */
> +/* { dg-shouldfail "" } */
> +#pragma acc enter data copyin(block2[0:SIZE])
> +#endif
> +    assert (acc_is_present (block1, SIZE));
> +    assert (acc_is_present (block2, SIZE));
> +  }
> +
> +  assert (!acc_is_present (block1, SIZE));
> +  assert (acc_is_present (block2, SIZE));
> +#ifdef OPENACC_API
> +  acc_copyout (block2, SIZE);
> +#else
> +#pragma acc exit data copyout(block2[0:SIZE])
> +#endif
> +  assert (!acc_is_present (block2, SIZE));
> +
> +  free (block1);
> +  free (block2);
> +
> +  return 0;
> +}
-----------------
Mentor Graphics (Deutschland) GmbH, Arnulfstraße 201, 80634 München / Germany
Registergericht München HRB 106955, Geschäftsführer: Thomas Heurung, Alexander Walter
diff mbox series

Patch

diff --git a/libgomp/oacc-mem.c b/libgomp/oacc-mem.c
index 783e7f363fb..f34ffa67079 100644
--- a/libgomp/oacc-mem.c
+++ b/libgomp/oacc-mem.c
@@ -725,6 +725,92 @@  acc_pcopyin (void *h, size_t s)
 #endif
 
 
+/* Perform actions necessary to decrement the dynamic reference counter for
+   splay tree key N.  Returns TRUE on success, or FALSE on failure (e.g. if we
+   hit a case we can't presently handle inside a data region).  */
+
+static bool
+decr_dynamic_refcount (splay_tree_key n, bool finalize)
+{
+  if (finalize)
+    {
+      if (n->refcount != REFCOUNT_INFINITY)
+	n->refcount -= n->virtual_refcount;
+      n->virtual_refcount = 0;
+    }
+
+  if (n->virtual_refcount > 0)
+    {
+      if (n->refcount != REFCOUNT_INFINITY)
+	n->refcount--;
+      n->virtual_refcount--;
+    }
+  /* An initial "enter data" mapping might create a target_mem_desc (in
+     gomp_map_vars_async via goacc_enter_datum or
+     goacc_enter_data_internal).  In that case we have a structural
+     reference count but a zero virtual reference count: we nevertheless
+     want to do the "exit data" operation here.  Detect the special case
+     using a sentinel value stored in the "prev" field, which is otherwise
+     unused for dynamic data mappings.  */
+  else if (n->refcount > 0
+	   && n->refcount != REFCOUNT_INFINITY
+	   && n->tgt->prev == &dyn_tgt_sentinel)
+    {
+      n->refcount--;
+      /* We know n->virtual_refcount is zero here, so if we still have a
+	 non-zero n->refcount we are ending a dynamically-scoped variable
+	 lifetime in the middle of a static lifetime for the same variable.
+	 If we're not careful this results in a dangling reference.  Attempt
+	 to handle this here, if only in simple cases.  E.g.:
+
+	   #pragma acc enter data copyin(var)
+	   #pragma acc data copy(var{, ...})
+	   {
+	     #pragma acc exit data copyout(var)
+	   }
+
+	 Here (the "exit data"), we reattach the relevant fields of the
+	 previously dynamically-scoped target_mem_desc to the static data
+	 region's target_mem_desc, hence merging the former into the latter.
+	 The old dynamic target_mem_desc can then be freed.
+
+	 We can't deal with static data regions that refer to existing dynamic
+	 data mappings or that introduce new static lifetimes of their own.  */
+      if (n->refcount > 0
+	  && n->tgt->list_count == 1
+	  && n->tgt->refcount == 1)
+	{
+	  struct goacc_thread *thr = goacc_thread ();
+	  struct target_mem_desc *tgt, *static_tgt = NULL;
+	  for (tgt = thr->mapped_data;
+	       tgt != NULL && static_tgt == NULL;
+	       tgt = tgt->prev)
+	    for (int j = 0; j < tgt->list_count; j++)
+	      if (tgt->list[j].key == n)
+		{
+		  static_tgt = tgt;
+		  break;
+		}
+	  if (!static_tgt
+	      || static_tgt->to_free != NULL
+	      || static_tgt->array != NULL)
+	    return false;
+	  static_tgt->to_free = n->tgt->to_free;
+	  static_tgt->array = n->tgt->array;
+	  static_tgt->tgt_start = n->tgt->tgt_start;
+	  static_tgt->tgt_end = n->tgt->tgt_end;
+	  static_tgt->to_free = n->tgt->to_free;
+	  static_tgt->refcount++;
+	  free (n->tgt);
+	  n->tgt = static_tgt;
+	}
+      else if (n->refcount > 0)
+	return false;
+    }
+
+  return true;
+}
+
 /* Exit a dynamic mapping for a single variable.  */
 
 static void
@@ -767,29 +853,12 @@  goacc_exit_datum (void *h, size_t s, unsigned short kind, int async)
 
   bool finalize = (kind == GOMP_MAP_DELETE
 		   || kind == GOMP_MAP_FORCE_FROM);
-  if (finalize)
-    {
-      if (n->refcount != REFCOUNT_INFINITY)
-	n->refcount -= n->virtual_refcount;
-      n->virtual_refcount = 0;
-    }
 
-  if (n->virtual_refcount > 0)
+  if (!decr_dynamic_refcount (n, finalize))
     {
-      if (n->refcount != REFCOUNT_INFINITY)
-	n->refcount--;
-      n->virtual_refcount--;
+      gomp_mutex_unlock (&acc_dev->lock);
+      gomp_fatal ("cannot handle delete/copyout within data region");
     }
-  /* An initial "enter data" mapping might create a target_mem_desc (in
-     gomp_map_vars_async via goacc_enter_datum).  In that case we have a
-     structural reference count but a zero virtual reference count: we
-     nevertheless want to do the "exit data" operation here.  Detect the
-     special case using a sentinel value stored in the "prev" field, which is
-     otherwise unused for dynamic data mappings.  */
-  else if (n->refcount > 0
-	   && n->refcount != REFCOUNT_INFINITY
-	   && n->tgt->prev == &dyn_tgt_sentinel)
-    n->refcount--;
 
   if (n->refcount == 0)
     {
@@ -1216,23 +1285,12 @@  goacc_exit_data_internal (struct gomp_device_descr *acc_dev, size_t mapnum,
 	    if (n == NULL)
 	      continue;
 
-	    if (finalize)
-	      {
-		if (n->refcount != REFCOUNT_INFINITY)
-		  n->refcount -= n->virtual_refcount;
-		n->virtual_refcount = 0;
-	      }
-
-	    if (n->virtual_refcount > 0)
+	    if (!decr_dynamic_refcount (n, finalize))
 	      {
-		if (n->refcount != REFCOUNT_INFINITY)
-		  n->refcount--;
-		n->virtual_refcount--;
+		/* The user is trying to do something too tricky for us.  */
+		gomp_mutex_unlock (&acc_dev->lock);
+		gomp_fatal ("cannot handle 'exit data' within data region");
 	      }
-	    else if (n->refcount > 0
-		     && n->refcount != REFCOUNT_INFINITY
-		     && n->tgt->prev == &dyn_tgt_sentinel)
-	      n->refcount--;
 
 	    if (copyfrom
 		&& n->refcount != REFCOUNT_INFINITY
diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1-lib.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1-lib.c
new file mode 100644
index 00000000000..23c20d4fab7
--- /dev/null
+++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1-lib.c
@@ -0,0 +1,3 @@ 
+/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
+/* { dg-additional-options "-DOPENACC_API" } */
+#include "static-dynamic-lifetimes-1.c"
diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1.c
new file mode 100644
index 00000000000..a743660f53e
--- /dev/null
+++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-1.c
@@ -0,0 +1,160 @@ 
+/* Test transitioning of data lifetimes between static and dynamic.  */
+
+/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
+
+#include <openacc.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#define SIZE 1024
+
+void
+f1 (void)
+{
+  char *block1 = (char *) malloc (SIZE);
+
+#ifdef OPENACC_API
+  acc_copyin (block1, SIZE);
+  acc_copyin (block1, SIZE);
+#else
+#pragma acc enter data copyin(block1[0:SIZE])
+#pragma acc enter data copyin(block1[0:SIZE])
+#endif
+
+#pragma acc data copy(block1[0:SIZE])
+  {
+#ifdef OPENACC_API
+    acc_copyin (block1, SIZE);
+#else
+#pragma acc enter data copyin(block1[0:SIZE])
+#endif
+  }
+
+  assert (acc_is_present (block1, SIZE));
+
+#ifdef OPENACC_API
+  acc_copyout (block1, SIZE);
+  assert (acc_is_present (block1, SIZE));
+  acc_copyout (block1, SIZE);
+  assert (acc_is_present (block1, SIZE));
+  acc_copyout (block1, SIZE);
+  assert (!acc_is_present (block1, SIZE));
+#else
+#pragma acc exit data copyout(block1[0:SIZE])
+  assert (acc_is_present (block1, SIZE));
+#pragma acc exit data copyout(block1[0:SIZE])
+  assert (acc_is_present (block1, SIZE));
+#pragma acc exit data copyout(block1[0:SIZE])
+  assert (!acc_is_present (block1, SIZE));
+#endif
+
+  free (block1);
+}
+
+void
+f2 (void)
+{
+  char *block1 = (char *) malloc (SIZE);
+
+#ifdef OPENACC_API
+  acc_copyin (block1, SIZE);
+#else
+#pragma acc enter data copyin(block1[0:SIZE])
+#endif
+
+#pragma acc data copy(block1[0:SIZE])
+  {
+#ifdef OPENACC_API
+    acc_copyout (block1, SIZE);
+#else
+#pragma acc exit data copyout(block1[0:SIZE])
+#endif
+    /* This should stay present until the end of the static data lifetime.  */
+    assert (acc_is_present (block1, SIZE));
+  }
+
+  assert (!acc_is_present (block1, SIZE));
+
+  free (block1);
+}
+
+void
+f3 (void)
+{
+  char *block1 = (char *) malloc (SIZE);
+
+#ifdef OPENACC_API
+  acc_copyin (block1, SIZE);
+#else
+#pragma acc enter data copyin(block1[0:SIZE])
+#endif
+
+#pragma acc data copy(block1[0:SIZE])
+  {
+#ifdef OPENACC_API
+    acc_copyout (block1, SIZE);
+    acc_copyin (block1, SIZE);
+#else
+#pragma acc exit data copyout(block1[0:SIZE])
+#pragma acc enter data copyin(block1[0:SIZE])
+#endif
+    assert (acc_is_present (block1, SIZE));
+  }
+
+  assert (acc_is_present (block1, SIZE));
+#ifdef OPENACC_API
+  acc_copyout (block1, SIZE);
+#else
+#pragma acc exit data copyout(block1[0:SIZE])
+#endif
+  assert (!acc_is_present (block1, SIZE));
+
+  free (block1);
+}
+
+void
+f4 (void)
+{
+  char *block1 = (char *) malloc (SIZE);
+  char *block2 = (char *) malloc (SIZE);
+  char *block3 = (char *) malloc (SIZE);
+
+#pragma acc data copy(block1[0:SIZE], block2[0:SIZE], block3[0:SIZE])
+  {
+  /* The first copyin of block2 is the enclosing data region.  This
+     "enter data" should make it live beyond the end of this region.
+     This works, though the on-target copies of block1, block2 and block3
+     will stay allocated until block2 is unmapped because they are bound
+     together in a single target_mem_desc.  */
+#ifdef OPENACC_API
+    acc_copyin (block2, SIZE);
+#else
+#pragma acc enter data copyin(block2[0:SIZE])
+#endif
+  }
+
+  assert (!acc_is_present (block1, SIZE));
+  assert (acc_is_present (block2, SIZE));
+  assert (!acc_is_present (block3, SIZE));
+
+#ifdef OPENACC_API
+  acc_copyout (block2, SIZE);
+#else
+#pragma acc exit data copyout(block2[0:SIZE])
+#endif
+  assert (!acc_is_present (block2, SIZE));
+
+  free (block1);
+  free (block2);
+  free (block3);
+}
+
+int
+main (int argc, char *argv[])
+{
+  f1 ();
+  f2 ();
+  f3 ();
+  f4 ();
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6-lib.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6-lib.c
new file mode 100644
index 00000000000..8507a0586a5
--- /dev/null
+++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6-lib.c
@@ -0,0 +1,5 @@ 
+/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
+/* { dg-additional-options "-DOPENACC_API" } */
+#include "static-dynamic-lifetimes-6.c"
+/* { dg-output "libgomp: cannot handle delete/copyout within data region" } */
+/* { dg-shouldfail "" } */
diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6.c
new file mode 100644
index 00000000000..ca3b385fbcc
--- /dev/null
+++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-6.c
@@ -0,0 +1,46 @@ 
+/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
+
+#include <openacc.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#define SIZE 1024
+
+int
+main (int argc, char *argv[])
+{
+  char *block1 = (char *) malloc (SIZE);
+  char *block2 = (char *) malloc (SIZE);
+
+#ifdef OPENACC_API
+  acc_copyin (block1, SIZE);
+  acc_copyin (block2, SIZE);
+#else
+#pragma acc enter data copyin(block1[0:SIZE], block2[0:SIZE])
+#endif
+
+#pragma acc data copy(block1[0:SIZE], block2[0:SIZE])
+  {
+#ifdef OPENACC_API
+    acc_copyout (block1, SIZE);
+    acc_copyout (block2, SIZE);
+    /* Error output checked in static-dynamic-lifetimes-6-lib.c.  */
+#else
+#pragma acc exit data copyout(block1[0:SIZE], block2[0:SIZE])
+/* We can only do this for a single dynamic data mapping at present.  */
+/* { dg-output "libgomp: cannot handle .exit data. within data region" } */
+/* { dg-shouldfail "" } */
+#endif
+    /* These should stay present until the end of the static data lifetime.  */
+    assert (acc_is_present (block1, SIZE));
+    assert (acc_is_present (block2, SIZE));
+  }
+
+  assert (!acc_is_present (block1, SIZE));
+  assert (!acc_is_present (block2, SIZE));
+
+  free (block1);
+  free (block2);
+
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7-lib.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7-lib.c
new file mode 100644
index 00000000000..962b5926f79
--- /dev/null
+++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7-lib.c
@@ -0,0 +1,5 @@ 
+/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
+/* { dg-additional-options "-DOPENACC_API" } */
+#include "static-dynamic-lifetimes-7.c"
+/* { dg-output "libgomp: cannot handle delete/copyout within data region" } */
+/* { dg-shouldfail "" } */
diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7.c
new file mode 100644
index 00000000000..dfcc7cae961
--- /dev/null
+++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-7.c
@@ -0,0 +1,45 @@ 
+/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
+
+#include <openacc.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#define SIZE 1024
+
+int
+main (int argc, char *argv[])
+{
+  char *block1 = (char *) malloc (SIZE);
+  char *block2 = (char *) malloc (SIZE);
+
+#ifdef OPENACC_API
+  acc_copyin (block1, SIZE);
+#else
+#pragma acc enter data copyin(block1[0:SIZE])
+#endif
+
+#pragma acc data copy(block1[0:SIZE], block2[0:SIZE])
+  {
+/* We can't attach the dynamic data mapping's (block1) target_mem_desc to the
+   enclosing static data region here, because that region maps block2 also.  */
+#ifdef OPENACC_API
+    acc_copyout (block1, SIZE);
+    /* Error output checked in static-dynamic-lifetimes-7-lib.c.  */
+#else
+#pragma acc exit data copyout(block1[0:SIZE])
+/* { dg-output "libgomp: cannot handle .exit data. within data region" } */
+/* { dg-shouldfail "" } */
+#endif
+    /* These should stay present until the end of the static data lifetime.  */
+    assert (acc_is_present (block1, SIZE));
+    assert (acc_is_present (block2, SIZE));
+  }
+
+  assert (!acc_is_present (block1, SIZE));
+  assert (!acc_is_present (block2, SIZE));
+
+  free (block1);
+  free (block2);
+
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8-lib.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8-lib.c
new file mode 100644
index 00000000000..2581d7e2559
--- /dev/null
+++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8-lib.c
@@ -0,0 +1,5 @@ 
+/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
+/* { dg-additional-options "-DOPENACC_API" } */
+#include "static-dynamic-lifetimes-8.c"
+/* { dg-output "libgomp: cannot handle delete/copyout within data region" } */
+/* { dg-shouldfail "" } */
diff --git a/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8.c b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8.c
new file mode 100644
index 00000000000..e3a64399fe9
--- /dev/null
+++ b/libgomp/testsuite/libgomp.oacc-c-c++-common/static-dynamic-lifetimes-8.c
@@ -0,0 +1,50 @@ 
+/* { dg-skip-if "" { *-*-* } { "-DACC_MEM_SHARED=1" } } */
+
+#include <openacc.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#define SIZE 1024
+
+int
+main (int argc, char *argv[])
+{
+  char *block1 = (char *) malloc (SIZE);
+  char *block2 = (char *) malloc (SIZE);
+
+#ifdef OPENACC_API
+  acc_copyin (block1, SIZE);
+#else
+#pragma acc enter data copyin(block1[0:SIZE])
+#endif
+
+#pragma acc data copy(block1[0:SIZE], block2[0:SIZE])
+  {
+#ifdef OPENACC_API
+    acc_copyout (block1, SIZE);
+    acc_copyin (block2, SIZE);
+    /* Error output checked in static-dynamic-lifetimes-8-lib.c.  */
+#else
+#pragma acc exit data copyout(block1[0:SIZE])
+/* { dg-output "libgomp: cannot handle .exit data. within data region" } */
+/* { dg-shouldfail "" } */
+#pragma acc enter data copyin(block2[0:SIZE])
+#endif
+    assert (acc_is_present (block1, SIZE));
+    assert (acc_is_present (block2, SIZE));
+  }
+
+  assert (!acc_is_present (block1, SIZE));
+  assert (acc_is_present (block2, SIZE));
+#ifdef OPENACC_API
+  acc_copyout (block2, SIZE);
+#else
+#pragma acc exit data copyout(block2[0:SIZE])
+#endif
+  assert (!acc_is_present (block2, SIZE));
+
+  free (block1);
+  free (block2);
+
+  return 0;
+}