[flang-commits] [flang] [mlir] [mlir][OpenACC] Skip implicit routine marking for host-only calls (PR #227758)

Delaram Talaashrafi via flang-commits flang-commits at lists.llvm.org
Fri Oct 2 15:50:32 PDT 2026


https://github.com/delaram-talaashrafi updated https://github.com/llvm/llvm-project/pull/227758

>From 638c959d27a05139db0715bcc61781b29fcff439 Mon Sep 17 00:00:00 2001
From: Delaram Talaashrafi <dtalaashrafi at rome5.pgi.net>
Date: Wed, 30 Sep 2026 08:38:24 -0700
Subject: [PATCH 1/5] [mlir][OpenACC] Skip implicit routine marking for
 host-only calls

Calls nested in a host-only branch of `acc.on_device` do not run on the
device. Do not try to attach implicit acc routine information to them.
---
 .../OpenACC/acc-implicit-routine.fir          | 49 ++++++++++++++++
 .../mlir/Dialect/OpenACC/OpenACCUtils.h       |  7 +++
 .../OpenACC/Transforms/ACCImplicitRoutine.cpp |  5 ++
 .../Dialect/OpenACC/Utils/OpenACCUtils.cpp    | 58 +++++++++++++++++++
 .../Dialect/OpenACC/acc-implicit-routine.mlir | 49 ++++++++++++++++
 5 files changed, 168 insertions(+)

diff --git a/flang/test/Transforms/OpenACC/acc-implicit-routine.fir b/flang/test/Transforms/OpenACC/acc-implicit-routine.fir
index 44ba96e3d6b04..cb071f5b12ac2 100644
--- a/flang/test/Transforms/OpenACC/acc-implicit-routine.fir
+++ b/flang/test/Transforms/OpenACC/acc-implicit-routine.fir
@@ -147,3 +147,52 @@ module {
     return
   }
 }
+
+// -----
+
+// Calls in a host-only branch of acc.on_device do not get an implicit routine.
+// acc_device_host = 2: the then branch is host-only.
+// acc_device_not_host = 3: the else branch is host-only.
+module {
+  func.func @_QPhost_then() {
+    return
+  }
+  func.func @_QPdevice_else() {
+    return
+  }
+  func.func @_QPdevice_then() {
+    return
+  }
+  func.func @_QPhost_else() {
+    return
+  }
+  func.func @_QPtest_host_branch() {
+    %host = arith.constant 2 : i32
+    %not_host = arith.constant 3 : i32
+    %on_host = acc.on_device %host : i32 -> i1
+    %on_not_host = acc.on_device %not_host : i32 -> i1
+    acc.serial {
+      fir.if %on_host {
+        fir.call @_QPhost_then() : () -> ()
+      } else {
+        fir.call @_QPdevice_else() : () -> ()
+      }
+      fir.if %on_not_host {
+        fir.call @_QPdevice_then() : () -> ()
+      } else {
+        fir.call @_QPhost_else() : () -> ()
+      }
+      acc.yield
+    }
+    return
+  }
+}
+
+// CHECK-NOT: acc.routine @{{.*}} func(@_QPhost_then)
+// CHECK-NOT: acc.routine @{{.*}} func(@_QPhost_else)
+// CHECK: acc.routine @acc_routine_0 func(@_QPdevice_else) implicit
+// CHECK: func.func @_QPdevice_else() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_0]>}
+// CHECK: acc.routine @acc_routine_1 func(@_QPdevice_then) implicit
+// CHECK: func.func @_QPdevice_then() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_1]>}
+// CHECK-NOT: acc.routine @{{.*}} func(@_QPhost_then)
+// CHECK-NOT: acc.routine @{{.*}} func(@_QPhost_else)
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
index 2d8780ecf4b16..fc16d2726164c 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
@@ -140,6 +140,13 @@ getDominatingDataClauses(mlir::Operation *computeConstructOp,
                          mlir::DominanceInfo &domInfo,
                          mlir::PostDominanceInfo &postDomInfo);
 
+/// Returns true if `op` is nested in a branch that executes only on the host.
+///
+/// The branch is the `then` of a condition equivalent to
+/// `acc.on_device(acc_device_host)`, or the `else` of a condition equivalent
+/// to `acc.on_device(acc_device_not_host)`.
+bool isInHostBranch(mlir::Operation *op);
+
 /// Emit an OpenACC remark with lazy message generation.
 ///
 /// The messageFn is only invoked if remarks are enabled, allowing callers
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
index 16b71189a4f94..2592903cf3066 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
@@ -49,6 +49,7 @@
 
 #include "mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h"
 #include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "mlir/Dialect/OpenACC/OpenACCUtils.h"
 #include "mlir/IR/Builders.h"
 #include "mlir/IR/BuiltinAttributes.h"
 #include "mlir/IR/BuiltinOps.h"
