[Mlir-commits] [flang] [mlir] [Flang][OpenMP] Don't generate code for unreachable target regions. (PR #178937)

Abid Qadeer llvmlistbot at llvm.org
Wed Feb 11 02:56:35 PST 2026


https://github.com/abidh updated https://github.com/llvm/llvm-project/pull/178937

>From 8b560641eb462ff85661c927a70150e7568cc21e Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Fri, 30 Jan 2026 16:08:46 +0000
Subject: [PATCH 1/3] [Flang][OpenMP] Don't generate code for unreachable
 target regions.

When a target region is placed inside a constant false condition (e.g.,
`if (.false.)`), MLIR's Canonicalizer pass correctly eliminates the dead
code on the host side, removing the `omp.target` operation entirely.
However, the device-side compilation pipeline is unaware of this
elimination and attempts to generate kernel code. Since the host never
created offload metadata for the eliminated target, the device-side
kernel function lacks the "kernel" attribute, causing OpenMPOpt to fail
with an assertion when it expects all outlined kernels to have this
attribute. The problem can be seen with the following code:

```fortran
program cele
  implicit none
  real :: V
  integer :: i
  if (.false.) then
    !$omp target teams distribute parallel do
    do i = 1, 5
      V = V * 2
    end do
    !$omp end target teams distribute parallel do
  end if
end program
```

It currently fails with the follwoing assertion:

