[Mlir-commits] [flang] [mlir] [flang][OpenMP] Diagnose threadprivate accesses in target device code (PR #237949)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon Nov 2 04:50:06 PST 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir-llvm

@llvm/pr-subscribers-flang-openmp

Author: Michael Klemm (mjklemm)

<details>
<summary>Changes</summary>

The effect of an access to a threadprivate variable in a target region is unspecified. Flang did not diagnose such accesses. Instead, lowering failed MLIR verification, or device code generation crashed.

- Semantics: add OmpDeviceChecker, which reports accesses to threadprivate variables in TARGET regions and DECLARE TARGET procedures. When compiling for a target device, it also reports accesses in procedures that are implicitly DECLARE TARGET because they are called from device code.
- Add the omp-device-threadprivate-check FIR pass as a backstop. It also removes unused omp.threadprivate operations from device code.
- Report an error instead of crashing when translating omp.threadprivate for a target device.
- Lowering: bind threadprivate variables inside the omp.loop_nest of composite DISTRIBUTE PARALLEL DO [SIMD]. This fixes valid host code, e.g. TEAMS DISTRIBUTE PARALLEL DO.

Fixes: LCOMPILER-3073, LCOMPILER-2751

---

Patch is 55.13 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/237949.diff


18 Files Affected:

- (modified) flang/include/flang/Optimizer/OpenMP/Passes.td (+20) 
- (modified) flang/lib/Lower/OpenMP/OpenMP.cpp (+22-5) 
- (modified) flang/lib/Optimizer/OpenMP/CMakeLists.txt (+1) 
- (added) flang/lib/Optimizer/OpenMP/DeviceThreadprivateCheck.cpp (+172) 
- (modified) flang/lib/Optimizer/Passes/Pipelines.cpp (+1) 
- (modified) flang/lib/Semantics/CMakeLists.txt (+1) 
- (added) flang/lib/Semantics/check-omp-device.cpp (+401) 
- (added) flang/lib/Semantics/check-omp-device.h (+140) 
- (modified) flang/lib/Semantics/semantics.cpp (+2) 
- (modified) flang/test/Fir/basic-program.fir (+1) 
- (modified) flang/test/Integration/OpenMP/threadprivate-target-device.f90 (+6-7) 
- (added) flang/test/Integration/OpenMP/threadprivate-unused-in-target-device.f90 (+45) 
- (added) flang/test/Lower/OpenMP/threadprivate-distribute-parallel-do.f90 (+48) 
- (added) flang/test/Semantics/OpenMP/threadprivate-target-implicit.f90 (+124) 
- (added) flang/test/Semantics/OpenMP/threadprivate-target.f90 (+157) 
- (added) flang/test/Transforms/omp-device-threadprivate-check.fir (+96) 
- (modified) mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp (+9) 
- (modified) mlir/test/Target/LLVMIR/openmp-llvm-invalid.mlir (+13) 


``````````diff
diff --git a/flang/include/flang/Optimizer/OpenMP/Passes.td b/flang/include/flang/Optimizer/OpenMP/Passes.td
index 08272d24252ee5..77b4fea3b296cd 100644
--- a/flang/include/flang/Optimizer/OpenMP/Passes.td
+++ b/flang/include/flang/Optimizer/OpenMP/Passes.td
@@ -117,6 +117,26 @@ def AutomapToTargetDataPass
   let dependentDialects = ["mlir::omp::OpenMPDialect"];
 }
 
