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.)