[flang-commits] [flang] [llvm] [flang][cuda] Mark cuf.device_is_active as a read-only query (PR #233987)

Zhen Wang via flang-commits flang-commits at lists.llvm.org
Tue Oct 20 14:53:51 PDT 2026


https://github.com/wangzpgi updated https://github.com/llvm/llvm-project/pull/233987

>From 3d431176bd5ff16f357c37d83a0b47db9ce5165f Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 20 Oct 2026 11:28:57 -0700
Subject: [PATCH 1/2] Mark cuf.device_is_active as a read-only query

---
 flang-rt/lib/cuda/allocatable.cpp             |  3 +
 .../flang/Optimizer/Dialect/CUF/CUFOps.td     | 10 +++-
 .../test/Fir/CUDA/cuda-device-is-active.mlir  | 57 ++++++++++++++++---
 3 files changed, 61 insertions(+), 9 deletions(-)

diff --git a/flang-rt/lib/cuda/allocatable.cpp b/flang-rt/lib/cuda/allocatable.cpp
index 7ff354bbd20bd9..abd259564a2235 100644
--- a/flang-rt/lib/cuda/allocatable.cpp
+++ b/flang-rt/lib/cuda/allocatable.cpp
@@ -21,6 +21,9 @@
 
 namespace Fortran::runtime::cuda {
 
+// The compiler models the cuf.device_is_active query this backs as a read of
+// the context state and may erase an unused query, so this probe must stay
+// free of observable effects.
 static bool deviceContextTornDown() {
   // Keep cudaGetLastError transparent: consume probe-only sticky errors when
   // the slot started clean, never discarding a pre-existing user error.
diff --git a/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.td b/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.td
index 9a136df875a493..2145796e2dc0a3 100644
--- a/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.td
+++ b/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.td
@@ -438,7 +438,8 @@ def cuf_OnDeviceOp : cuf_Op<"on_device", [Pure]> {
   let assemblyFormat = "attr-dict `:` type($res)";
 }
 
-def cuf_DeviceIsActiveOp : cuf_Op<"device_is_active", []> {
+def cuf_DeviceIsActiveOp : cuf_Op<"device_is_active",
+    [MemoryEffects<[MemRead]>]> {
   let summary = "Query whether the CUDA device context is still active";
 
   let description = [{
@@ -448,6 +449,13 @@ def cuf_DeviceIsActiveOp : cuf_Op<"device_is_active", []> {
     Converted to a `_FortranACUFDeviceIsActive` runtime call on the host and
     to `false` in device code, so host-only cleanup is DCE'd from GPU
     functions.
+
+    The query is modeled as a read of the context state: it has no other
+    effect, so a `cuf.device_is_active` whose result is unused, for instance
+    once the cleanup it guarded has been removed, is dead and erased by
+    canonicalization while it is still an operation, before its conversion to
+    the runtime call. The runtime query it lowers to must stay free of
+    observable effects for this to hold.
   }];
 
   let results = (outs I1:$res);
diff --git a/flang/test/Fir/CUDA/cuda-device-is-active.mlir b/flang/test/Fir/CUDA/cuda-device-is-active.mlir
index bf032cbba2d447..935e10e8c47b81 100644
--- a/flang/test/Fir/CUDA/cuda-device-is-active.mlir
+++ b/flang/test/Fir/CUDA/cuda-device-is-active.mlir
@@ -1,9 +1,10 @@
 // RUN: fir-opt --split-input-file --cuf-convert-late %s | FileCheck %s
+// RUN: fir-opt --split-input-file --canonicalize %s | FileCheck %s --check-prefix=CANON
 
 // Host: lower to the CUDA Fortran runtime query.
-func.func @host() {
+func.func @host() -> i1 {
   %0 = cuf.device_is_active : i1
-  return
+  return %0 : i1
 }
 
 // CHECK-LABEL: func.func @host
@@ -14,9 +15,9 @@ func.func @host() {
 
 // Device copies in the GPU module must not call the host runtime.
 gpu.module @cuda_device_mod {
-  gpu.func @dev() {
+  gpu.func @dev() -> i1 {
     %0 = cuf.device_is_active : i1
-    gpu.return
+    gpu.return %0 : i1
   }
 }
 
@@ -28,9 +29,9 @@ gpu.module @cuda_device_mod {
 // -----
 
 // Device procedures still in the host module are device context too.
-func.func @device_proc() attributes {cuf.proc_attr = #cuf.cuda_proc<device>} {
+func.func @device_proc() -> i1 attributes {cuf.proc_attr = #cuf.cuda_proc<device>} {
   %0 = cuf.device_is_active : i1
-  return
+  return %0 : i1
 }
 
 // CHECK-LABEL: func.func @device_proc
@@ -41,10 +42,50 @@ func.func @device_proc() attributes {cuf.proc_attr = #cuf.cuda_proc<device>} {
 
 // host_device is not a full device context; keep the runtime call on the host
 // copy. The device clone lives in the GPU module and is folded separately.
-func.func @host_device_proc() attributes {cuf.proc_attr = #cuf.cuda_proc<host_device>} {
+func.func @host_device_proc() -> i1 attributes {cuf.proc_attr = #cuf.cuda_proc<host_device>} {
   %0 = cuf.device_is_active : i1
-  return
+  return %0 : i1
 }
 
 // CHECK-LABEL: func.func @host_device_proc
 // CHECK: fir.call @_FortranACUFDeviceIsActive() : () -> i1
+
+// -----
+
+// The query only reads context state: an unused one is dead and goes away,
+// a used one stays.
+func.func @unused_query() {
+  %0 = cuf.device_is_active : i1
+  return
+}
+
+// CANON-LABEL: func.func @unused_query
+// CANON-NOT: cuf.device_is_active
+// CANON: return
+
+// -----
+
+func.func @used_query() -> i1 {
+  %0 = cuf.device_is_active : i1
+  return %0 : i1
+}
+
+// CANON-LABEL: func.func @used_query
+// CANON: %[[Q:.*]] = cuf.device_is_active : i1
+// CANON: return %[[Q]]
+
+// -----
+
+// The motivating case: once the guarded cleanup is gone, the empty fir.if and
+// the query that fed it are both dead.
+func.func @empty_guard() {
+  %0 = cuf.device_is_active : i1
+  fir.if %0 {
+  }
+  return
+}
+
+// CANON-LABEL: func.func @empty_guard
+// CANON-NOT: cuf.device_is_active
+// CANON-NOT: fir.if
+// CANON: return

>From 5839e60575afc8e0bbdfbff4bb286e5d23c675a6 Mon Sep 17 00:00:00 2001
From: Zhen Wang <zhenw at nvidia.com>
Date: Tue, 20 Oct 2026 14:35:43 -0700
Subject: [PATCH 2/2] Read a dedicated device-context resource in
 cuf.device_is_active

---
 flang-rt/lib/cuda/allocatable.cpp                  |  3 ---
 flang/include/flang/Optimizer/Dialect/CUF/CUFOps.h | 10 ++++++++++
 .../include/flang/Optimizer/Dialect/CUF/CUFOps.td  | 14 ++++++--------
 3 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/flang-rt/lib/cuda/allocatable.cpp b/flang-rt/lib/cuda/allocatable.cpp
index abd259564a2235..7ff354bbd20bd9 100644
--- a/flang-rt/lib/cuda/allocatable.cpp
+++ b/flang-rt/lib/cuda/allocatable.cpp
@@ -21,9 +21,6 @@
 
 namespace Fortran::runtime::cuda {
 
-// The compiler models the cuf.device_is_active query this backs as a read of
-// the context state and may erase an unused query, so this probe must stay
-// free of observable effects.
 static bool deviceContextTornDown() {
   // Keep cudaGetLastError transparent: consume probe-only sticky errors when
   // the slot started clean, never discarding a pre-existing user error.
diff --git a/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.h b/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.h
index 7088d9d6d3eaa1..464319f255d239 100644
--- a/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.h
+++ b/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.h
@@ -16,6 +16,16 @@
 #include "flang/Optimizer/Dialect/FIRType.h"
 #include "mlir/Dialect/LLVMIR/LLVMDialect.h"
 #include "mlir/IR/OpDefinition.h"
+#include "mlir/Interfaces/SideEffectInterfaces.h"
+
+namespace cuf {
+/// The state of the CUDA device context, read by cuf.device_is_active.
+struct DeviceContextResource
+    : public mlir::SideEffects::Resource::Base<DeviceContextResource> {
+  mlir::StringRef getName() const final { return "DeviceContextResource"; }
+  bool isAddressable() const override { return false; }
+};
+} // namespace cuf
 
 #define GET_OP_CLASSES
 #include "flang/Optimizer/Dialect/CUF/CUFOps.h.inc"
diff --git a/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.td b/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.td
index 2145796e2dc0a3..fee64ece3ef3ea 100644
--- a/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.td
+++ b/flang/include/flang/Optimizer/Dialect/CUF/CUFOps.td
@@ -26,6 +26,11 @@ include "mlir/Interfaces/LoopLikeInterface.td"
 include "mlir/IR/BuiltinAttributes.td"
 include "mlir/IR/SymbolInterfaces.td"
 
+// The state of the CUDA device context, read by cuf.device_is_active. No
+// operation writes it: the context is torn down by a user call such as
+// cudaDeviceReset, which is an ordinary call with unknown effects.
+def CUFDeviceContextResource : Resource<"::cuf::DeviceContextResource">;
+
 class cuf_Op<string mnemonic, list<Trait> traits>
     : Op<CUFDialect, mnemonic, traits>;
 
@@ -439,7 +444,7 @@ def cuf_OnDeviceOp : cuf_Op<"on_device", [Pure]> {
 }
 
 def cuf_DeviceIsActiveOp : cuf_Op<"device_is_active",
-    [MemoryEffects<[MemRead]>]> {
+    [MemoryEffects<[MemRead<CUFDeviceContextResource>]>]> {
   let summary = "Query whether the CUDA device context is still active";
 
   let description = [{
@@ -449,13 +454,6 @@ def cuf_DeviceIsActiveOp : cuf_Op<"device_is_active",
     Converted to a `_FortranACUFDeviceIsActive` runtime call on the host and
     to `false` in device code, so host-only cleanup is DCE'd from GPU
     functions.
-
-    The query is modeled as a read of the context state: it has no other
-    effect, so a `cuf.device_is_active` whose result is unused, for instance
-    once the cleanup it guarded has been removed, is dead and erased by
-    canonicalization while it is still an operation, before its conversion to
-    the runtime call. The runtime query it lowers to must stay free of
-    observable effects for this to hold.
   }];
 
   let results = (outs I1:$res);



More information about the flang-commits mailing list