@@ -139,6 +140,8 @@ class ACCImplicitRoutine
                                           acc::OpenACCSupport &accSupport) {
     LogicalResult result = success();
     op->walk([&](CallOpInterface callOp) {
+      if (acc::isInHostBranch(callOp.getOperation()))
+        return;
       if (!callOp.getCallableForCallee())
         return;
 
@@ -196,6 +199,8 @@ class ACCImplicitRoutine
       auto func = symTab.lookup<FunctionOpInterface>(
           currentRoutine.getFuncName().getLeafReference());
       func.walk([&](CallOpInterface callOp) {
+        if (acc::isInHostBranch(callOp.getOperation()))
+          return;
         if (!callOp.getCallableForCallee())
           return;
 
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
index 268bbea3bee8f..5da987cd3df99 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
@@ -8,8 +8,10 @@
 
 #include "mlir/Dialect/OpenACC/OpenACCUtils.h"
 
+#include "mlir/Analysis/SliceAnalysis.h"
 #include "mlir/Dialect/GPU/IR/GPUDialect.h"
 #include "mlir/Dialect/OpenACC/OpenACC.h"
+#include "mlir/Dialect/SCF/IR/SCF.h"
 #include "mlir/Dialect/Utils/StaticValueUtils.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/IR/BuiltinTypes.h"
@@ -251,6 +253,7 @@ bool mlir::acc::isValidSymbolUse(mlir::Operation *user,
   // Check if the defining op is a function
   if (auto func =
           mlir::dyn_cast_if_present<mlir::FunctionOpInterface>(definingOp)) {
+
     // If this symbol is actually an acc routine or a specialized acc routine -
     // then it is expected for it to be offloaded - therefore it is valid.
     if (func->hasDiscardableAttr(mlir::acc::getRoutineInfoAttrName()) ||
@@ -455,6 +458,61 @@ mlir::acc::getDominatingDataClauses(mlir::Operation *computeConstructOp,
   return dominatingDataClauses.takeVector();
 }
 
+// OpenACC `acc_device_t` values passed to `acc.on_device`. These differ from
+// `acc::DeviceType`.
+static constexpr int64_t kAccDeviceHost = 2;
+static constexpr int64_t kAccDeviceNotHost = 3;
+
+static mlir::Value getIfCondition(mlir::Operation *op) {
+  if (auto ifOp = mlir::dyn_cast<mlir::scf::IfOp>(op))
+    return ifOp.getCondition();
+
+  if (!mlir::isa<mlir::RegionBranchOpInterface>(op) ||
+      mlir::isa<mlir::LoopLikeOpInterface>(op))
+    return {};
+  if (op->getNumOperands() != 1 || op->getNumRegions() != 2 ||
+      !op->getOperand(0).getType().isSignlessInteger(1))
+    return {};
+  return op->getOperand(0);
+}
+
+bool mlir::acc::isInHostBranch(mlir::Operation *op) {
+  for (mlir::Operation *parent = op->getParentOp();
+       parent &&
+       !mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::ComputeRegionOp,
+                  mlir::FunctionOpInterface>(parent);
+       parent = parent->getParentOp()) {
+    mlir::Value condition = getIfCondition(parent);
+    if (!condition)
+      continue;
+
+    // Include the condition's defining operation so a direct `acc.on_device`
+    // result is part of the slice.
+    mlir::BackwardSliceOptions sliceOptions;
+    sliceOptions.inclusive = true;
+    sliceOptions.omitBlockArguments = true;
+    llvm::SetVector<mlir::Operation *> slice;
+    if (failed(mlir::getBackwardSlice(condition, &slice, sliceOptions)))
+      continue;
+
+    for (mlir::Operation *sliceOp : slice) {
+      auto onDeviceOp = mlir::dyn_cast<mlir::acc::OnDeviceOp>(sliceOp);
+      if (!onDeviceOp)
+        continue;
+
+      int64_t deviceTypeValue =
+          *mlir::getConstantIntValue(onDeviceOp.getDeviceType());
+
+      bool inThen = parent->getRegion(0).isAncestor(op->getParentRegion());
+      bool inElse = parent->getRegion(1).isAncestor(op->getParentRegion());
+      if ((deviceTypeValue == kAccDeviceHost && inThen) ||
+          (deviceTypeValue == kAccDeviceNotHost && inElse))
+        return true;
+    }
+  }
+  return false;
+}
+
 mlir::remark::detail::InFlightRemark
 mlir::acc::emitRemark(mlir::Operation *op,
                       const std::function<std::string()> &messageFn,
diff --git a/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir b/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir
index cef8fc663291e..da93abdbffc98 100644
--- a/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir
+++ b/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir
@@ -299,3 +299,52 @@ module {
     return
   }
 }
+
+// -----
+
+// Calls in a host-only branch of acc.on_device do not get an implicit routine.
+// acc_device_host = 2: the then branch is host-only.
+// acc_device_not_host = 3: the else branch is host-only.
+module {
+  func.func @host_then() {
+    return
+  }
+  func.func @device_else() {
+    return
+  }
+  func.func @device_then() {
+    return
+  }
+  func.func @host_else() {
+    return
+  }
+  func.func @test_host_branch_in_compute() {
+    %host = arith.constant 2 : i32
+    %not_host = arith.constant 3 : i32
+    %on_host = acc.on_device %host : i32 -> i1
+    %on_not_host = acc.on_device %not_host : i32 -> i1
+    acc.serial {
+      scf.if %on_host {
+        func.call @host_then() : () -> ()
+      } else {
+        func.call @device_else() : () -> ()
+      }
+      scf.if %on_not_host {
+        func.call @device_then() : () -> ()
+      } else {
+        func.call @host_else() : () -> ()
+      }
+      acc.yield
+    }
+    return
+  }
+}
+
+// CHECK-NOT: acc.routine @{{.*}} func(@host_then)
+// CHECK-NOT: acc.routine @{{.*}} func(@host_else)
+// CHECK: acc.routine @acc_routine_0 func(@device_else) implicit
+// CHECK: func.func @device_else() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_0]>}
+// CHECK: acc.routine @acc_routine_1 func(@device_then) implicit
+// CHECK: func.func @device_then() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_1]>}
+// CHECK-NOT: acc.routine @{{.*}} func(@host_then)
+// CHECK-NOT: acc.routine @{{.*}} func(@host_else)

>From e69ceaacb0caafb98f254e72bc52ea199e5fcad7 Mon Sep 17 00:00:00 2001
From: Delaram Talaashrafi <dtalaashrafi at rome5.pgi.net>
Date: Thu, 1 Oct 2026 13:52:56 -0700
Subject: [PATCH 2/5] Reviews

---
 .../OpenACC/acc-implicit-routine.fir          |  8 +++---
 .../mlir/Dialect/OpenACC/OpenACCUtils.h       | 13 ++++++----
 .../mlir/Dialect/OpenACC/Transforms/Passes.td | 20 ++++++++++----
 .../OpenACC/Transforms/ACCImplicitRoutine.cpp |  8 ++++--
 .../Dialect/OpenACC/Utils/OpenACCUtils.cpp    | 20 +++++++-------
 .../Dialect/OpenACC/acc-implicit-routine.mlir | 26 ++++++++++++++++---
 6 files changed, 65 insertions(+), 30 deletions(-)

diff --git a/flang/test/Transforms/OpenACC/acc-implicit-routine.fir b/flang/test/Transforms/OpenACC/acc-implicit-routine.fir
index cb071f5b12ac2..b12fea9ec64c3 100644
--- a/flang/test/Transforms/OpenACC/acc-implicit-routine.fir
+++ b/flang/test/Transforms/OpenACC/acc-implicit-routine.fir
@@ -1,4 +1,4 @@
-// RUN: fir-opt %s --pass-pipeline="builtin.module(acc-initialize-fir-analyses,acc-implicit-routine)" -split-input-file -verify-diagnostics | FileCheck %s
+// RUN: fir-opt %s --pass-pipeline="builtin.module(acc-initialize-fir-analyses,acc-implicit-routine{the-device-types=3,4})" -split-input-file -verify-diagnostics | FileCheck %s
 
 // -----
 
@@ -150,9 +150,9 @@ module {
 
 // -----
 
-// Calls in a host-only branch of acc.on_device do not get an implicit routine.
-// acc_device_host = 2: the then branch is host-only.
-// acc_device_not_host = 3: the else branch is host-only.
+// Calls that do not execute for the-device-types=3,4 do not get an implicit
+// routine. 3 is acc_device_not_host and 4 is acc_device_nvidia, so the then
+// of acc_on_device(2) and the else of acc_on_device(3) stay off the target.
 module {
   func.func @_QPhost_then() {
     return
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
index fc16d2726164c..f7a6339b2e928 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
@@ -140,12 +140,15 @@ getDominatingDataClauses(mlir::Operation *computeConstructOp,
                          mlir::DominanceInfo &domInfo,
                          mlir::PostDominanceInfo &postDomInfo);
 
-/// Returns true if `op` is nested in a branch that executes only on the host.
+/// Returns true if `op` is nested in a branch that does not execute when
+/// `acc.on_device` is true only for `deviceTypes`.
 ///
-/// The branch is the `then` of a condition equivalent to
-/// `acc.on_device(acc_device_host)`, or the `else` of a condition equivalent
-/// to `acc.on_device(acc_device_not_host)`.
-bool isInHostBranch(mlir::Operation *op);
+/// `deviceTypes` holds the runtime `acc_device_t` values for which
+/// `acc.on_device` is true on the target. The branch is the `then` of
+/// `acc.on_device` whose operand is outside that list, or the `else` of
+/// `acc.on_device` whose operand is in the list. A non-constant operand is
+/// ignored. An empty list means no `acc_device_t` value is true on the target.
+bool isInHostBranch(mlir::Operation *op, llvm::ArrayRef<int64_t> deviceTypes);
 
 /// Emit an OpenACC remark with lazy message generation.
 ///
diff --git a/mlir/include/mlir/Dialect/OpenACC/Transforms/Passes.td b/mlir/include/mlir/Dialect/OpenACC/Transforms/Passes.td
index 9a81070200e77..eeb963d7f69f8 100644
--- a/mlir/include/mlir/Dialect/OpenACC/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/OpenACC/Transforms/Passes.td
@@ -27,6 +27,12 @@ def AccDeviceTypeOption : Option<"deviceType", "device-type",
        clEnumValN(mlir::acc::DeviceType::Radeon, "radeon", "radeon"))
     }]>;
 
+// Runtime `acc_device_t` values for which `acc.on_device` is true on the
+// target device. These values are the integers carried by `acc.on_device`.
+def AccTheDeviceTypesOption : ListOption<"theDeviceTypes", "the-device-types",
+    "int64_t",
+    "Runtime acc_device_t values for which acc.on_device is true on the target">;
+
 //===----------------------------------------------------------------------===//
 // Pass definitions
 //===----------------------------------------------------------------------===//
@@ -143,8 +149,15 @@ def ACCImplicitRoutine : Pass<"acc-implicit-routine", "mlir::ModuleOp"> {
     3. Recursively walking through all existing `acc routine` and creating
        implicit routine operations for function calls within these routines,
        while avoiding infinite recursion through proper tracking.
+
+    Calls in a branch that does not execute on the target device are skipped.
+    `the-device-types` lists the runtime `acc_device_t` values for which
+    `acc.on_device` is true on that device. A call in the `then` of
+    `acc.on_device` whose operand is outside the list, or in the `else` of
+    `acc.on_device` whose operand is in the list, does not get an implicit
+    routine. An empty list means no `acc_device_t` value is true on the target.
   }];
-  let options = [ AccDeviceTypeOption ];
+  let options = [ AccDeviceTypeOption, AccTheDeviceTypesOption ];
 }
 
 def ACCDeclareGPUModuleInsertion : Pass<"acc-declare-gpu-module-insertion", "mlir::ModuleOp"> {
@@ -317,10 +330,7 @@ def ACCSpecializeForDevice : Pass<"acc-specialize-for-device", "mlir::func::Func
     - Runtime ops (erased): acc.init, acc.shutdown, acc.set, acc.wait
                   (folded if known): acc.on_device
   }];
-  let options = [
-    ListOption<"theDeviceTypes", "the-device-types", "int64_t",
-               "List of integers which represent the device we are specializing for">
-  ];
+  let options = [ AccTheDeviceTypesOption ];
 }
 
 def ACCSpecializeForHost : Pass<"acc-specialize-for-host", "mlir::func::FuncOp"> {
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
index 2592903cf3066..2be79b361e1b2 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
@@ -32,6 +32,10 @@
 //    implicit routine operations for function calls within these routines,
 //    while avoiding infinite recursion through proper tracking.
 //
+// Calls in a branch that does not execute for `the-device-types` are skipped.
+// That list holds the runtime `acc_device_t` values for which `acc.on_device`
+// is true on the target.
+//
 // Requirements:
 // -------------
 // To use this pass in a pipeline, the following requirements must be met:
@@ -140,7 +144,7 @@ class ACCImplicitRoutine
                                           acc::OpenACCSupport &accSupport) {
     LogicalResult result = success();
     op->walk([&](CallOpInterface callOp) {
-      if (acc::isInHostBranch(callOp.getOperation()))
+      if (acc::isInHostBranch(callOp.getOperation(), *theDeviceTypes))
         return;
       if (!callOp.getCallableForCallee())
         return;
@@ -199,7 +203,7 @@ class ACCImplicitRoutine
       auto func = symTab.lookup<FunctionOpInterface>(
           currentRoutine.getFuncName().getLeafReference());
       func.walk([&](CallOpInterface callOp) {
-        if (acc::isInHostBranch(callOp.getOperation()))
+        if (acc::isInHostBranch(callOp.getOperation(), *theDeviceTypes))
           return;
         if (!callOp.getCallableForCallee())
           return;
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
index 5da987cd3df99..2a387f40f9a8d 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
@@ -458,11 +458,6 @@ mlir::acc::getDominatingDataClauses(mlir::Operation *computeConstructOp,
   return dominatingDataClauses.takeVector();
 }
 
-// OpenACC `acc_device_t` values passed to `acc.on_device`. These differ from
-// `acc::DeviceType`.
-static constexpr int64_t kAccDeviceHost = 2;
-static constexpr int64_t kAccDeviceNotHost = 3;
-
 static mlir::Value getIfCondition(mlir::Operation *op) {
   if (auto ifOp = mlir::dyn_cast<mlir::scf::IfOp>(op))
     return ifOp.getCondition();
@@ -476,7 +471,8 @@ static mlir::Value getIfCondition(mlir::Operation *op) {
   return op->getOperand(0);
 }
 
-bool mlir::acc::isInHostBranch(mlir::Operation *op) {
+bool mlir::acc::isInHostBranch(mlir::Operation *op,
+                               llvm::ArrayRef<int64_t> deviceTypes) {
   for (mlir::Operation *parent = op->getParentOp();
        parent &&
        !mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::ComputeRegionOp,
@@ -500,13 +496,17 @@ bool mlir::acc::isInHostBranch(mlir::Operation *op) {
       if (!onDeviceOp)
         continue;
 
-      int64_t deviceTypeValue =
-          *mlir::getConstantIntValue(onDeviceOp.getDeviceType());
+      std::optional<int64_t> deviceTypeValue =
+          mlir::getConstantIntValue(onDeviceOp.getDeviceType());
+      if (!deviceTypeValue)
+        continue;
 
+      bool onTarget = llvm::is_contained(deviceTypes, *deviceTypeValue);
       bool inThen = parent->getRegion(0).isAncestor(op->getParentRegion());
       bool inElse = parent->getRegion(1).isAncestor(op->getParentRegion());
-      if ((deviceTypeValue == kAccDeviceHost && inThen) ||
-          (deviceTypeValue == kAccDeviceNotHost && inElse))
+      // Off the target: the then of a device type outside `deviceTypes`, or
+      // the else of a device type in `deviceTypes`.
+      if ((!onTarget && inThen) || (onTarget && inElse))
         return true;
     }
   }
diff --git a/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir b/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir
index da93abdbffc98..e3e9bef787dcd 100644
--- a/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir
+++ b/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir
@@ -1,4 +1,4 @@
-// RUN: mlir-opt %s -acc-implicit-routine -split-input-file -verify-diagnostics | FileCheck %s
+// RUN: mlir-opt %s -acc-implicit-routine=the-device-types=3,4 -split-input-file -verify-diagnostics | FileCheck %s
 
 // -----
 
@@ -302,9 +302,10 @@ module {
 
 // -----
 
-// Calls in a host-only branch of acc.on_device do not get an implicit routine.
-// acc_device_host = 2: the then branch is host-only.
-// acc_device_not_host = 3: the else branch is host-only.
+// Calls that do not execute for the-device-types=3,4 do not get an implicit
+// routine. 3 is acc_device_not_host and 4 is acc_device_nvidia, so:
+//   acc_on_device(2) then, acc_on_device(3) else, and acc_on_device(5) then
+// stay off the target. The else of acc_on_device(5) still executes on it.
 module {
   func.func @host_then() {
     return
@@ -318,11 +319,19 @@ module {
   func.func @host_else() {
     return
   }
+  func.func @other_device_then() {
+    return
+  }
+  func.func @other_device_else() {
+    return
+  }
   func.func @test_host_branch_in_compute() {
     %host = arith.constant 2 : i32
     %not_host = arith.constant 3 : i32
+    %radeon = arith.constant 5 : i32
     %on_host = acc.on_device %host : i32 -> i1
     %on_not_host = acc.on_device %not_host : i32 -> i1
+    %on_radeon = acc.on_device %radeon : i32 -> i1
     acc.serial {
       scf.if %on_host {
         func.call @host_then() : () -> ()
@@ -334,6 +343,11 @@ module {
       } else {
         func.call @host_else() : () -> ()
       }
+      scf.if %on_radeon {
+        func.call @other_device_then() : () -> ()
+      } else {
+        func.call @other_device_else() : () -> ()
+      }
       acc.yield
     }
     return
@@ -342,9 +356,13 @@ module {
 
 // CHECK-NOT: acc.routine @{{.*}} func(@host_then)
 // CHECK-NOT: acc.routine @{{.*}} func(@host_else)
+// CHECK-NOT: acc.routine @{{.*}} func(@other_device_then)
 // CHECK: acc.routine @acc_routine_0 func(@device_else) implicit
 // CHECK: func.func @device_else() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_0]>}
 // CHECK: acc.routine @acc_routine_1 func(@device_then) implicit
 // CHECK: func.func @device_then() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_1]>}
 // CHECK-NOT: acc.routine @{{.*}} func(@host_then)
 // CHECK-NOT: acc.routine @{{.*}} func(@host_else)
+// CHECK-NOT: acc.routine @{{.*}} func(@other_device_then)
+// CHECK: acc.routine @acc_routine_2 func(@other_device_else) implicit
+// CHECK: func.func @other_device_else() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_2]>}

>From fd52af469af61051eaa1b6d6a48d0c30e3f9b455 Mon Sep 17 00:00:00 2001
From: Delaram Talaashrafi <dtalaashrafi at rome5.pgi.net>
Date: Fri, 2 Oct 2026 08:36:51 -0700
Subject: [PATCH 3/5] Fix

---
 .../mlir/Dialect/OpenACC/OpenACCUtils.h       | 14 +++----
 .../mlir/Dialect/OpenACC/Transforms/Passes.td |  7 ----
 .../Dialect/OpenACC/Utils/OpenACCUtils.cpp    | 41 +++++++------------
 3 files changed, 21 insertions(+), 41 deletions(-)

diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
index f7a6339b2e928..4d7cc3da0dd84 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
@@ -140,14 +140,12 @@ getDominatingDataClauses(mlir::Operation *computeConstructOp,
                          mlir::DominanceInfo &domInfo,
                          mlir::PostDominanceInfo &postDomInfo);
 
-/// Returns true if `op` is nested in a branch that does not execute when
-/// `acc.on_device` is true only for `deviceTypes`.
-///
-/// `deviceTypes` holds the runtime `acc_device_t` values for which
-/// `acc.on_device` is true on the target. The branch is the `then` of
-/// `acc.on_device` whose operand is outside that list, or the `else` of
-/// `acc.on_device` whose operand is in the list. A non-constant operand is
-/// ignored. An empty list means no `acc_device_t` value is true on the target.
+/// Returns true if `op` is in a branch that does not execute on the target.
+/// `deviceTypes` lists the `acc_device_t` values for which `acc.on_device` is
+/// true. That branch is the `then` of a constant `acc.on_device` outside the
+/// list, or the `else` of one in the list. A non-constant operand is ignored.
+/// An empty list makes every such condition false, so only its `then` branch
+/// is off the target.
 bool isInHostBranch(mlir::Operation *op, llvm::ArrayRef<int64_t> deviceTypes);
 
 /// Emit an OpenACC remark with lazy message generation.
diff --git a/mlir/include/mlir/Dialect/OpenACC/Transforms/Passes.td b/mlir/include/mlir/Dialect/OpenACC/Transforms/Passes.td
index eeb963d7f69f8..202028994003d 100644
--- a/mlir/include/mlir/Dialect/OpenACC/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/OpenACC/Transforms/Passes.td
@@ -149,13 +149,6 @@ def ACCImplicitRoutine : Pass<"acc-implicit-routine", "mlir::ModuleOp"> {
     3. Recursively walking through all existing `acc routine` and creating
        implicit routine operations for function calls within these routines,
        while avoiding infinite recursion through proper tracking.
-
-    Calls in a branch that does not execute on the target device are skipped.
-    `the-device-types` lists the runtime `acc_device_t` values for which
-    `acc.on_device` is true on that device. A call in the `then` of
-    `acc.on_device` whose operand is outside the list, or in the `else` of
-    `acc.on_device` whose operand is in the list, does not get an implicit
-    routine. An empty list means no `acc_device_t` value is true on the target.
   }];
   let options = [ AccDeviceTypeOption, AccTheDeviceTypesOption ];
 }
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
index 2a387f40f9a8d..9089a3a9e11b9 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
@@ -8,7 +8,6 @@
 
 #include "mlir/Dialect/OpenACC/OpenACCUtils.h"
 
-#include "mlir/Analysis/SliceAnalysis.h"
 #include "mlir/Dialect/GPU/IR/GPUDialect.h"
 #include "mlir/Dialect/OpenACC/OpenACC.h"
 #include "mlir/Dialect/SCF/IR/SCF.h"
@@ -482,33 +481,23 @@ bool mlir::acc::isInHostBranch(mlir::Operation *op,
     if (!condition)
       continue;
 
-    // Include the condition's defining operation so a direct `acc.on_device`
-    // result is part of the slice.
-    mlir::BackwardSliceOptions sliceOptions;
-    sliceOptions.inclusive = true;
-    sliceOptions.omitBlockArguments = true;
-    llvm::SetVector<mlir::Operation *> slice;
-    if (failed(mlir::getBackwardSlice(condition, &slice, sliceOptions)))
+    // The condition must be the `acc.on_device` result itself.
+    auto onDeviceOp = condition.getDefiningOp<mlir::acc::OnDeviceOp>();
+    if (!onDeviceOp)
       continue;
 
-    for (mlir::Operation *sliceOp : slice) {
-      auto onDeviceOp = mlir::dyn_cast<mlir::acc::OnDeviceOp>(sliceOp);
-      if (!onDeviceOp)
-        continue;
-
-      std::optional<int64_t> deviceTypeValue =
-          mlir::getConstantIntValue(onDeviceOp.getDeviceType());
-      if (!deviceTypeValue)
-        continue;
-
-      bool onTarget = llvm::is_contained(deviceTypes, *deviceTypeValue);
-      bool inThen = parent->getRegion(0).isAncestor(op->getParentRegion());
-      bool inElse = parent->getRegion(1).isAncestor(op->getParentRegion());
-      // Off the target: the then of a device type outside `deviceTypes`, or
-      // the else of a device type in `deviceTypes`.
-      if ((!onTarget && inThen) || (onTarget && inElse))
-        return true;
-    }
+    std::optional<int64_t> deviceTypeValue =
+        mlir::getConstantIntValue(onDeviceOp.getDeviceType());
+    if (!deviceTypeValue)
+      continue;
+
+    bool onTarget = llvm::is_contained(deviceTypes, *deviceTypeValue);
+    bool inThen = parent->getRegion(0).isAncestor(op->getParentRegion());
+    bool inElse = parent->getRegion(1).isAncestor(op->getParentRegion());
+    // Off the target: the then of a device type outside `deviceTypes`, or
+    // the else of a device type in `deviceTypes`.
+    if ((!onTarget && inThen) || (onTarget && inElse))
+      return true;
   }
   return false;
 }

>From 925e7dd31d9b14971258e9fd9c632114ea648594 Mon Sep 17 00:00:00 2001
From: Delaram Talaashrafi <dtalaashrafi at rome5.pgi.net>
Date: Fri, 2 Oct 2026 12:35:37 -0700
Subject: [PATCH 4/5] Empty list

---
 mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h | 3 +--
 mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp  | 4 ++++
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
index 4d7cc3da0dd84..2539c54cba445 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
@@ -144,8 +144,7 @@ getDominatingDataClauses(mlir::Operation *computeConstructOp,
 /// `deviceTypes` lists the `acc_device_t` values for which `acc.on_device` is
 /// true. That branch is the `then` of a constant `acc.on_device` outside the
 /// list, or the `else` of one in the list. A non-constant operand is ignored.
-/// An empty list makes every such condition false, so only its `then` branch
-/// is off the target.
+/// An empty list returns false.
 bool isInHostBranch(mlir::Operation *op, llvm::ArrayRef<int64_t> deviceTypes);
 
 /// Emit an OpenACC remark with lazy message generation.
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
index 9089a3a9e11b9..12f6e19b9a5bc 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
@@ -472,6 +472,10 @@ static mlir::Value getIfCondition(mlir::Operation *op) {
 
 bool mlir::acc::isInHostBranch(mlir::Operation *op,
                                llvm::ArrayRef<int64_t> deviceTypes) {
+  // No device type is known to be true, so no branch can be classified.
+  if (deviceTypes.empty())
+    return false;
+
   for (mlir::Operation *parent = op->getParentOp();
        parent &&
        !mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::ComputeRegionOp,

>From 5513c5be00e659cd5bed6e8d38d420d0174ab6af Mon Sep 17 00:00:00 2001
From: Delaram Talaashrafi <dtalaashrafi at rome5.pgi.net>
Date: Fri, 2 Oct 2026 15:49:54 -0700
Subject: [PATCH 5/5] Fix

---
 .../OpenACC/acc-implicit-routine.fir          |  3 --
 .../mlir/Dialect/OpenACC/OpenACCUtils.h       |  2 +-
 .../OpenACC/Transforms/ACCImplicitRoutine.cpp |  4 +-
 .../Dialect/OpenACC/Utils/OpenACCUtils.cpp    |  3 +-
 .../Dialect/OpenACC/acc-implicit-routine.mlir | 49 +++++++++++++++++++
 5 files changed, 53 insertions(+), 8 deletions(-)

diff --git a/flang/test/Transforms/OpenACC/acc-implicit-routine.fir b/flang/test/Transforms/OpenACC/acc-implicit-routine.fir
index b12fea9ec64c3..9a0b358379a64 100644
--- a/flang/test/Transforms/OpenACC/acc-implicit-routine.fir
+++ b/flang/test/Transforms/OpenACC/acc-implicit-routine.fir
@@ -150,9 +150,6 @@ module {
 
 // -----
 
-// Calls that do not execute for the-device-types=3,4 do not get an implicit
-// routine. 3 is acc_device_not_host and 4 is acc_device_nvidia, so the then
-// of acc_on_device(2) and the else of acc_on_device(3) stay off the target.
 module {
   func.func @_QPhost_then() {
     return
diff --git a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
index 2539c54cba445..79aff45314626 100644
--- a/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
+++ b/mlir/include/mlir/Dialect/OpenACC/OpenACCUtils.h
@@ -145,7 +145,7 @@ getDominatingDataClauses(mlir::Operation *computeConstructOp,
 /// true. That branch is the `then` of a constant `acc.on_device` outside the
 /// list, or the `else` of one in the list. A non-constant operand is ignored.
 /// An empty list returns false.
-bool isInHostBranch(mlir::Operation *op, llvm::ArrayRef<int64_t> deviceTypes);
+bool isInOffTargetBranch(mlir::Operation *op, llvm::ArrayRef<int64_t> deviceTypes);
 
 /// Emit an OpenACC remark with lazy message generation.
 ///
diff --git a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
index 2be79b361e1b2..c5a4e35616f86 100644
--- a/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
+++ b/mlir/lib/Dialect/OpenACC/Transforms/ACCImplicitRoutine.cpp
@@ -144,7 +144,7 @@ class ACCImplicitRoutine
                                           acc::OpenACCSupport &accSupport) {
     LogicalResult result = success();
     op->walk([&](CallOpInterface callOp) {
-      if (acc::isInHostBranch(callOp.getOperation(), *theDeviceTypes))
+      if (acc::isInOffTargetBranch(callOp.getOperation(), *theDeviceTypes))
         return;
       if (!callOp.getCallableForCallee())
         return;
@@ -203,7 +203,7 @@ class ACCImplicitRoutine
       auto func = symTab.lookup<FunctionOpInterface>(
           currentRoutine.getFuncName().getLeafReference());
       func.walk([&](CallOpInterface callOp) {
-        if (acc::isInHostBranch(callOp.getOperation(), *theDeviceTypes))
+        if (acc::isInOffTargetBranch(callOp.getOperation(), *theDeviceTypes))
           return;
         if (!callOp.getCallableForCallee())
           return;
diff --git a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
index 12f6e19b9a5bc..6ee4669207039 100644
--- a/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
+++ b/mlir/lib/Dialect/OpenACC/Utils/OpenACCUtils.cpp
@@ -252,7 +252,6 @@ bool mlir::acc::isValidSymbolUse(mlir::Operation *user,
   // Check if the defining op is a function
   if (auto func =
           mlir::dyn_cast_if_present<mlir::FunctionOpInterface>(definingOp)) {
-
     // If this symbol is actually an acc routine or a specialized acc routine -
     // then it is expected for it to be offloaded - therefore it is valid.
     if (func->hasDiscardableAttr(mlir::acc::getRoutineInfoAttrName()) ||
@@ -470,7 +469,7 @@ static mlir::Value getIfCondition(mlir::Operation *op) {
   return op->getOperand(0);
 }
 
-bool mlir::acc::isInHostBranch(mlir::Operation *op,
+bool mlir::acc::isInOffTargetBranch(mlir::Operation *op,
                                llvm::ArrayRef<int64_t> deviceTypes) {
   // No device type is known to be true, so no branch can be classified.
   if (deviceTypes.empty())
diff --git a/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir b/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir
index e3e9bef787dcd..0f306509b0896 100644
--- a/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir
+++ b/mlir/test/Dialect/OpenACC/acc-implicit-routine.mlir
@@ -1,4 +1,5 @@
 // RUN: mlir-opt %s -acc-implicit-routine=the-device-types=3,4 -split-input-file -verify-diagnostics | FileCheck %s
+// RUN: mlir-opt %s -acc-implicit-routine -split-input-file -verify-diagnostics | FileCheck %s --check-prefix=NODEVICE
 
 // -----
 
@@ -366,3 +367,51 @@ module {
 // CHECK-NOT: acc.routine @{{.*}} func(@other_device_then)
 // CHECK: acc.routine @acc_routine_2 func(@other_device_else) implicit
 // CHECK: func.func @other_device_else() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_2]>}
+
+// -----
+
+// With no the-device-types, no branch is classified as off the target, so both
+// sides of acc.on_device get an implicit routine.
+module {
+  func.func @no_dev_host_then() {
+    return
+  }
+  func.func @no_dev_host_else() {
+    return
+  }
+  func.func @no_dev_device_then() {
+    return
+  }
+  func.func @no_dev_device_else() {
+    return
+  }
+  func.func @test_no_device_types() {
+    %host = arith.constant 2 : i32
+    %not_host = arith.constant 3 : i32
+    %on_host = acc.on_device %host : i32 -> i1
+    %on_device = acc.on_device %not_host : i32 -> i1
+    acc.serial {
+      scf.if %on_host {
+        func.call @no_dev_host_then() : () -> ()
+      } else {
+        func.call @no_dev_host_else() : () -> ()
+      }
+      scf.if %on_device {
+        func.call @no_dev_device_then() : () -> ()
+      } else {
+        func.call @no_dev_device_else() : () -> ()
+      }
+      acc.yield
+    }
+    return
+  }
+}
+
+// NODEVICE: acc.routine @acc_routine_0 func(@no_dev_host_then) implicit
+// NODEVICE: func.func @no_dev_host_then() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_0]>}
+// NODEVICE: acc.routine @acc_routine_1 func(@no_dev_host_else) implicit
+// NODEVICE: func.func @no_dev_host_else() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_1]>}
+// NODEVICE: acc.routine @acc_routine_2 func(@no_dev_device_then) implicit
+// NODEVICE: func.func @no_dev_device_then() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_2]>}
+// NODEVICE: acc.routine @acc_routine_3 func(@no_dev_device_else) implicit
+// NODEVICE: func.func @no_dev_device_else() attributes {acc.routine_info = #acc.routine_info<[@acc_routine_3]>}



More information about the flang-commits mailing list