https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112667

            Bug ID: 112667
           Summary: [OpenMP] C++: Handle static local variable in target
                    regions
           Product: gcc
           Version: 14.0
            Status: UNCONFIRMED
          Keywords: openmp, rejects-valid
          Severity: normal
          Priority: P3
         Component: middle-end
          Assignee: unassigned at gcc dot gnu.org
          Reporter: burnus at gcc dot gnu.org
                CC: jakub at gcc dot gnu.org, tschwinge at gcc dot gnu.org
  Target Milestone: ---

Cross ref, for initialization of static global C++ variables, see:

[PATCH] OpenMP: Constructors and destructors for "declare target" static
aggregates
https://gcc.gnu.org/pipermail/gcc-patches/2023-May/618340.html 

* * *

Based on Thomas' email to
https://mailman.openmp.org/mailman/private/omp-lang/2023/018626.html

Assume 'declare target' as needed:

    struct S
    {
      S() { }
      ~S() { }
    };

    static void f()
    {
      static S s;
    }

    int main()
    {
    #pragma omp target
      {
        f();
      }
    }

This fails in GCC as:

"error: variable ‘_ZGVZL1fvE1s’ has been referenced in offloaded code but
hasn’t been marked to be included in the offloaded code"

where "c++filt _ZGVZL1fvE1s" prints: "guard variable for f()::s"

 * * *

Regarding the validity, Tom replied:

It is valid OpenMP offload code, we have specific language to handle the 
initialization as well actually.  We don't explicitly state that the 
initialization should be protected, but it falls through from the base 
language rules.  For cases where the static has a separate corresponding 
instance on the device, I would expect it to have its own guard variable 
on the device (probably _each_ device actually) as well. For cases with 
unified shared memory, or generally where the static is using the same 
storage as the original, I would expect the guard to use node scope 
rather than device scope.

The challenging question is whether the offload device is allowed to 
actually do the initialization in this case (or language says each 
device initializes its own instance, but this is control flow dependent 
now).  In an ideal world it would probably be something like a reverse 
offload that does it for the unified case, but I'm pretty sure following 
the requirements through would say "whichever thread on whichever device 
gets there first" is the one that does it.

[And continued:]

[...]we have some language about how 
initialization happens.  I can try to dig it out if you like but 
essentially it boils down to this:

* static lifetime variables at global/class scope are initialized before 
code runs in the same TU, by each device which has a separate instance
* at function scope initialized when first encountered

* * *

Jakub remarked:

I believe we should in the omp_discover_* sub-pass handle with
a help of a langhook automatically mark the guard variables (possibly
iff the guarded variable is marked?), or e.g. rtti info (_ZTS*, _ZTI*)
and eventually figure out what we should do about virtual tables (_ZTV*).
The last case is most complicated, as it contains function pointers, and we
need to figure out if we mark all methods, or say replace some pointers in
the virtual table with NULLs or something that errors or terminates if it
isn't marked.

And sure, __cxa_guard_* would need to be implemented in the offloading
libsupc++.a or libstdc++.a.

* * *

Side remark regarding virtual tables: OpenMP since 5.2 has in "13.8 target
Construct" [287:10-12]:

"[C++] Invoking a virtual member function of an object on a device other than
the device on which the object was constructed results in unspecified behavior,
unless the object is accessible and was constructed on the host device."

Thanks to OpenMP 5.1's 'indirect' we already have a means to lookup on the
device the function pointers on the host. (Implicitly assumes unified_address,
which is the case of all offload devices in GCC.)

Reply via email to