[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