+def DeviceThreadprivateCheckPass
+    : Pass<"omp-device-threadprivate-check", "::mlir::ModuleOp"> {
+  let summary = "Diagnose threadprivate variable accesses in target device "
+                "code";
+  let description = [{
+    Accessing a threadprivate variable in target device code is not supported.
+    Semantics diagnoses accesses in target regions and in procedures that are
+    explicitly or implicitly declare target, based on the calls it can see.
+    This pass is a backstop for accesses in procedures that only become
+    implicitly declare target through calls that are not visible to
+    semantics, e.g. calls generated during lowering. It must run after
+    implicit `declare_target` information has been propagated.
+
+    Threadprivate operations of variables that are not accessed in target
+    device code, e.g. variables of a used module, are removed from it.
+
+    The pass only has an effect when compiling for a target device.
+  }];
+}
+
 def UnimplementedDeviceCheckPass
     : Pass<"omp-unimplemented-device-check", "::mlir::ModuleOp"> {
   let summary = "Report not-yet-implemented situations on target device code";
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index 3ce793989dee30..4be9464d8d3018 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -1138,12 +1138,17 @@ static bool isThreadPrivate(lower::SymbolRef sym) {
 }
 #endif
 
+/// Create omp.threadprivate operations for all threadprivate symbols referenced
+/// in \p eval and rebind the symbols to them. The operations are created at
+/// the start of \p insertBlock or, if not given, of the current alloca block.
 static void threadPrivatizeVars(lower::AbstractConverter &converter,
-                                lower::pft::Evaluation &eval) {
+                                lower::pft::Evaluation &eval,
+                                mlir::Block *insertBlock = nullptr) {
   fir::FirOpBuilder &firOpBuilder = converter.getFirOpBuilder();
   mlir::Location currentLocation = converter.getCurrentLocation();
   mlir::OpBuilder::InsertionGuard guard(firOpBuilder);
-  firOpBuilder.setInsertionPointToStart(firOpBuilder.getAllocaBlock());
+  firOpBuilder.setInsertionPointToStart(
+      insertBlock ? insertBlock : firOpBuilder.getAllocaBlock());
 
   // If the symbol corresponds to the original ThreadprivateOp, use the symbol
   // value from that operation to create one ThreadprivateOp copy operation
@@ -1929,7 +1934,10 @@ static void createBodyOfOp(mlir::Operation &op, const OpWithBodyGenInfo &info,
   }
 
   if (info.dir == llvm::omp::Directive::OMPD_parallel) {
-    threadPrivatizeVars(info.converter, info.eval);
+    // A composite omp.parallel may only contain its nested omp.distribute.
+    // The threadprivate operations are created inside the loop nest instead.
+    if (!info.genSkeletonOnly)
+      threadPrivatizeVars(info.converter, info.eval);
     if (info.clauses) {
       firOpBuilder.setInsertionPoint(marker);
       ClauseProcessor(info.converter, info.semaCtx, *info.clauses)
@@ -5337,7 +5345,13 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDo(
   genLoopNestOp(converter, symTable, semaCtx, eval, loc, queue, doItem,
                 loopNestClauseOps, iv,
                 {{distributeOp, distributeArgs}, {wsloopOp, wsloopArgs}},
-                llvm::omp::Directive::OMPD_distribute_parallel_do, dsp);
+                llvm::omp::Directive::OMPD_distribute_parallel_do, dsp,
+                [&](mlir::Operation *loopNestOp) {
+                  // Threadprivate variables cannot be bound in the composite
+                  // omp.parallel, so do it at the start of the loop body.
+                  threadPrivatizeVars(converter, eval,
+                                      &loopNestOp->getRegion(0).front());
+                });
 
   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
   fir::FirOpBuilder::InsertionGuard guard(builder);
@@ -5463,7 +5477,10 @@ static mlir::omp::DistributeOp genCompositeDistributeParallelDoSimd(
                  {wsloopOp, wsloopArgs},
                  {simdOp, simdArgs}},
                 llvm::omp::Directive::OMPD_distribute_parallel_do_simd,
-                simdItemDSP);
+                simdItemDSP, [&](mlir::Operation *loopNestOp) {
+                  threadPrivatizeVars(converter, eval,
+                                      &loopNestOp->getRegion(0).front());
+                });
 
   fir::FirOpBuilder &builder = converter.getFirOpBuilder();
   fir::FirOpBuilder::InsertionGuard guard(builder);
diff --git a/flang/lib/Optimizer/OpenMP/CMakeLists.txt b/flang/lib/Optimizer/OpenMP/CMakeLists.txt
index 4cc7625bb07f3f..f39f5b22490213 100644
--- a/flang/lib/Optimizer/OpenMP/CMakeLists.txt
+++ b/flang/lib/Optimizer/OpenMP/CMakeLists.txt
@@ -2,6 +2,7 @@ get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)
 
 add_flang_library(FlangOpenMPTransforms
   AutomapToTargetData.cpp
+  DeviceThreadprivateCheck.cpp
   DoConcurrentConversion.cpp
   GenericLoopConversion.cpp
   MapsForPrivatizedSymbols.cpp
diff --git a/flang/lib/Optimizer/OpenMP/DeviceThreadprivateCheck.cpp b/flang/lib/Optimizer/OpenMP/DeviceThreadprivateCheck.cpp
new file mode 100644
index 00000000000000..a559bd1ab63d72
--- /dev/null
+++ b/flang/lib/Optimizer/OpenMP/DeviceThreadprivateCheck.cpp
@@ -0,0 +1,172 @@
+//===- DeviceThreadprivateCheck.cpp ---------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Diagnose accesses to threadprivate variables in target device code and
+// remove the threadprivate operations of unaccessed variables from it.
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Optimizer/OpenMP/Passes.h"
+
+#include "flang/Optimizer/Dialect/FIROps.h"
+#include "flang/Optimizer/Dialect/FortranVariableInterface.h"
+#include "flang/Optimizer/HLFIR/HLFIROps.h"
+#include "flang/Optimizer/Support/InternalNames.h"
+#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "llvm/ADT/SetVector.h"
+
+using namespace mlir;
+
+namespace flangomp {
+#define GEN_PASS_DEF_DEVICETHREADPRIVATECHECKPASS
+#include "flang/Optimizer/OpenMP/Passes.h.inc"
+} // namespace flangomp
+
+/// Check whether the given operation is part of target device code, i.e. it is
+/// inside of an omp.target region or of a function that is declare target for
+/// the device.
+static bool isDeviceCode(Operation *op) {
+  if (op->getParentOfType<omp::TargetOp>())
+    return true;
+
+  if (auto declareTargetOp = op->getParentOfType<omp::DeclareTargetInterface>())
+    if (omp::DeclareTargetAttr declareTargetAttr =
+            declareTargetOp.getDeclareTarget())
+      return declareTargetAttr.getDeviceType() !=
+             omp::DeclareTargetDeviceType::host;
+
+  return false;
+}
+
+/// Operations that only compute an address or a descriptor from their operands
+/// without accessing the memory behind them.
+static bool isAddressComputation(Operation *op) {
+  return isa<fir::FortranVariableOpInterface, fir::CoordinateOp,
+             fir::ConvertOp, fir::EmboxOp, fir::ReboxOp, fir::BoxAddrOp,
+             fir::ArrayCoorOp, hlfir::DesignateOp>(op);
+}
+
+/// Follow the uses of \p value through address computations, which are added
+/// to \p addressOps. Return the first operation accessing the memory, if any.
+static Operation *findAccess(Value value,
+                             llvm::SetVector<Operation *> &addressOps) {
+  Operation *access = nullptr;
+  for (Operation *user : value.getUsers()) {
+    if (!isAddressComputation(user)) {
+      // Keep the access that comes first in the block, if possible.
+      if (!access || (user->getBlock() == access->getBlock() &&
+                      user->isBeforeInBlock(access)))
+        access = user;
+      continue;
+    }
+    if (!addressOps.insert(user))
+      continue;
+    for (Value result : user->getResults())
+      if (Operation *resultAccess = findAccess(result, addressOps))
+        if (!access || (resultAccess->getBlock() == access->getBlock() &&
+                        resultAccess->isBeforeInBlock(access)))
+          access = resultAccess;
+  }
+  return access;
+}
+
+/// Return the global holding the threadprivate variable of \p op, if found.
+static fir::GlobalOp getThreadprivateGlobal(omp::ThreadprivateOp op) {
+  Value addr = op.getSymAddr();
+  while (Operation *defOp = addr.getDefiningOp()) {
+    if (auto addrOf = dyn_cast<fir::AddrOfOp>(defOp))
+      return SymbolTable::lookupNearestSymbolFrom<fir::GlobalOp>(
+          op, addrOf.getSymbol());
+    if (auto varOp = dyn_cast<fir::FortranVariableOpInterface>(defOp))
+      addr = varOp.getBase();
+    else if (auto convert = dyn_cast<fir::ConvertOp>(defOp))
+      addr = convert.getValue();
+    else
+      break;
+  }
+  return nullptr;
+}
+
+/// Describe the threadprivate variable stored in \p global for a diagnostic.
+static std::string describeThreadprivate(fir::GlobalOp global) {
+  if (!global)
+    return "a THREADPRIVATE variable";
+
+  llvm::StringRef symName = global.getSymName();
+  if (global.getLinkage() == fir::LinkageEnum::Common) {
+    // Common block names are mangled like external procedure names.
+    llvm::StringRef name = symName;
+    if (name == "__BLNK__")
+      name = "";
+    else
+      name.consume_back("_");
+    return ("THREADPRIVATE common block '/" + name + "/'").str();
+  }
+
+  auto [kind, deconstructed] = fir::NameUniquer::deconstruct(symName);
+  llvm::StringRef name =
+      kind == fir::NameUniquer::NameKind::VARIABLE ? deconstructed.name
+                                                   : symName;
+  return ("THREADPRIVATE variable '" + name + "'").str();
+}
+
+namespace {
+class DeviceThreadprivateCheckPass
+    : public flangomp::impl::DeviceThreadprivateCheckPassBase<
+          DeviceThreadprivateCheckPass> {
+
+  void runOnOperation() override {
+    // Only run checks when compiling for a target device.
+    auto offloadModule = dyn_cast<omp::OffloadModuleInterface>(*getOperation());
+    if (!offloadModule || !offloadModule.getIsTargetDevice())
+      return;
+
+    llvm::SmallVector<omp::ThreadprivateOp> threadprivateOps;
+    getOperation()->walk([&](omp::ThreadprivateOp op) {
+      if (isDeviceCode(op))
+        threadprivateOps.push_back(op);
+    });
+
+    bool errorsEmitted = false;
+    for (omp::ThreadprivateOp op : threadprivateOps) {
+      llvm::SetVector<Operation *> addressOps;
+      if (Operation *access = findAccess(op.getTlsAddr(), addressOps)) {
+        fir::GlobalOp global = getThreadprivateGlobal(op);
+        InFlightDiagnostic diag = access->emitError()
+                                  << describeThreadprivate(global)
+                                  << " cannot be accessed in target device "
+                                     "code";
+        if (global)
+          diag.attachNote(global.getLoc()) << "declared here";
+        errorsEmitted = true;
+        continue;
+      }
+
+      // The variable is declared, but never accessed. Remove the threadprivate
+      // operation, which cannot be translated for the device, together with
+      // the address computations depending on it. All users of these address
+      // computations are in the set, so there is always one without remaining
+      // uses that can be erased.
+      llvm::SmallVector<Operation *> pending(addressOps.begin(),
+                                             addressOps.end());
+      while (!pending.empty()) {
+        auto *unused = llvm::find_if(
+            pending, [](Operation *addressOp) { return addressOp->use_empty(); });
+        assert(unused != pending.end() && "expected an unused operation");
+        (*unused)->erase();
+        pending.erase(unused);
+      }
+      op->erase();
+    }
+
+    if (errorsEmitted)
+      signalPassFailure();
+  }
+};
+} // namespace
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index 1608d29d2db6e2..8f9458175a267c 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -483,6 +483,7 @@ void createDefaultFIRCodeGenPassPipeline(mlir::PassManager &pm,
     // Propagate implicit declare target information early in order to diagnose
     // target device not-yet-implemented cases based on FIR.
     pm.addPass(mlir::omp::createMarkDeclareTargetPass());
+    pm.addPass(flangomp::createDeviceThreadprivateCheckPass());
     pm.addPass(flangomp::createUnimplementedDeviceCheckPass());
   }
 
diff --git a/flang/lib/Semantics/CMakeLists.txt b/flang/lib/Semantics/CMakeLists.txt
index 6fc45bdb449d96..d12830e52ca2eb 100644
--- a/flang/lib/Semantics/CMakeLists.txt
+++ b/flang/lib/Semantics/CMakeLists.txt
@@ -21,6 +21,7 @@ add_flang_library(FortranSemantics
   check-namelist.cpp
   check-nullify.cpp
   check-omp-atomic.cpp
+  check-omp-device.cpp
   check-omp-loop.cpp
   check-omp-variant.cpp
   check-omp-structure.cpp
diff --git a/flang/lib/Semantics/check-omp-device.cpp b/flang/lib/Semantics/check-omp-device.cpp
new file mode 100644
index 00000000000000..c2bdd0dc93df59
--- /dev/null
+++ b/flang/lib/Semantics/check-omp-device.cpp
@@ -0,0 +1,401 @@
+//===-- lib/Semantics/check-omp-device.cpp --------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "check-omp-device.h"
+
+#include "flang/Evaluate/tools.h"
+#include "flang/Evaluate/traverse.h"
+#include "flang/Parser/parse-tree.h"
+#include "flang/Semantics/openmp-directive-sets.h"
+#include "flang/Semantics/symbol.h"
+#include "flang/Semantics/tools.h"
+
+#include <deque>
+#include <string>
+
+namespace Fortran::semantics {
+
+// Collects the procedures called in an expression. Procedures passed as
+// actual arguments are not called by the expression, so they are skipped.
+struct CalleeCollector
+    : public evaluate::Traverse<CalleeCollector, std::vector<const Symbol *>> {
+  using Result = std::vector<const Symbol *>;
+  using Base = evaluate::Traverse<CalleeCollector, Result>;
+  using Base::operator();
+  CalleeCollector() : Base{*this} {}
+  Result Default() { return {}; }
+  Result Combine(Result &&a, Result &&b) {
+    a.insert(a.end(), b.begin(), b.end());
+    return std::move(a);
+  }
+  Result operator()(const evaluate::ProcedureDesignator &) const { return {}; }
+  Result operator()(const evaluate::ProcedureRef &x) const {
+    Result result{(*this)(x.arguments())};
+    if (const Symbol *callee{x.proc().GetSymbol()}) {
+      result.push_back(callee);
+    }
+    return result;
+  }
+};
+
+static bool IsThreadprivate(const Symbol &symbol) {
+  if (const auto *details{symbol.detailsIf<CommonBlockDetails>()}) {
+    return llvm::any_of(details->objects(), [](const MutableSymbolRef &object) {
+      return object->GetUltimate().test(Symbol::Flag::OmpThreadprivate);
+    });
+  }
+  return symbol.test(Symbol::Flag::OmpThreadprivate);
+}
+
+static std::string GetVariableName(const Symbol &symbol) {
+  if (symbol.has<CommonBlockDetails>()) {
+    return "/" + symbol.name().ToString() + "/";
+  }
+  return symbol.name().ToString();
+}
+
+OmpDeviceChecker::OmpDeviceChecker(SemanticsContext &context)
+    : context_{context},
+      isTargetDevice_{context.langOptions().OpenMPIsTargetDevice} {}
+
+void OmpDeviceChecker::Leave(const parser::Program &) {
+  if (isTargetDevice_) {
+    CheckImplicitDeclareTarget();
+  }
+}
+
+void OmpDeviceChecker::Enter(const parser::SubroutineSubprogram &x) {
+  const auto &stmt{std::get<parser::Statement<parser::SubroutineStmt>>(x.t)};
+  EnterProcedure(std::get<parser::Name>(stmt.statement.t).symbol);
+}
+
+void OmpDeviceChecker::Leave(const parser::SubroutineSubprogram &) {
+  LeaveProcedure();
+}
+
+void OmpDeviceChecker::Enter(const parser::FunctionSubprogram &x) {
+  const auto &stmt{std::get<parser::Statement<parser::FunctionStmt>>(x.t)};
+  EnterProcedure(std::get<parser::Name>(stmt.statement.t).symbol);
+}
+
+void OmpDeviceChecker::Leave(const parser::FunctionSubprogram &) {
+  LeaveProcedure();
+}
+
+void OmpDeviceChecker::Enter(const parser::SeparateModuleSubprogram &x) {
+  const auto &stmt{std::get<parser::Statement<parser::MpSubprogramStmt>>(x.t)};
+  EnterProcedure(stmt.statement.v.symbol);
+}
+
+void OmpDeviceChecker::Leave(const parser::SeparateModuleSubprogram &) {
+  LeaveProcedure();
+}
+
+void OmpDeviceChecker::Enter(const parser::ExecutionPart &) {
+  inExecutionPart_ = true;
+}
+
+void OmpDeviceChecker::Leave(const parser::ExecutionPart &) {
+  inExecutionPart_ = false;
+}
+
+void OmpDeviceChecker::Enter(const parser::EntryStmt &x) {
+  const Symbol *procedure{CurrentProcedure()};
+  const Symbol *entry{std::get<parser::Name>(x.t).symbol};
+  if (procedure && entry) {
+    aliases_[entry] = procedure;
+  }
+}
+
+void OmpDeviceChecker::Leave(const parser::OmpBeginDirective &x) {
+  // The body of a TARGET construct is device code, but its clauses are
+  // evaluated by the encountering task.
+  if (llvm::omp::allTargetSet.test(x.DirId())) {
+    targets_.push_back(&x);
+  }
+}
+
+void OmpDeviceChecker::Leave(const parser::OmpBlockConstruct &x) {
+  LeaveConstruct(x.BeginDir());
+}
+
+void OmpDeviceChecker::Leave(const parser::OpenMPLoopConstruct &x) {
+  LeaveConstruct(x.BeginDir());
+}
+
+void OmpDeviceChecker::Enter(const parser::OmpClause &x) {
+  clauses_.push_back(x.Id());
+}
+
+void OmpDeviceChecker::Leave(const parser::OmpClause &) { clauses_.pop_back(); }
+
+void OmpDeviceChecker::Enter(const parser::OmpObjectList &) {
+  // THREADPRIVATE variables in clauses are diagnosed by the clause checks,
+  // except in the clauses that allow them. Those clauses access them.
+  bool skip{clauses_.empty() ||
+      (clauses_.back() != llvm::omp::Clause::OMPC_copyin &&
+          clauses_.back() != llvm::omp::Clause::OMPC_copyprivate)};
+  skippedObjectLists_.push_back(skip);
+  skipDepth_ += skip;
+}
+
+void OmpDeviceChecker::Leave(const parser::OmpObjectList &) {
+  skipDepth_ -= skippedObjectLists_.back();
+  skippedObjectLists_.pop_back();
+}
+
+// Objects in directive arguments (e.g. FLUSH) are not checked.
+void OmpDeviceChecker::Enter(const parser::OmpArgumentList &) { ++skipDepth_; }
+
+void OmpDeviceChecker::Leave(const parser::OmpArgumentList &) { --skipDepth_; }
+
+// DATA statements initialize variables before execution starts.
+void OmpDeviceChecker::Enter(const parser::DataStmt &) { ++skipDepth_; }
+
+void OmpDeviceChecker::Leave(const parser::DataStmt &) { --skipDepth_; }
+
+void OmpDeviceChecker::Enter(const parser::Name &x) {
+  if (inExecutionPart_ && skipDepth_ == 0 && x.symbol) {
+    CheckReference(x.source, *x.symbol);
+  }
+}
+
+void OmpDeviceChecker::Enter(const parser::Expr &x) {
+  // The analyzed outermost expression contains all calls.
+  if (exprDepth_++ == 0 && inExecutionPart_ && skipDepth_ == 0) {
+    if (const SomeExpr *expr{GetExpr(context_, x)}) {
+      RecordCalls(x.source, *expr);
+    }
+  }
+}
+
+void OmpDeviceChecker::Leave(const parser::Expr &) { --exprDepth_; }
+
+void OmpDeviceChecker::Enter(const parser::CallStmt &x) {
+  if (inExecutionPart_ && x.typedCall) {
+    if (const Symbol *callee{x.typedCall->proc().GetSymbol()}) {
+      RecordCall(x.source, *callee);
+    }
+  }
+}
+
+void OmpDeviceChecker::Enter(const parser::AssignmentStmt &x) {
+  // Defined assignment
+  if (inExecutionPart_) {
+    if (const auto *assignment{GetAssignment(x)}) {
+      if (const auto *call{
+              std::get_if<evaluate::ProcedureRef>(&assignment->u)}) {
+        if (...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/237949


More information about the Mlir-commits mailing list