[Mlir-commits] [flang] [mlir] [Flang][OpenMP] Don't generate code for unreachable target regions. (PR #178937)
Abid Qadeer
llvmlistbot at llvm.org
Fri Jan 30 10:31:08 PST 2026
https://github.com/abidh created https://github.com/llvm/llvm-project/pull/178937
When a target region is placed inside a constant false condition (e.g., `if (.false.)`), the dead code gets eliminated 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 following 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.
>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] [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
More information about the Mlir-commits
mailing list