[llvm-branch-commits] [flang] [flang][OpenMP] Track reachable metadirective replacements (PR #219014)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Sep 23 19:05:27 PDT 2026


================
@@ -763,46 +966,112 @@ void OmpStructureChecker::Enter(const parser::OmpDirectiveSpecification &x) {
 
 void OmpStructureChecker::Leave(const parser::OmpDirectiveSpecification &x) {
   if (GetDirectiveNest(MetadirectiveNest) || GetDirectiveNest(ApplyNest)) {
+    CHECK(!directiveSpecificationReachability_.empty());
+    directiveSpecificationReachability_.pop_back();
     dirContext_.pop_back();
   }
 }
 
 void OmpStructureChecker::Enter(const parser::OmpMetadirectiveDirective &x) {
+  auto branches{GetReachableMetadirectiveReplacements(x.v.Clauses())};
+  pendingLoopDirectiveGroups_.push_back(
+      {std::move(branches), /*isStandaloneMetadirective=*/true});
   EnterDirectiveNest(MetadirectiveNest);
 }
 
 void OmpStructureChecker::Leave(const parser::OmpMetadirectiveDirective &) {
   ExitDirectiveNest(MetadirectiveNest);
 }
 
+void OmpStructureChecker::Enter(
+    const parser::OmpDelimitedMetadirectiveDirective &x) {
+  auto branches{GetReachableMetadirectiveReplacements(x.BeginDir().Clauses())};
+  llvm::SmallVector<EffectiveDirectivePath, 4> paths;
+  for (const MetadirectiveReplacementBranch &branch : branches) {
+    EffectiveDirectivePath path{branch.enclosingPath};
+    if (branch.spec) {
+      path.insert(path.begin(), branch.spec->DirId());
+    }
+    paths.push_back(std::move(path));
+  }
+  paths = GetUniqueEffectiveDirectivePaths(std::move(paths));
+  activeMetadirectiveReplacements_.push_back(
+      {dirContext_.size(), std::move(paths)});
+  pendingLoopDirectiveGroups_.push_back({std::move(branches)});
+}
+
+void OmpStructureChecker::Leave(
+    const parser::OmpDelimitedMetadirectiveDirective &) {
+  CHECK(!activeMetadirectiveReplacements_.empty());
+  activeMetadirectiveReplacements_.pop_back();
+}
+
 // Check a loop-associated metadirective's variants against the loop nest they
 // apply to. The nest is not attached to the directive in the parse tree. It is
 // the next executable construct, either a following sibling or the first
 // execution-part construct for a declarative metadirective.
 void OmpStructureChecker::Enter(const parser::ExecutionPartConstruct &x) {
-  if (metadirectiveLoopVariants_.empty()) {
+  executionPartReplacementDepths_.push_back(
+      activeMetadirectiveReplacements_.size());
+  if (pendingLoopDirectiveGroups_.empty()) {
     return;
   }
   if (parser::Unwrap<parser::CompilerDirective>(x)) {
     return;
   }
+
+  const parser::DoConstruct *rootLoop{parser::Unwrap<parser::DoConstruct>(x)};
+  bool isStrictlyStructuredBlock{
+      parser::Unwrap<parser::BlockConstruct>(x) != nullptr};
+
+  // Keep standalone replacements active throughout their associated DO or
+  // BLOCK construct so nested construct selectors see the selected path.
+  if (rootLoop || isStrictlyStructuredBlock) {
----------------
MattPD wrote:

When a standalone metadirective selects a block-associated replacement, semantics keeps that replacement active through the following strictly structured BLOCK. Test `f28` asserts this behavior.

Lowering does not enclose that BLOCK in the selected region. `genMetadirective` dispatches the variant on the metadirective's own evaluation and keeps the selected construct context active only during that dispatch. It includes a following sibling in the dispatch only when the sibling is a loop. The selected region is already dropped at the merge base, so `!$omp metadirective when(implementation={vendor(llvm)}: parallel)` followed by a BLOCK does not lower to `omp.parallel` at either the merge base or this PR's head.

With this PR, the disagreement has a second symptom: Semantics accepts an input triggering a lowering abort. You can reproduce the abort by saving the following to `standalone.f90` and running `flang -fc1 -fopenmp -fopenmp-version=52 -emit-hlfir -o - standalone.f90`:

```fortran
subroutine standalone_parallel_gap(n)
  integer :: n, i
  !$omp metadirective when(implementation={vendor(llvm)}: parallel) &
  !$omp& otherwise(nothing)
  block
    !$omp metadirective &
    !$omp& when(construct={parallel}: nothing) &
    !$omp& when(implementation={vendor(score(0): llvm)}: simd collapse(2)) &
    !$omp& otherwise(nothing)
    do i = 1, n
    end do
  end block
end subroutine
```

With this PR, `-fsyntax-only` accepts the program. In semantics, the selected `parallel` context causes the inner metadirective to select `nothing`. The SIMD candidate is therefore unreachable, so semantics does not check its loop. Lowering lacks that `parallel` context, selects the SIMD candidate, and aborts with "not yet implemented: METADIRECTIVE variant with COLLAPSE or ORDERED requires a deeper perfectly-nested loop nest than is present". On https://github.com/llvm/llvm-project/pull/224431 alone, the program is rejected in semantics. The `begin metadirective` / `end metadirective` spelling lowers to `omp.parallel` and agrees with semantics.

The semantics model follows the strictly-structured-block rule, so the fix belongs in lowering. This comment tracks that gap and does not ask for a change in this PR. A lowering fix could process the following BLOCK while the selected construct context remains active. It would then move the BLOCK back to its original position and skip its normal lowering, as the existing loop handling does. Until then, `f28` asserts only the semantics model. It does not test the corresponding lowering behavior.

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


More information about the llvm-branch-commits mailing list