```
Assertion `omp::isOpenMPKernel(*Kernel) && "Expected kernel function!"' failed.
llvm/lib/Transforms/IPO/OpenMPOpt.cpp:4291
```

This PR adds MarkUnreachableTargetsPass that identifies `omp.target`
operations in unreachable code blocks and marks them with
`omp.target_unreachable` attribute. This attribute is later used in
FunctionFilteringPass and in OpenMPToLLVMIRTranslation to prevent
generation of code for such op.
---
 .../include/flang/Optimizer/OpenMP/Passes.td  |  12 +
 flang/lib/Optimizer/OpenMP/CMakeLists.txt     |   1 +
 .../Optimizer/OpenMP/FunctionFiltering.cpp    |   9 +-
 .../OpenMP/MarkUnreachableTargets.cpp         | 166 +++++++++
 flang/lib/Optimizer/Passes/Pipelines.cpp      |   5 +
 flang/test/Lower/OpenMP/target-dead-code.f90  |  82 +++++
 .../OpenMP/mark-unreachable-targets.mlir      | 331 ++++++++++++++++++
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |   5 +
 .../LLVMIR/omptarget-unreachable-region.mlir  |  21 ++
 9 files changed, 630 insertions(+), 2 deletions(-)
 create mode 100644 flang/lib/Optimizer/OpenMP/MarkUnreachableTargets.cpp
 create mode 100644 flang/test/Lower/OpenMP/target-dead-code.f90
 create mode 100644 flang/test/Transforms/OpenMP/mark-unreachable-targets.mlir
 create mode 100644 mlir/test/Target/LLVMIR/omptarget-unreachable-region.mlir

diff --git a/flang/include/flang/Optimizer/OpenMP/Passes.td b/flang/include/flang/Optimizer/OpenMP/Passes.td
index f17b1e3794908..793d575b7da8e 100644
--- a/flang/include/flang/Optimizer/OpenMP/Passes.td
+++ b/flang/include/flang/Optimizer/OpenMP/Passes.td
@@ -41,6 +41,18 @@ def MarkDeclareTargetPass
   let dependentDialects = ["mlir::omp::OpenMPDialect"];
 }
 
+def MarkUnreachableTargetsPass
+    : Pass<"omp-mark-unreachable-targets", "mlir::ModuleOp"> {
+  let summary = "Marks OpenMP target operations in unreachable code";
+  let description = [{
+    Identifies OpenMP target operations that reside in unreachable code
+    (e.g., inside if(.false.) blocks) and marks them with an attribute.
+    This allows device compilation to skip generating code for targets
+    that were eliminated on the host side.
+  }];
+  let dependentDialects = ["mlir::omp::OpenMPDialect"];
+}
+
 def FunctionFilteringPass : Pass<"omp-function-filtering"> {
   let summary = "Filters out functions intended for the host when compiling "
                 "for the target device.";
diff --git a/flang/lib/Optimizer/OpenMP/CMakeLists.txt b/flang/lib/Optimizer/OpenMP/CMakeLists.txt
index 23a7dc8f08399..136b9d9ea9313 100644
--- a/flang/lib/Optimizer/OpenMP/CMakeLists.txt
+++ b/flang/lib/Optimizer/OpenMP/CMakeLists.txt
@@ -8,6 +8,7 @@ add_flang_library(FlangOpenMPTransforms
   MapsForPrivatizedSymbols.cpp
   MapInfoFinalization.cpp
   MarkDeclareTarget.cpp
+  MarkUnreachableTargets.cpp
   LowerWorkdistribute.cpp
   LowerWorkshare.cpp
   LowerNontemporal.cpp
diff --git a/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp b/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp
index 0acee8991e372..f7828df21182e 100644
--- a/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp
+++ b/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp
@@ -120,10 +120,15 @@ class FunctionFilteringPass
       // Do not filter functions with target regions inside, because they have
       // to be available for both host and device so that regular and reverse
       // offloading can be supported.
+      // However, skip target regions marked as unreachable.
       bool hasTargetRegion =
           funcOp
-              ->walk<WalkOrder::PreOrder>(
-                  [&](omp::TargetOp) { return WalkResult::interrupt(); })
+              ->walk<WalkOrder::PreOrder>([&](omp::TargetOp targetOp) {
+                // Skip targets marked as unreachable
+                if (targetOp->hasAttr("omp.target_unreachable"))
+                  return WalkResult::advance();
+                return WalkResult::interrupt();
+              })
               .wasInterrupted();
 
       omp::DeclareTargetDeviceType declareType =
diff --git a/flang/lib/Optimizer/OpenMP/MarkUnreachableTargets.cpp b/flang/lib/Optimizer/OpenMP/MarkUnreachableTargets.cpp
new file mode 100644
index 0000000000000..06423f32a77f0
--- /dev/null
+++ b/flang/lib/Optimizer/OpenMP/MarkUnreachableTargets.cpp
@@ -0,0 +1,166 @@
+//===- MarkUnreachableTargets.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This pass marks OpenMP target operations that are in unreachable code
+// with an attribute. This allows device compilation to skip generating code
+// for such ops.
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Optimizer/OpenMP/Passes.h"
+
+#include "flang/Optimizer/Dialect/FIRDialect.h"
+#include "flang/Optimizer/Dialect/FIROps.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Support/LLVM.h"
+#include "llvm/ADT/SmallSet.h"
+
+namespace flangomp {
+#define GEN_PASS_DEF_MARKUNREACHABLETARGETSPASS
+#include "flang/Optimizer/OpenMP/Passes.h.inc"
+} // namespace flangomp
+
+using namespace mlir;
+
+namespace {
+
+/// Check if an operation is nested inside a fir.if with a constant false
+/// condition.
+static bool isInUnreachableIfBlock(Operation *op) {
+  Operation *current = op;
+
+  // Walk up through parent operations
+  while (current) {
+    Operation *parentOp = current->getParentOp();
+    if (!parentOp)
+      break;
+
+    // Check for fir.if with constant false condition
+    if (auto firIf = dyn_cast<fir::IfOp>(parentOp)) {
+      if (auto constOp =
+              firIf.getCondition().getDefiningOp<arith::ConstantOp>()) {
+        if (auto intAttr = dyn_cast<IntegerAttr>(constOp.getValue())) {
+          // If condition is false (0) and op is in the "then" region
+          if (intAttr.getInt() == 0 &&
+              current->getParentRegion() == &firIf.getThenRegion())
+            return true;
+          // If condition is true (non-zero) and op is in the "else" region
+          if (intAttr.getInt() != 0 && !firIf.getElseRegion().empty() &&
+              current->getParentRegion() == &firIf.getElseRegion())
+            return true;
+        }
+      }
+    }
+
+    current = parentOp;
+  }
+
+  return false;
+}
+
+/// Check if a block is unreachable due to constant condition branches.
+/// A block is unreachable only if ALL predecessors lead to it through
+/// unreachable paths (i.e., constant false conditions).
+/// This handles patterns like:
+///   %false = arith.constant false
+///   cf.cond_br %false, ^bb1, ^bb2
+/// where ^bb1 is unreachable.
+static bool isBlockUnreachable(Block *block) {
+  // Entry blocks and blocks with no predecessors are reachable
+  if (block->hasNoPredecessors())
+    return false;
+
+  // Check all predecessors - block is unreachable only if ALL paths are
+  // provably unreachable via constant conditions
+  for (Block *pred : block->getPredecessors()) {
+    Operation *terminator = pred->getTerminator();
+
+    // Check if this is a cf.cond_br with constant condition
+    if (auto condBr = dyn_cast<cf::CondBranchOp>(terminator)) {
+      // Try to get the constant value of the condition
+      if (auto constOp =
+              condBr.getCondition().getDefiningOp<arith::ConstantOp>()) {
+        if (auto intAttr = dyn_cast<IntegerAttr>(constOp.getValue())) {
+          bool condIsTrue = intAttr.getInt() != 0;
+
+          // If condition is false and block is the true destination,
+          // this path is unreachable - continue checking other predecessors
+          if (!condIsTrue && block == condBr.getTrueDest())
+            continue;
+          // If condition is true and block is the false destination,
+          // this path is unreachable - continue checking other predecessors
+          if (condIsTrue && block == condBr.getFalseDest())
+            continue;
+          // Otherwise, this path IS reachable (condition matches destination)
+          return false;
+        }
+      }
+    }
+
+    // If we reach here, this predecessor either:
+    // - is not a CondBranchOp, OR
+    // - doesn't have a constant condition
+    // Either way, this path could be taken, so block is reachable
+    return false;
+  }
+
+  // All predecessors lead to this block through unreachable paths
+  return true;
+}
+
+/// Recursively check if an operation is in an unreachable block.
+/// This walks up the block hierarchy to check if any containing block
+/// is unreachable, handling both fir.if and cf.cond_br patterns.
+static bool isOperationUnreachable(Operation *op) {
+  // First check for fir.if patterns (before SCF lowering)
+  if (isInUnreachableIfBlock(op))
+    return true;
+
+  // Then check for cf.cond_br patterns (after SCF lowering)
+  Block *currentBlock = op->getBlock();
+
+  // Walk up through nested regions checking each block
+  while (currentBlock) {
+    if (isBlockUnreachable(currentBlock))
+      return true;
+
+    // Move to parent operation's block
+    Operation *parentOp = currentBlock->getParentOp();
+    if (!parentOp || isa<ModuleOp>(parentOp) || isa<func::FuncOp>(parentOp))
+      break;
+
+    currentBlock = parentOp->getBlock();
+  }
+
+  return false;
+}
+
+class MarkUnreachableTargetsPass
+    : public flangomp::impl::MarkUnreachableTargetsPassBase<
+          MarkUnreachableTargetsPass> {
+public:
+  MarkUnreachableTargetsPass() = default;
+
+  void runOnOperation() override {
+    MLIRContext *context = &getContext();
+    auto module = getOperation();
+
+    // Walk all target operations and mark those that are unreachable
+    module.walk([&](omp::TargetOp targetOp) {
+      if (isOperationUnreachable(targetOp.getOperation()))
+        targetOp->setAttr("omp.target_unreachable", UnitAttr::get(context));
+    });
+  }
+};
+
+} // namespace
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index 6054675643c64..a73a3f9d2325c 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -341,6 +341,11 @@ void createOpenMPFIRPassPipeline(mlir::PassManager &pm,
   pm.addPass(flangomp::createAutomapToTargetDataPass());
   pm.addPass(flangomp::createMapInfoFinalizationPass());
   pm.addPass(flangomp::createMarkDeclareTargetPass());
+
+  // Mark unreachable target operations before FunctionFilteringPass
+  // extracts them.
+  pm.addPass(flangomp::createMarkUnreachableTargetsPass());
+
   pm.addPass(flangomp::createGenericLoopConversionPass());
   if (opts.isTargetDevice)
     pm.addPass(flangomp::createFunctionFilteringPass());
diff --git a/flang/test/Lower/OpenMP/target-dead-code.f90 b/flang/test/Lower/OpenMP/target-dead-code.f90
new file mode 100644
index 0000000000000..82932ca82858f
--- /dev/null
+++ b/flang/test/Lower/OpenMP/target-dead-code.f90
@@ -0,0 +1,82 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s --check-prefix=FIR
+
+! Test that OpenMP target regions in dead code are marked for elimination
+
+! Test 1: if (.false.) with target - should be marked unreachable
+! FIR-LABEL: func.func @_QPtest_dead_simple
+! FIR: %[[FALSE:.*]] = arith.constant false
+! FIR: fir.if %[[FALSE]] {
+! FIR: omp.target
+! FIR: } {omp.target_unreachable}
+subroutine test_dead_simple()
+  real :: v
+  if (.false.) then
+    !$omp target map(tofrom:v)
+    v = 1.0
+    !$omp end target
+  end if
+end subroutine
+
+! Test 2: Live target - should NOT be marked
+! FIR-LABEL: func.func @_QPtest_live_simple
+! FIR: omp.target
+! FIR-NOT: omp.target_unreachable
+subroutine test_live_simple()
+  real :: v
+  !$omp target map(tofrom:v)
+  v = 2.0
+  !$omp end target
+end subroutine
+
+! Test 3: Mixed dead and live
+! FIR-LABEL: func.func @_QPtest_mixed
+subroutine test_mixed()
+  real :: v
+  ! Dead - should be marked
+  ! FIR: fir.if %{{.*}} {
+  if (.false.) then
+    !$omp target map(tofrom:v)
+    ! FIR: omp.target
+    ! FIR: } {omp.target_unreachable}
+    v = 3.0
+    !$omp end target
+  end if
+  ! Live - should NOT be marked
+  !$omp target map(tofrom:v)
+  ! FIR: omp.target
+  ! FIR-NOT: omp.target_unreachable
+  v = 4.0
+  !$omp end target
+end subroutine
+
+! Test 4: Nested - outer false
+! FIR-LABEL: func.func @_QPtest_nested_outer_false
+subroutine test_nested_outer_false()
+  real :: v
+  ! FIR: fir.if %{{.*}} {
+  if (.false.) then
+    ! FIR: fir.if %{{.*}} {
+    if (.true.) then
+      ! FIR: omp.target
+      ! FIR: } {omp.target_unreachable}
+      !$omp target map(tofrom:v)
+      v = 5.0
+      !$omp end target
+    end if
+  end if
+end subroutine
+
+! Test 5: Parameter constant
+! FIR-LABEL: func.func @_QPtest_parameter
+subroutine test_parameter()
+  real :: v
+  logical, parameter :: DEAD = .false.
+  ! FIR: fir.if %{{.*}} {
+  if (DEAD) then
+    ! FIR: omp.target
+    ! FIR: } {omp.target_unreachable}
+    !$omp target map(tofrom:v)
+    v = 6.0
+    !$omp end target
+  end if
+end subroutine
diff --git a/flang/test/Transforms/OpenMP/mark-unreachable-targets.mlir b/flang/test/Transforms/OpenMP/mark-unreachable-targets.mlir
new file mode 100644
index 0000000000000..66f7e607a65fd
--- /dev/null
+++ b/flang/test/Transforms/OpenMP/mark-unreachable-targets.mlir
@@ -0,0 +1,331 @@
+// RUN: fir-opt --omp-mark-unreachable-targets %s | FileCheck %s
+
+// CHECK-LABEL: func.func @test_if_false_simple
+func.func @test_if_false_simple() {
+  %false = arith.constant false
+  // CHECK: fir.if %{{.*}} {
+  fir.if %false {
+    // CHECK: omp.target
+    // CHECK-NEXT: omp.terminator
+    // CHECK-NEXT: } {omp.target_unreachable}
+    omp.target {
+      omp.terminator
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_if_true_simple
+func.func @test_if_true_simple() {
+  %true = arith.constant true
+  // CHECK: fir.if %{{.*}} {
+  fir.if %true {
+    // CHECK: omp.target {
+    // CHECK-NEXT: omp.terminator
+    // CHECK-NEXT: }
+    // CHECK-NOT: omp.target_unreachable
+    omp.target {
+      omp.terminator
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_nested_outer_false
+func.func @test_nested_outer_false() {
+  %false = arith.constant false
+  %true = arith.constant true
+  // CHECK: fir.if %{{.*}} {
+  fir.if %false {
+    // CHECK: fir.if %{{.*}} {
+    fir.if %true {
+      // CHECK: omp.target
+      // CHECK-NEXT: omp.terminator
+      // CHECK-NEXT: } {omp.target_unreachable}
+      omp.target {
+        omp.terminator
+      }
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_nested_inner_false
+func.func @test_nested_inner_false() {
+  %false = arith.constant false
+  %true = arith.constant true
+  // CHECK: fir.if %{{.*}} {
+  fir.if %true {
+    // CHECK: fir.if %{{.*}} {
+    fir.if %false {
+      // CHECK: omp.target
+      // CHECK: } {omp.target_unreachable}
+      omp.target {
+        omp.terminator
+      }
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_nested_both_true
+func.func @test_nested_both_true() {
+  %true1 = arith.constant true
+  %true2 = arith.constant true
+  // CHECK: fir.if %{{.*}} {
+  fir.if %true1 {
+    // CHECK: fir.if %{{.*}} {
+    fir.if %true2 {
+      // CHECK: omp.target {
+      // CHECK-NEXT: omp.terminator
+      // CHECK-NEXT: }
+      // CHECK-NOT: omp.target_unreachable
+      omp.target {
+        omp.terminator
+      }
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_mixed_targets
+func.func @test_mixed_targets() {
+  %false = arith.constant false
+  %true = arith.constant true
+
+  // Dead target
+  // CHECK: fir.if %{{.*}} {
+  fir.if %false {
+    // CHECK: omp.target
+    // CHECK: } {omp.target_unreachable}
+    omp.target {
+      omp.terminator
+    }
+  }
+
+  // Live target - should NOT have unreachable attribute
+  // CHECK: omp.target {
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  // CHECK-NOT: omp.target_unreachable
+  omp.target {
+    omp.terminator
+  }
+
+  // Another live target in if (true)
+  // CHECK: fir.if %{{.*}} {
+  fir.if %true {
+    // CHECK: omp.target {
+    // CHECK-NEXT: omp.terminator
+    // CHECK-NEXT: }
+    // CHECK-NOT: omp.target_unreachable
+    omp.target {
+      omp.terminator
+    }
+  }
+
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_multiple_dead_targets
+func.func @test_multiple_dead_targets() {
+  %false = arith.constant false
+
+  // CHECK: fir.if %{{.*}} {
+  fir.if %false {
+    // CHECK: omp.target
+    // CHECK: } {omp.target_unreachable}
+    omp.target {
+      omp.terminator
+    }
+
+    // CHECK: omp.target
+    // CHECK: } {omp.target_unreachable}
+    omp.target {
+      omp.terminator
+    }
+
+    // CHECK: omp.target
+    // CHECK: } {omp.target_unreachable}
+    omp.target {
+      omp.terminator
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_if_else_false
+func.func @test_if_else_false() {
+  %false = arith.constant false
+
+  // CHECK: fir.if %{{.*}} {
+  fir.if %false {
+    // CHECK: omp.target
+    // CHECK: } {omp.target_unreachable}
+    omp.target {
+      omp.terminator
+    }
+  } else {
+    // Else branch should not be marked (it's reachable)
+    // CHECK: omp.target {
+    // CHECK-NEXT: omp.terminator
+    // CHECK-NEXT: }
+    // CHECK-NOT: omp.target_unreachable
+    omp.target {
+      omp.terminator
+    }
+  }
+  return
+}
+
+// -----
+
+// Test with cf.cond_br
+// CHECK-LABEL: func.func @test_cf_cond_br_false
+func.func @test_cf_cond_br_false() {
+  %false = arith.constant false
+  // CHECK: cf.cond_br %{{.*}}, ^bb1, ^bb2
+  cf.cond_br %false, ^bb1, ^bb2
+^bb1:
+  // CHECK: omp.target
+  // CHECK: } {omp.target_unreachable}
+  omp.target {
+    omp.terminator
+  }
+  cf.br ^bb2
+^bb2:
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_cf_cond_br_true
+func.func @test_cf_cond_br_true() {
+  %true = arith.constant true
+  // CHECK: cf.cond_br %{{.*}}, ^bb1, ^bb2
+  cf.cond_br %true, ^bb1, ^bb2
+^bb1:
+  // CHECK: omp.target {
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  // CHECK-NOT: omp.target_unreachable
+  omp.target {
+    omp.terminator
+  }
+  cf.br ^bb2
+^bb2:
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_runtime_condition
+func.func @test_runtime_condition(%arg0: i1) {
+  // CHECK: fir.if %arg0 {
+  fir.if %arg0 {
+    // Runtime condition - should NOT be marked
+    // CHECK: omp.target {
+    // CHECK-NEXT: omp.terminator
+    // CHECK-NEXT: }
+    // CHECK-NOT: omp.target_unreachable
+    omp.target {
+      omp.terminator
+    }
+  }
+  return
+}
+
+// -----
+
+// Test for multiple predecessors - one reachable, one unreachable
+// The block should NOT be marked as unreachable if ANY path is reachable
+// CHECK-LABEL: func.func @test_multiple_predecessors
+func.func @test_multiple_predecessors() {
+  %false = arith.constant false
+  cf.cond_br %false, ^bb2, ^bb1
+^bb1:
+  // Reachable path to bb2
+  cf.br ^bb2
+^bb2:
+  // This block has two predecessors:
+  // - bb0 with false condition (unreachable path)
+  // - bb1 with unconditional branch (reachable path)
+  // Target should NOT be marked unreachable because bb1 provides a reachable path
+  // CHECK: omp.target {
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  // CHECK-NOT: omp.target_unreachable
+  omp.target {
+    omp.terminator
+  }
+  return
+}
+
+// -----
+
+// Test for multiple predecessors - ALL unreachable
+// CHECK-LABEL: func.func @test_multiple_predecessors_all_unreachable
+func.func @test_multiple_predecessors_all_unreachable() {
+  %false1 = arith.constant false
+  %false2 = arith.constant false
+  cf.cond_br %false1, ^bb3, ^bb1
+^bb1:
+  cf.cond_br %false2, ^bb3, ^bb2
+^bb2:
+  cf.br ^bb4
+^bb3:
+  // This block has two predecessors:
+  // - bb0 with false condition to bb3 (unreachable)
+  // - bb1 with false condition to bb3 (unreachable)
+  // Target SHOULD be marked unreachable because ALL paths are unreachable
+  // CHECK: omp.target
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: } {omp.target_unreachable}
+  omp.target {
+    omp.terminator
+  }
+  cf.br ^bb4
+^bb4:
+  return
+}
+
+// -----
+
+// Test for multiple predecessors with mixed constant and runtime conditions
+// CHECK-LABEL: func.func @test_multiple_predecessors_mixed
+func.func @test_multiple_predecessors_mixed(%arg0: i1) {
+  %false = arith.constant false
+  cf.cond_br %false, ^bb2, ^bb1
+^bb1:
+  // Runtime condition - could branch to bb2
+  cf.cond_br %arg0, ^bb2, ^bb3
+^bb2:
+  // This block has two predecessors:
+  // - bb0 with false condition (unreachable)
+  // - bb1 with runtime condition (potentially reachable)
+  // Target should NOT be marked because we can't prove bb1 path is unreachable
+  // CHECK: omp.target {
+  // CHECK-NEXT: omp.terminator
+  // CHECK-NEXT: }
+  // CHECK-NOT: omp.target_unreachable
+  omp.target {
+    omp.terminator
+  }
+  cf.br ^bb3
+^bb3:
+  return
+}
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index f04d614633965..acb6145628799 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -6370,6 +6370,11 @@ static LogicalResult
 convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
                  LLVM::ModuleTranslation &moduleTranslation) {
   auto targetOp = cast<omp::TargetOp>(opInst);
+
+  // Skip target operations marked as unreachable
+  if (targetOp->hasAttr("omp.target_unreachable"))
+    return success();
+
   // The current debug location already has the DISubprogram for the outlined
   // function that will be created for the target op. We save it here so that
   // we can set it on the outlined function.
diff --git a/mlir/test/Target/LLVMIR/omptarget-unreachable-region.mlir b/mlir/test/Target/LLVMIR/omptarget-unreachable-region.mlir
new file mode 100644
index 0000000000000..b162d7742fb56
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/omptarget-unreachable-region.mlir
@@ -0,0 +1,21 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+// Test that omp.target operations marked with omp.target_unreachable
+// do not generate any kernel code in LLVM IR
+
+module attributes {omp.is_target_device = false} {
+  // Test 1: Target with unreachable attribute - should generate NO kernel code
+  llvm.func @test_unreachable_target() {
+    %0 = llvm.mlir.constant(1 : i64) : i64
+    %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr
+    %map = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr {name = "a"}
+    omp.target map_entries(%map -> %arg0 : !llvm.ptr) {
+      %2 = llvm.mlir.constant(42 : i32) : i32
+      llvm.store %2, %arg0 : i32, !llvm.ptr
+      omp.terminator
+    } {omp.target_unreachable}
+    llvm.return
+  }
+}
+
+// CHECK-NOT: @__omp_offloading_{{.*}}_test_unreachable_target

>From e7aec96e1821f1a9decfcc0ffd989f02979e857f Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Wed, 4 Feb 2026 18:10:18 +0000
Subject: [PATCH 2/3] Handle review comments.

1. Use DominanceInfo to find unreachable blocks.

2. Remove unreachable op instead of marking it.

3. Rename pass and files to better reflect functionality.
---
 .../include/flang/Optimizer/OpenMP/Passes.td  |  14 +-
 flang/lib/Optimizer/OpenMP/CMakeLists.txt     |   2 +-
 .../OpenMP/DeleteUnreachableTargets.cpp       | 140 ++++++++
 .../Optimizer/OpenMP/FunctionFiltering.cpp    |   4 -
 .../OpenMP/MarkUnreachableTargets.cpp         | 166 ---------
 flang/lib/Optimizer/Passes/Pipelines.cpp      |   4 +-
 flang/test/Lower/OpenMP/target-dead-code.f90  |  29 +-
 .../OpenMP/delete-unreachable-targets.mlir    | 322 +++++++++++++++++
 .../OpenMP/mark-unreachable-targets.mlir      | 331 ------------------
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      |   4 -
 .../LLVMIR/omptarget-unreachable-region.mlir  |  21 --
 11 files changed, 483 insertions(+), 554 deletions(-)
 create mode 100644 flang/lib/Optimizer/OpenMP/DeleteUnreachableTargets.cpp
 delete mode 100644 flang/lib/Optimizer/OpenMP/MarkUnreachableTargets.cpp
 create mode 100644 flang/test/Transforms/OpenMP/delete-unreachable-targets.mlir
 delete mode 100644 flang/test/Transforms/OpenMP/mark-unreachable-targets.mlir
 delete mode 100644 mlir/test/Target/LLVMIR/omptarget-unreachable-region.mlir

diff --git a/flang/include/flang/Optimizer/OpenMP/Passes.td b/flang/include/flang/Optimizer/OpenMP/Passes.td
index 793d575b7da8e..1b7da0da3721b 100644
--- a/flang/include/flang/Optimizer/OpenMP/Passes.td
+++ b/flang/include/flang/Optimizer/OpenMP/Passes.td
@@ -41,14 +41,14 @@ def MarkDeclareTargetPass
   let dependentDialects = ["mlir::omp::OpenMPDialect"];
 }
 
-def MarkUnreachableTargetsPass
-    : Pass<"omp-mark-unreachable-targets", "mlir::ModuleOp"> {
-  let summary = "Marks OpenMP target operations in unreachable code";
+def DeleteUnreachableTargetsPass
+    : Pass<"omp-delete-unreachable-targets", "mlir::ModuleOp"> {
+  let summary = "Deletes OpenMP target operations in unreachable code";
   let description = [{
-    Identifies OpenMP target operations that reside in unreachable code
-    (e.g., inside if(.false.) blocks) and marks them with an attribute.
-    This allows device compilation to skip generating code for targets
-    that were eliminated on the host side.
+    Identifies and removes OpenMP target operations that reside in unreachable
+    code (e.g., inside if(.false.) blocks). This ensures consistency between
+    host and device compilation by preventing unreachable targets from being
+    processed on the device side.
   }];
   let dependentDialects = ["mlir::omp::OpenMPDialect"];
 }
diff --git a/flang/lib/Optimizer/OpenMP/CMakeLists.txt b/flang/lib/Optimizer/OpenMP/CMakeLists.txt
index 136b9d9ea9313..eb4930fb2f6a7 100644
--- a/flang/lib/Optimizer/OpenMP/CMakeLists.txt
+++ b/flang/lib/Optimizer/OpenMP/CMakeLists.txt
@@ -8,7 +8,7 @@ add_flang_library(FlangOpenMPTransforms
   MapsForPrivatizedSymbols.cpp
   MapInfoFinalization.cpp
   MarkDeclareTarget.cpp
-  MarkUnreachableTargets.cpp
+  DeleteUnreachableTargets.cpp
   LowerWorkdistribute.cpp
   LowerWorkshare.cpp
   LowerNontemporal.cpp
diff --git a/flang/lib/Optimizer/OpenMP/DeleteUnreachableTargets.cpp b/flang/lib/Optimizer/OpenMP/DeleteUnreachableTargets.cpp
new file mode 100644
index 0000000000000..4409f52bb137e
--- /dev/null
+++ b/flang/lib/Optimizer/OpenMP/DeleteUnreachableTargets.cpp
@@ -0,0 +1,140 @@
+//===- DeleteUnreachableTargets.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This pass removes OpenMP target operations that are in unreachable code.
+// This ensures host and device compilation have consistent target regions.
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Optimizer/Dialect/FIRDialect.h"
+#include "flang/Optimizer/Dialect/FIROps.h"
+#include "flang/Optimizer/OpenMP/Passes.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
+#include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/Dominance.h"
+#include "mlir/IR/Matchers.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Support/LLVM.h"
+#include "llvm/ADT/SmallSet.h"
+
+namespace flangomp {
+#define GEN_PASS_DEF_DELETEUNREACHABLETARGETSPASS
+#include "flang/Optimizer/OpenMP/Passes.h.inc"
+} // namespace flangomp
+
+using namespace mlir;
+
+namespace {
+
+/// Check if an operation is unreachable from the entry block of its function.
+/// This function detects unreachability in two ways:
+/// 1. Constant conditions in structured control flow (e.g., fir.if with
+///    constant false condition)
+/// 2. Block-level unreachability (using DominanceInfo)
+static bool isOperationUnreachable(Operation *op, DominanceInfo &domInfo) {
+  // Walk up through parent operations to check for constant conditions in
+  // structured control flow (fir.if)
+  Operation *current = op;
+  while (current) {
+    Operation *parentOp = current->getParentOp();
+    if (!parentOp)
+      break;
+
+    // Check for fir.if with constant condition.
+    // This catches Fortran constructs like "if (.false.) then ... end if".
+    if (auto firIf = dyn_cast<fir::IfOp>(parentOp)) {
+      IntegerAttr constAttr;
+      if (matchPattern(firIf.getCondition(), m_Constant(&constAttr))) {
+        // If condition is false (0) and op is in the "then" region
+        if (constAttr.getInt() == 0 &&
+            current->getParentRegion() == &firIf.getThenRegion())
+          return true;
+        // If condition is true (non-zero) and op is in the "else" region
+        if (constAttr.getInt() != 0 && !firIf.getElseRegion().empty() &&
+            current->getParentRegion() == &firIf.getElseRegion())
+          return true;
+      }
+    }
+
+    // Stop at function boundary
+    if (isa<func::FuncOp>(parentOp))
+      break;
+
+    current = parentOp;
+  }
+
+  // Check block-level reachability using DominanceInfo.
+  // This detects blocks that are unreachable from the function entry due to
+  // control flow (e.g., blocks with no predecessors, disconnected blocks).
+
+  // Find the ancestor block that is in the function's main region.
+  // We need to check if that block is reachable, not just the immediate
+  // block containing the operation (which might be inside a fir.if region).
+  auto funcOp = op->getParentOfType<func::FuncOp>();
+  if (!funcOp)
+    return false;
+
+  Region *funcRegion = &funcOp.getRegion();
+  Operation *ancestor = op;
+  Block *blockInFuncRegion = nullptr;
+
+  // Walk up to find the block in the function's main region
+  while (ancestor) {
+    Block *block = ancestor->getBlock();
+    if (block && block->getParent() == funcRegion) {
+      blockInFuncRegion = block;
+      break;
+    }
+    ancestor = ancestor->getParentOp();
+    if (!ancestor || ancestor == funcOp.getOperation())
+      break;
+  }
+
+  // Check if we found a block in the function's main region and if it's
+  // reachable
+  if (blockInFuncRegion) {
+    Block *entryBlock = &funcRegion->front();
+    if (blockInFuncRegion == entryBlock)
+      return false;
+    return !domInfo.isReachableFromEntry(blockInFuncRegion);
+  }
+
+  return false;
+}
+
+class DeleteUnreachableTargetsPass
+    : public flangomp::impl::DeleteUnreachableTargetsPassBase<
+          DeleteUnreachableTargetsPass> {
+public:
+  DeleteUnreachableTargetsPass() = default;
+
+  void runOnOperation() override {
+    auto module = getOperation();
+
+    module.walk([&](func::FuncOp funcOp) {
+      // Create dominance info for this function
+      DominanceInfo domInfo(funcOp);
+
+      // Collect unreachable target operations in this function
+      SmallVector<omp::TargetOp> unreachableTargets;
+      funcOp.walk([&](omp::TargetOp targetOp) {
+        if (isOperationUnreachable(targetOp.getOperation(), domInfo))
+          unreachableTargets.push_back(targetOp);
+      });
+
+      // Delete unreachable target operations
+      for (omp::TargetOp targetOp : unreachableTargets)
+        targetOp->erase();
+    });
+  }
+};
+
+} // namespace
diff --git a/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp b/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp
index f7828df21182e..472d6a9f08a6e 100644
--- a/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp
+++ b/flang/lib/Optimizer/OpenMP/FunctionFiltering.cpp
@@ -120,13 +120,9 @@ class FunctionFilteringPass
       // Do not filter functions with target regions inside, because they have
       // to be available for both host and device so that regular and reverse
       // offloading can be supported.
-      // However, skip target regions marked as unreachable.
       bool hasTargetRegion =
           funcOp
               ->walk<WalkOrder::PreOrder>([&](omp::TargetOp targetOp) {
-                // Skip targets marked as unreachable
-                if (targetOp->hasAttr("omp.target_unreachable"))
-                  return WalkResult::advance();
                 return WalkResult::interrupt();
               })
               .wasInterrupted();
diff --git a/flang/lib/Optimizer/OpenMP/MarkUnreachableTargets.cpp b/flang/lib/Optimizer/OpenMP/MarkUnreachableTargets.cpp
deleted file mode 100644
index 06423f32a77f0..0000000000000
--- a/flang/lib/Optimizer/OpenMP/MarkUnreachableTargets.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-//===- MarkUnreachableTargets.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
-//
-//===----------------------------------------------------------------------===//
-//
-// This pass marks OpenMP target operations that are in unreachable code
-// with an attribute. This allows device compilation to skip generating code
-// for such ops.
-//
-//===----------------------------------------------------------------------===//
-
-#include "flang/Optimizer/OpenMP/Passes.h"
-
-#include "flang/Optimizer/Dialect/FIRDialect.h"
-#include "flang/Optimizer/Dialect/FIROps.h"
-#include "mlir/Dialect/Arith/IR/Arith.h"
-#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
-#include "mlir/Dialect/Func/IR/FuncOps.h"
-#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
-#include "mlir/IR/BuiltinOps.h"
-#include "mlir/Pass/Pass.h"
-#include "mlir/Support/LLVM.h"
-#include "llvm/ADT/SmallSet.h"
-
-namespace flangomp {
-#define GEN_PASS_DEF_MARKUNREACHABLETARGETSPASS
-#include "flang/Optimizer/OpenMP/Passes.h.inc"
-} // namespace flangomp
-
-using namespace mlir;
-
-namespace {
-
-/// Check if an operation is nested inside a fir.if with a constant false
-/// condition.
-static bool isInUnreachableIfBlock(Operation *op) {
-  Operation *current = op;
-
-  // Walk up through parent operations
-  while (current) {
-    Operation *parentOp = current->getParentOp();
-    if (!parentOp)
-      break;
-
-    // Check for fir.if with constant false condition
-    if (auto firIf = dyn_cast<fir::IfOp>(parentOp)) {
-      if (auto constOp =
-              firIf.getCondition().getDefiningOp<arith::ConstantOp>()) {
-        if (auto intAttr = dyn_cast<IntegerAttr>(constOp.getValue())) {
-          // If condition is false (0) and op is in the "then" region
-          if (intAttr.getInt() == 0 &&
-              current->getParentRegion() == &firIf.getThenRegion())
-            return true;
-          // If condition is true (non-zero) and op is in the "else" region
-          if (intAttr.getInt() != 0 && !firIf.getElseRegion().empty() &&
-              current->getParentRegion() == &firIf.getElseRegion())
-            return true;
-        }
-      }
-    }
-
-    current = parentOp;
-  }
-
-  return false;
-}
-
-/// Check if a block is unreachable due to constant condition branches.
-/// A block is unreachable only if ALL predecessors lead to it through
-/// unreachable paths (i.e., constant false conditions).
-/// This handles patterns like:
-///   %false = arith.constant false
-///   cf.cond_br %false, ^bb1, ^bb2
-/// where ^bb1 is unreachable.
-static bool isBlockUnreachable(Block *block) {
-  // Entry blocks and blocks with no predecessors are reachable
-  if (block->hasNoPredecessors())
-    return false;
-
-  // Check all predecessors - block is unreachable only if ALL paths are
-  // provably unreachable via constant conditions
-  for (Block *pred : block->getPredecessors()) {
-    Operation *terminator = pred->getTerminator();
-
-    // Check if this is a cf.cond_br with constant condition
-    if (auto condBr = dyn_cast<cf::CondBranchOp>(terminator)) {
-      // Try to get the constant value of the condition
-      if (auto constOp =
-              condBr.getCondition().getDefiningOp<arith::ConstantOp>()) {
-        if (auto intAttr = dyn_cast<IntegerAttr>(constOp.getValue())) {
-          bool condIsTrue = intAttr.getInt() != 0;
-
-          // If condition is false and block is the true destination,
-          // this path is unreachable - continue checking other predecessors
-          if (!condIsTrue && block == condBr.getTrueDest())
-            continue;
-          // If condition is true and block is the false destination,
-          // this path is unreachable - continue checking other predecessors
-          if (condIsTrue && block == condBr.getFalseDest())
-            continue;
-          // Otherwise, this path IS reachable (condition matches destination)
-          return false;
-        }
-      }
-    }
-
-    // If we reach here, this predecessor either:
-    // - is not a CondBranchOp, OR
-    // - doesn't have a constant condition
-    // Either way, this path could be taken, so block is reachable
-    return false;
-  }
-
-  // All predecessors lead to this block through unreachable paths
-  return true;
-}
-
-/// Recursively check if an operation is in an unreachable block.
-/// This walks up the block hierarchy to check if any containing block
-/// is unreachable, handling both fir.if and cf.cond_br patterns.
-static bool isOperationUnreachable(Operation *op) {
-  // First check for fir.if patterns (before SCF lowering)
-  if (isInUnreachableIfBlock(op))
-    return true;
-
-  // Then check for cf.cond_br patterns (after SCF lowering)
-  Block *currentBlock = op->getBlock();
-
-  // Walk up through nested regions checking each block
-  while (currentBlock) {
-    if (isBlockUnreachable(currentBlock))
-      return true;
-
-    // Move to parent operation's block
-    Operation *parentOp = currentBlock->getParentOp();
-    if (!parentOp || isa<ModuleOp>(parentOp) || isa<func::FuncOp>(parentOp))
-      break;
-
-    currentBlock = parentOp->getBlock();
-  }
-
-  return false;
-}
-
-class MarkUnreachableTargetsPass
-    : public flangomp::impl::MarkUnreachableTargetsPassBase<
-          MarkUnreachableTargetsPass> {
-public:
-  MarkUnreachableTargetsPass() = default;
-
-  void runOnOperation() override {
-    MLIRContext *context = &getContext();
-    auto module = getOperation();
-
-    // Walk all target operations and mark those that are unreachable
-    module.walk([&](omp::TargetOp targetOp) {
-      if (isOperationUnreachable(targetOp.getOperation()))
-        targetOp->setAttr("omp.target_unreachable", UnitAttr::get(context));
-    });
-  }
-};
-
-} // namespace
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index a73a3f9d2325c..da325ffeaae76 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -342,9 +342,9 @@ void createOpenMPFIRPassPipeline(mlir::PassManager &pm,
   pm.addPass(flangomp::createMapInfoFinalizationPass());
   pm.addPass(flangomp::createMarkDeclareTargetPass());
 
-  // Mark unreachable target operations before FunctionFilteringPass
+  // Delete unreachable target operations before FunctionFilteringPass
   // extracts them.
-  pm.addPass(flangomp::createMarkUnreachableTargetsPass());
+  pm.addPass(flangomp::createDeleteUnreachableTargetsPass());
 
   pm.addPass(flangomp::createGenericLoopConversionPass());
   if (opts.isTargetDevice)
diff --git a/flang/test/Lower/OpenMP/target-dead-code.f90 b/flang/test/Lower/OpenMP/target-dead-code.f90
index 82932ca82858f..8b4029e73eb9b 100644
--- a/flang/test/Lower/OpenMP/target-dead-code.f90
+++ b/flang/test/Lower/OpenMP/target-dead-code.f90
@@ -1,13 +1,12 @@
 ! RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s --check-prefix=FIR
 
-! Test that OpenMP target regions in dead code are marked for elimination
+! Test that OpenMP target regions in dead code are deleted
 
-! Test 1: if (.false.) with target - should be marked unreachable
+! Test 1: if (.false.) with target - target should be deleted
 ! FIR-LABEL: func.func @_QPtest_dead_simple
 ! FIR: %[[FALSE:.*]] = arith.constant false
 ! FIR: fir.if %[[FALSE]] {
-! FIR: omp.target
-! FIR: } {omp.target_unreachable}
+! FIR-NOT: omp.target
 subroutine test_dead_simple()
   real :: v
   if (.false.) then
@@ -17,10 +16,9 @@ subroutine test_dead_simple()
   end if
 end subroutine
 
-! Test 2: Live target - should NOT be marked
+! Test 2: Live target - should remain
 ! FIR-LABEL: func.func @_QPtest_live_simple
 ! FIR: omp.target
-! FIR-NOT: omp.target_unreachable
 subroutine test_live_simple()
   real :: v
   !$omp target map(tofrom:v)
@@ -32,51 +30,46 @@ subroutine test_live_simple()
 ! FIR-LABEL: func.func @_QPtest_mixed
 subroutine test_mixed()
   real :: v
-  ! Dead - should be marked
+  ! Dead - should be deleted
   ! FIR: fir.if %{{.*}} {
   if (.false.) then
     !$omp target map(tofrom:v)
-    ! FIR: omp.target
-    ! FIR: } {omp.target_unreachable}
     v = 3.0
     !$omp end target
   end if
-  ! Live - should NOT be marked
+  ! FIR-NOT: omp.target
+  ! Live - should remain (expect exactly 1 omp.target in function)
   !$omp target map(tofrom:v)
   ! FIR: omp.target
-  ! FIR-NOT: omp.target_unreachable
   v = 4.0
   !$omp end target
 end subroutine
 
-! Test 4: Nested - outer false
+! Test 4: Nested - outer false, target should be deleted
 ! FIR-LABEL: func.func @_QPtest_nested_outer_false
 subroutine test_nested_outer_false()
   real :: v
   ! FIR: fir.if %{{.*}} {
   if (.false.) then
-    ! FIR: fir.if %{{.*}} {
     if (.true.) then
-      ! FIR: omp.target
-      ! FIR: } {omp.target_unreachable}
       !$omp target map(tofrom:v)
       v = 5.0
       !$omp end target
     end if
   end if
+  ! FIR-NOT: omp.target
 end subroutine
 
-! Test 5: Parameter constant
+! Test 5: Parameter constant - target should be deleted
 ! FIR-LABEL: func.func @_QPtest_parameter
 subroutine test_parameter()
   real :: v
   logical, parameter :: DEAD = .false.
   ! FIR: fir.if %{{.*}} {
   if (DEAD) then
-    ! FIR: omp.target
-    ! FIR: } {omp.target_unreachable}
     !$omp target map(tofrom:v)
     v = 6.0
     !$omp end target
   end if
+  ! FIR-NOT: omp.target
 end subroutine
diff --git a/flang/test/Transforms/OpenMP/delete-unreachable-targets.mlir b/flang/test/Transforms/OpenMP/delete-unreachable-targets.mlir
new file mode 100644
index 0000000000000..55e4bdf5d65de
--- /dev/null
+++ b/flang/test/Transforms/OpenMP/delete-unreachable-targets.mlir
@@ -0,0 +1,322 @@
+// RUN: fir-opt --omp-delete-unreachable-targets %s | FileCheck %s
+
+// This test verifies that OpenMP target operations in unreachable code are
+// deleted.
+
+
+// CHECK-LABEL: func.func @test_if_false_simple
+func.func @test_if_false_simple() {
+  %false = arith.constant false
+  // The target in the dead branch should be removed
+  // CHECK: fir.if %false {
+  // CHECK-NOT: omp.target
+  // CHECK: }
+  fir.if %false {
+    omp.target {
+      omp.terminator
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_if_true_simple
+func.func @test_if_true_simple() {
+  %true = arith.constant true
+  // The target should remain since the branch is reachable
+  // CHECK: omp.target
+  fir.if %true {
+    omp.target {
+      omp.terminator
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_nested_outer_false
+func.func @test_nested_outer_false() {
+  %false = arith.constant false
+  %true = arith.constant true
+  // Outer false makes the whole nested structure unreachable
+  // CHECK: fir.if %false {
+  // CHECK-NOT: omp.target
+  // CHECK: }
+  fir.if %false {
+    fir.if %true {
+      omp.target {
+        omp.terminator
+      }
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_nested_inner_false
+func.func @test_nested_inner_false() {
+  %false = arith.constant false
+  %true = arith.constant true
+  // Outer true, inner false - target should be removed
+  // CHECK: fir.if %true {
+  // CHECK: fir.if %false {
+  // CHECK-NOT: omp.target
+  // CHECK: }
+  fir.if %true {
+    fir.if %false {
+      omp.target {
+        omp.terminator
+      }
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_nested_both_true
+func.func @test_nested_both_true() {
+  %true1 = arith.constant true
+  %true2 = arith.constant true
+  // CHECK: omp.target
+  fir.if %true1 {
+    fir.if %true2 {
+      omp.target {
+        omp.terminator
+      }
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_mixed_targets
+func.func @test_mixed_targets() {
+  %false = arith.constant false
+  %true = arith.constant true
+
+  // Live target - should remain (expect 2 targets total in output)
+  // CHECK: omp.target
+  omp.target {
+    omp.terminator
+  }
+
+  // Another live target in if (true) - should remain
+  // CHECK: omp.target
+  fir.if %true {
+    omp.target {
+      omp.terminator
+    }
+  }
+
+  // Dead target - will be removed
+  // CHECK-NOT: omp.target
+  fir.if %false {
+    omp.target {
+      omp.terminator
+    }
+  }
+
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_multiple_dead_targets
+func.func @test_multiple_dead_targets() {
+  %false = arith.constant false
+
+  // All targets inside dead branch should be removed
+  // CHECK-NOT: omp.target
+  fir.if %false {
+    omp.target {
+      omp.terminator
+    }
+
+    omp.target {
+      omp.terminator
+    }
+
+    omp.target {
+      omp.terminator
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_if_else_false
+func.func @test_if_else_false() {
+  %false = arith.constant false
+
+  // CHECK: fir.if %false {
+  fir.if %false {
+    // Then branch is unreachable, target should be deleted
+    omp.target {
+      omp.terminator
+    }
+  } else {
+    // CHECK-NOT: omp.target
+    // CHECK: } else {
+    // Else branch is reachable, target should remain
+    // CHECK: omp.target
+    omp.target {
+      omp.terminator
+    }
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_runtime_condition
+func.func @test_runtime_condition(%arg0: i1) {
+  // Runtime condition - cannot be optimized, should remain unchanged
+  // CHECK: fir.if %arg0 {
+  fir.if %arg0 {
+    // CHECK: omp.target
+    omp.target {
+      omp.terminator
+    }
+  }
+  return
+}
+
+// -----
+
+// Test that targets nested in structured control flow within unreachable blocks
+// are correctly identified as unreachable
+// CHECK-LABEL: func.func @test_nested_in_unreachable_block
+func.func @test_nested_in_unreachable_block() {
+  cf.br ^bb2
+^bb1:
+  // This entire block is unreachable
+  // Even though the fir.if condition is true, the whole block is dead
+  %true = arith.constant true
+  // CHECK: ^bb1:
+  // CHECK-NOT: omp.target
+  // CHECK: cf.br ^bb2
+  fir.if %true {
+    omp.target {
+      omp.terminator
+    }
+  }
+  cf.br ^bb2
+^bb2:
+  // CHECK: ^bb2:
+  // CHECK-NEXT: omp.target
+  omp.target {
+    omp.terminator
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_unreachable_block_after_branch
+func.func @test_unreachable_block_after_branch() {
+  cf.br ^bb2
+^bb1:
+  // This block is unreachable - no predecessor branches to it
+  // CHECK: ^bb1:
+  // CHECK-NOT: omp.target
+  // CHECK: cf.br ^bb2
+  omp.target {
+    omp.terminator
+  }
+  cf.br ^bb2
+^bb2:
+  // This block is reachable
+  // CHECK: ^bb2:
+  // CHECK-NEXT: omp.target
+  omp.target {
+    omp.terminator
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_multiple_unreachable_blocks
+func.func @test_multiple_unreachable_blocks() {
+  cf.br ^bb3
+^bb1:
+  // Unreachable block - no predecessor branches to it
+  // CHECK: ^bb1:
+  // CHECK-NOT: omp.target
+  // CHECK: cf.br ^bb2
+  omp.target {
+    omp.terminator
+  }
+  cf.br ^bb2
+^bb2:
+  // Also unreachable - only reachable from ^bb1 which is itself unreachable
+  // CHECK: ^bb2:
+  // CHECK-NOT: omp.target
+  // CHECK: return
+  omp.target {
+    omp.terminator
+  }
+  return
+^bb3:
+  // Reachable from entry
+  // CHECK: ^bb3:
+  // CHECK-NEXT: omp.target
+  omp.target {
+    omp.terminator
+  }
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_both_branches_reachable
+func.func @test_both_branches_reachable(%arg0: i1) {
+  cf.cond_br %arg0, ^bb1, ^bb2
+^bb1:
+  // CHECK: ^bb1:
+  // CHECK-NEXT: omp.target
+  omp.target {
+    omp.terminator
+  }
+  cf.br ^bb3
+^bb2:
+  // CHECK: ^bb2:
+  // CHECK-NEXT: omp.target
+  omp.target {
+    omp.terminator
+  }
+  cf.br ^bb3
+^bb3:
+  return
+}
+
+// -----
+
+// CHECK-LABEL: func.func @test_disconnected_block
+func.func @test_disconnected_block() {
+  // Entry goes directly to exit
+  cf.br ^bb2
+^bb1:
+  // This block is completely disconnected - no way to reach it
+  // CHECK: ^bb1:
+  // CHECK-NOT: omp.target
+  // CHECK: cf.br ^bb2
+  omp.target {
+    omp.terminator
+  }
+  cf.br ^bb2
+^bb2:
+  // Reachable from entry
+  // CHECK: ^bb2:
+  // CHECK-NEXT: omp.target
+  omp.target {
+    omp.terminator
+  }
+  return
+}
diff --git a/flang/test/Transforms/OpenMP/mark-unreachable-targets.mlir b/flang/test/Transforms/OpenMP/mark-unreachable-targets.mlir
deleted file mode 100644
index 66f7e607a65fd..0000000000000
--- a/flang/test/Transforms/OpenMP/mark-unreachable-targets.mlir
+++ /dev/null
@@ -1,331 +0,0 @@
-// RUN: fir-opt --omp-mark-unreachable-targets %s | FileCheck %s
-
-// CHECK-LABEL: func.func @test_if_false_simple
-func.func @test_if_false_simple() {
-  %false = arith.constant false
-  // CHECK: fir.if %{{.*}} {
-  fir.if %false {
-    // CHECK: omp.target
-    // CHECK-NEXT: omp.terminator
-    // CHECK-NEXT: } {omp.target_unreachable}
-    omp.target {
-      omp.terminator
-    }
-  }
-  return
-}
-
-// -----
-
-// CHECK-LABEL: func.func @test_if_true_simple
-func.func @test_if_true_simple() {
-  %true = arith.constant true
-  // CHECK: fir.if %{{.*}} {
-  fir.if %true {
-    // CHECK: omp.target {
-    // CHECK-NEXT: omp.terminator
-    // CHECK-NEXT: }
-    // CHECK-NOT: omp.target_unreachable
-    omp.target {
-      omp.terminator
-    }
-  }
-  return
-}
-
-// -----
-
-// CHECK-LABEL: func.func @test_nested_outer_false
-func.func @test_nested_outer_false() {
-  %false = arith.constant false
-  %true = arith.constant true
-  // CHECK: fir.if %{{.*}} {
-  fir.if %false {
-    // CHECK: fir.if %{{.*}} {
-    fir.if %true {
-      // CHECK: omp.target
-      // CHECK-NEXT: omp.terminator
-      // CHECK-NEXT: } {omp.target_unreachable}
-      omp.target {
-        omp.terminator
-      }
-    }
-  }
-  return
-}
-
-// -----
-
-// CHECK-LABEL: func.func @test_nested_inner_false
-func.func @test_nested_inner_false() {
-  %false = arith.constant false
-  %true = arith.constant true
-  // CHECK: fir.if %{{.*}} {
-  fir.if %true {
-    // CHECK: fir.if %{{.*}} {
-    fir.if %false {
-      // CHECK: omp.target
-      // CHECK: } {omp.target_unreachable}
-      omp.target {
-        omp.terminator
-      }
-    }
-  }
-  return
-}
-
-// -----
-
-// CHECK-LABEL: func.func @test_nested_both_true
-func.func @test_nested_both_true() {
-  %true1 = arith.constant true
-  %true2 = arith.constant true
-  // CHECK: fir.if %{{.*}} {
-  fir.if %true1 {
-    // CHECK: fir.if %{{.*}} {
-    fir.if %true2 {
-      // CHECK: omp.target {
-      // CHECK-NEXT: omp.terminator
-      // CHECK-NEXT: }
-      // CHECK-NOT: omp.target_unreachable
-      omp.target {
-        omp.terminator
-      }
-    }
-  }
-  return
-}
-
-// -----
-
-// CHECK-LABEL: func.func @test_mixed_targets
-func.func @test_mixed_targets() {
-  %false = arith.constant false
-  %true = arith.constant true
-
-  // Dead target
-  // CHECK: fir.if %{{.*}} {
-  fir.if %false {
-    // CHECK: omp.target
-    // CHECK: } {omp.target_unreachable}
-    omp.target {
-      omp.terminator
-    }
-  }
-
-  // Live target - should NOT have unreachable attribute
-  // CHECK: omp.target {
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  // CHECK-NOT: omp.target_unreachable
-  omp.target {
-    omp.terminator
-  }
-
-  // Another live target in if (true)
-  // CHECK: fir.if %{{.*}} {
-  fir.if %true {
-    // CHECK: omp.target {
-    // CHECK-NEXT: omp.terminator
-    // CHECK-NEXT: }
-    // CHECK-NOT: omp.target_unreachable
-    omp.target {
-      omp.terminator
-    }
-  }
-
-  return
-}
-
-// -----
-
-// CHECK-LABEL: func.func @test_multiple_dead_targets
-func.func @test_multiple_dead_targets() {
-  %false = arith.constant false
-
-  // CHECK: fir.if %{{.*}} {
-  fir.if %false {
-    // CHECK: omp.target
-    // CHECK: } {omp.target_unreachable}
-    omp.target {
-      omp.terminator
-    }
-
-    // CHECK: omp.target
-    // CHECK: } {omp.target_unreachable}
-    omp.target {
-      omp.terminator
-    }
-
-    // CHECK: omp.target
-    // CHECK: } {omp.target_unreachable}
-    omp.target {
-      omp.terminator
-    }
-  }
-  return
-}
-
-// -----
-
-// CHECK-LABEL: func.func @test_if_else_false
-func.func @test_if_else_false() {
-  %false = arith.constant false
-
-  // CHECK: fir.if %{{.*}} {
-  fir.if %false {
-    // CHECK: omp.target
-    // CHECK: } {omp.target_unreachable}
-    omp.target {
-      omp.terminator
-    }
-  } else {
-    // Else branch should not be marked (it's reachable)
-    // CHECK: omp.target {
-    // CHECK-NEXT: omp.terminator
-    // CHECK-NEXT: }
-    // CHECK-NOT: omp.target_unreachable
-    omp.target {
-      omp.terminator
-    }
-  }
-  return
-}
-
-// -----
-
-// Test with cf.cond_br
-// CHECK-LABEL: func.func @test_cf_cond_br_false
-func.func @test_cf_cond_br_false() {
-  %false = arith.constant false
-  // CHECK: cf.cond_br %{{.*}}, ^bb1, ^bb2
-  cf.cond_br %false, ^bb1, ^bb2
-^bb1:
-  // CHECK: omp.target
-  // CHECK: } {omp.target_unreachable}
-  omp.target {
-    omp.terminator
-  }
-  cf.br ^bb2
-^bb2:
-  return
-}
-
-// -----
-
-// CHECK-LABEL: func.func @test_cf_cond_br_true
-func.func @test_cf_cond_br_true() {
-  %true = arith.constant true
-  // CHECK: cf.cond_br %{{.*}}, ^bb1, ^bb2
-  cf.cond_br %true, ^bb1, ^bb2
-^bb1:
-  // CHECK: omp.target {
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  // CHECK-NOT: omp.target_unreachable
-  omp.target {
-    omp.terminator
-  }
-  cf.br ^bb2
-^bb2:
-  return
-}
-
-// -----
-
-// CHECK-LABEL: func.func @test_runtime_condition
-func.func @test_runtime_condition(%arg0: i1) {
-  // CHECK: fir.if %arg0 {
-  fir.if %arg0 {
-    // Runtime condition - should NOT be marked
-    // CHECK: omp.target {
-    // CHECK-NEXT: omp.terminator
-    // CHECK-NEXT: }
-    // CHECK-NOT: omp.target_unreachable
-    omp.target {
-      omp.terminator
-    }
-  }
-  return
-}
-
-// -----
-
-// Test for multiple predecessors - one reachable, one unreachable
-// The block should NOT be marked as unreachable if ANY path is reachable
-// CHECK-LABEL: func.func @test_multiple_predecessors
-func.func @test_multiple_predecessors() {
-  %false = arith.constant false
-  cf.cond_br %false, ^bb2, ^bb1
-^bb1:
-  // Reachable path to bb2
-  cf.br ^bb2
-^bb2:
-  // This block has two predecessors:
-  // - bb0 with false condition (unreachable path)
-  // - bb1 with unconditional branch (reachable path)
-  // Target should NOT be marked unreachable because bb1 provides a reachable path
-  // CHECK: omp.target {
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  // CHECK-NOT: omp.target_unreachable
-  omp.target {
-    omp.terminator
-  }
-  return
-}
-
-// -----
-
-// Test for multiple predecessors - ALL unreachable
-// CHECK-LABEL: func.func @test_multiple_predecessors_all_unreachable
-func.func @test_multiple_predecessors_all_unreachable() {
-  %false1 = arith.constant false
-  %false2 = arith.constant false
-  cf.cond_br %false1, ^bb3, ^bb1
-^bb1:
-  cf.cond_br %false2, ^bb3, ^bb2
-^bb2:
-  cf.br ^bb4
-^bb3:
-  // This block has two predecessors:
-  // - bb0 with false condition to bb3 (unreachable)
-  // - bb1 with false condition to bb3 (unreachable)
-  // Target SHOULD be marked unreachable because ALL paths are unreachable
-  // CHECK: omp.target
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: } {omp.target_unreachable}
-  omp.target {
-    omp.terminator
-  }
-  cf.br ^bb4
-^bb4:
-  return
-}
-
-// -----
-
-// Test for multiple predecessors with mixed constant and runtime conditions
-// CHECK-LABEL: func.func @test_multiple_predecessors_mixed
-func.func @test_multiple_predecessors_mixed(%arg0: i1) {
-  %false = arith.constant false
-  cf.cond_br %false, ^bb2, ^bb1
-^bb1:
-  // Runtime condition - could branch to bb2
-  cf.cond_br %arg0, ^bb2, ^bb3
-^bb2:
-  // This block has two predecessors:
-  // - bb0 with false condition (unreachable)
-  // - bb1 with runtime condition (potentially reachable)
-  // Target should NOT be marked because we can't prove bb1 path is unreachable
-  // CHECK: omp.target {
-  // CHECK-NEXT: omp.terminator
-  // CHECK-NEXT: }
-  // CHECK-NOT: omp.target_unreachable
-  omp.target {
-    omp.terminator
-  }
-  cf.br ^bb3
-^bb3:
-  return
-}
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index acb6145628799..9f92964a6b80f 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -6371,10 +6371,6 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
                  LLVM::ModuleTranslation &moduleTranslation) {
   auto targetOp = cast<omp::TargetOp>(opInst);
 
-  // Skip target operations marked as unreachable
-  if (targetOp->hasAttr("omp.target_unreachable"))
-    return success();
-
   // The current debug location already has the DISubprogram for the outlined
   // function that will be created for the target op. We save it here so that
   // we can set it on the outlined function.
diff --git a/mlir/test/Target/LLVMIR/omptarget-unreachable-region.mlir b/mlir/test/Target/LLVMIR/omptarget-unreachable-region.mlir
deleted file mode 100644
index b162d7742fb56..0000000000000
--- a/mlir/test/Target/LLVMIR/omptarget-unreachable-region.mlir
+++ /dev/null
@@ -1,21 +0,0 @@
-// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
-
-// Test that omp.target operations marked with omp.target_unreachable
-// do not generate any kernel code in LLVM IR
-
-module attributes {omp.is_target_device = false} {
-  // Test 1: Target with unreachable attribute - should generate NO kernel code
-  llvm.func @test_unreachable_target() {
-    %0 = llvm.mlir.constant(1 : i64) : i64
-    %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr
-    %map = omp.map.info var_ptr(%1 : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr {name = "a"}
-    omp.target map_entries(%map -> %arg0 : !llvm.ptr) {
-      %2 = llvm.mlir.constant(42 : i32) : i32
-      llvm.store %2, %arg0 : i32, !llvm.ptr
-      omp.terminator
-    } {omp.target_unreachable}
-    llvm.return
-  }
-}
-
-// CHECK-NOT: @__omp_offloading_{{.*}}_test_unreachable_target

>From cde4023eac971cee62d41def38c40577fe560a86 Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Tue, 10 Feb 2026 22:03:29 +0000
Subject: [PATCH 3/3] Handle review comments (2).

Use DeadCodeAnalysis instead of Dominance analysis.
---
 .../OpenMP/DeleteUnreachableTargets.cpp       | 117 +++++-------------
 1 file changed, 28 insertions(+), 89 deletions(-)

diff --git a/flang/lib/Optimizer/OpenMP/DeleteUnreachableTargets.cpp b/flang/lib/Optimizer/OpenMP/DeleteUnreachableTargets.cpp
index 4409f52bb137e..a6544ae4968a8 100644
--- a/flang/lib/Optimizer/OpenMP/DeleteUnreachableTargets.cpp
+++ b/flang/lib/Optimizer/OpenMP/DeleteUnreachableTargets.cpp
@@ -14,16 +14,15 @@
 #include "flang/Optimizer/Dialect/FIRDialect.h"
 #include "flang/Optimizer/Dialect/FIROps.h"
 #include "flang/Optimizer/OpenMP/Passes.h"
-#include "mlir/Dialect/Arith/IR/Arith.h"
-#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
+#include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h"
+#include "mlir/Analysis/DataFlow/Utils.h"
+#include "mlir/Analysis/DataFlowFramework.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
 #include "mlir/Dialect/OpenMP/OpenMPDialect.h"
 #include "mlir/IR/BuiltinOps.h"
-#include "mlir/IR/Dominance.h"
-#include "mlir/IR/Matchers.h"
 #include "mlir/Pass/Pass.h"
 #include "mlir/Support/LLVM.h"
-#include "llvm/ADT/SmallSet.h"
+#include "llvm/ADT/SmallVector.h"
 
 namespace flangomp {
 #define GEN_PASS_DEF_DELETEUNREACHABLETARGETSPASS
@@ -34,80 +33,18 @@ using namespace mlir;
 
 namespace {
 
-/// Check if an operation is unreachable from the entry block of its function.
-/// This function detects unreachability in two ways:
-/// 1. Constant conditions in structured control flow (e.g., fir.if with
-///    constant false condition)
-/// 2. Block-level unreachability (using DominanceInfo)
-static bool isOperationUnreachable(Operation *op, DominanceInfo &domInfo) {
-  // Walk up through parent operations to check for constant conditions in
-  // structured control flow (fir.if)
-  Operation *current = op;
-  while (current) {
-    Operation *parentOp = current->getParentOp();
-    if (!parentOp)
-      break;
-
-    // Check for fir.if with constant condition.
-    // This catches Fortran constructs like "if (.false.) then ... end if".
-    if (auto firIf = dyn_cast<fir::IfOp>(parentOp)) {
-      IntegerAttr constAttr;
-      if (matchPattern(firIf.getCondition(), m_Constant(&constAttr))) {
-        // If condition is false (0) and op is in the "then" region
-        if (constAttr.getInt() == 0 &&
-            current->getParentRegion() == &firIf.getThenRegion())
-          return true;
-        // If condition is true (non-zero) and op is in the "else" region
-        if (constAttr.getInt() != 0 && !firIf.getElseRegion().empty() &&
-            current->getParentRegion() == &firIf.getElseRegion())
-          return true;
-      }
-    }
-
-    // Stop at function boundary
-    if (isa<func::FuncOp>(parentOp))
-      break;
-
-    current = parentOp;
-  }
-
-  // Check block-level reachability using DominanceInfo.
-  // This detects blocks that are unreachable from the function entry due to
-  // control flow (e.g., blocks with no predecessors, disconnected blocks).
-
-  // Find the ancestor block that is in the function's main region.
-  // We need to check if that block is reachable, not just the immediate
-  // block containing the operation (which might be inside a fir.if region).
-  auto funcOp = op->getParentOfType<func::FuncOp>();
-  if (!funcOp)
+/// Check if an operation is unreachable using DeadCodeAnalysis.
+static bool isOperationUnreachable(Operation *op, DataFlowSolver &solver) {
+  Block *block = op->getBlock();
+  if (!block)
     return false;
 
-  Region *funcRegion = &funcOp.getRegion();
-  Operation *ancestor = op;
-  Block *blockInFuncRegion = nullptr;
+  // Query DeadCodeAnalysis to check if the block is live (reachable).
+  ProgramPoint *point = solver.getProgramPointBefore(block);
+  const dataflow::Executable *executable =
+      solver.lookupState<dataflow::Executable>(point);
 
-  // Walk up to find the block in the function's main region
-  while (ancestor) {
-    Block *block = ancestor->getBlock();
-    if (block && block->getParent() == funcRegion) {
-      blockInFuncRegion = block;
-      break;
-    }
-    ancestor = ancestor->getParentOp();
-    if (!ancestor || ancestor == funcOp.getOperation())
-      break;
-  }
-
-  // Check if we found a block in the function's main region and if it's
-  // reachable
-  if (blockInFuncRegion) {
-    Block *entryBlock = &funcRegion->front();
-    if (blockInFuncRegion == entryBlock)
-      return false;
-    return !domInfo.isReachableFromEntry(blockInFuncRegion);
-  }
-
-  return false;
+  return (executable && !executable->isLive());
 }
 
 class DeleteUnreachableTargetsPass
@@ -118,22 +55,24 @@ class DeleteUnreachableTargetsPass
 
   void runOnOperation() override {
     auto module = getOperation();
+    DataFlowSolver solver;
+    dataflow::loadBaselineAnalyses(solver);
 
-    module.walk([&](func::FuncOp funcOp) {
-      // Create dominance info for this function
-      DominanceInfo domInfo(funcOp);
-
-      // Collect unreachable target operations in this function
-      SmallVector<omp::TargetOp> unreachableTargets;
-      funcOp.walk([&](omp::TargetOp targetOp) {
-        if (isOperationUnreachable(targetOp.getOperation(), domInfo))
-          unreachableTargets.push_back(targetOp);
-      });
+    if (failed(solver.initializeAndRun(module))) {
+      signalPassFailure();
+      return;
+    }
 
-      // Delete unreachable target operations
-      for (omp::TargetOp targetOp : unreachableTargets)
-        targetOp->erase();
+    // Collect unreachable target operations
+    SmallVector<omp::TargetOp> unreachableTargets;
+    module.walk([&](omp::TargetOp targetOp) {
+      if (isOperationUnreachable(targetOp.getOperation(), solver))
+        unreachableTargets.push_back(targetOp);
     });
+
+    // Delete unreachable target operations
+    for (omp::TargetOp targetOp : unreachableTargets)
+      targetOp->erase();
   }
 };
 



More information about the Mlir-commits mailing list