[Mlir-commits] [mlir] 3501bbf - [mlir][OpenACC] Preserve explicit device numbers in wait lowering (#222604)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Thu Sep 24 07:24:07 PDT 2026


Author: leovance
Date: 2026-09-24T07:24:00-07:00
New Revision: 3501bbfcff7a0e87b1b90d9f67f1ad12f0feb3d7

URL: https://github.com/llvm/llvm-project/commit/3501bbfcff7a0e87b1b90d9f67f1ad12f0feb3d7
DIFF: https://github.com/llvm/llvm-project/commit/3501bbfcff7a0e87b1b90d9f67f1ad12f0feb3d7.diff

LOG: [mlir][OpenACC] Preserve explicit device numbers in wait lowering (#222604)

WaitOpLowering currently ignores wait_devnum and always passes zero as
the device number to __tgt_acc_wait.

Forward the converted operand when present, adapting it to the runtime's
i32 parameter. Zero-extend narrow unsigned integers, sign-extend narrow
signed or signless integers, and truncate wider operands. Determine
signedness from the original operand type, since LLVM type conversion
makes integer types signless.

Keep the default device number unchanged when wait_devnum is absent.

Add regression coverage for explicit device numbers, integer width and
signedness conversions, index operands, and waits combining if and
async.

Added: 
    

Modified: 
    mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h
    mlir/lib/Conversion/OpenACCToLLVM/ACCExecutableDirectivePatterns.cpp
    mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVMUtils.cpp
    mlir/test/Conversion/OpenACCToLLVM/data.mlir
    mlir/test/Conversion/OpenACCToLLVM/wait.mlir

Removed: 
    


################################################################################
diff  --git a/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h
index 4a640f7c98752..a61d77c63cdcb 100644
--- a/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h
+++ b/mlir/include/mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h
@@ -90,10 +90,13 @@ Value getAsyncQueue(Location loc, Value asyncOperand, bool asyncOnly,
 /// which is what a `wait` clause or an `acc.wait` directive asks for. An empty
 /// \p waitOperands waits for every queue, as a `wait` clause without values
 /// does. The values must already be converted to the LLVM dialect.
+/// If provided, \p deviceNum must be an i32 value; otherwise the device number
+/// defaults to zero.
 LogicalResult emitWaitCall(Location loc, ValueRange waitOperands,
                            Value asyncQueue, OpBuilder &builder,
                            Region &globalSymbolRegion, SymbolTable &symbolTable,
-                           const ACCRuntimeCallConfig &config);
+                           const ACCRuntimeCallConfig &config,
+                           Value deviceNum = {});
 
 /// Emits the runtime call that asks for the device address the object of the
 /// data clause \p clauseOp is mapped to, which is what a `use_device` clause

diff  --git a/mlir/lib/Conversion/OpenACCToLLVM/ACCExecutableDirectivePatterns.cpp b/mlir/lib/Conversion/OpenACCToLLVM/ACCExecutableDirectivePatterns.cpp
index 2d36d4b66c32f..2539d8157af75 100644
--- a/mlir/lib/Conversion/OpenACCToLLVM/ACCExecutableDirectivePatterns.cpp
+++ b/mlir/lib/Conversion/OpenACCToLLVM/ACCExecutableDirectivePatterns.cpp
@@ -15,6 +15,7 @@
 #include "mlir/Conversion/OpenACCToLLVM/ACCToLLVMUtils.h"
 
 #include "mlir/Conversion/LLVMCommon/Pattern.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
 #include "mlir/Dialect/LLVMIR/LLVMDialect.h"
 #include "mlir/Dialect/OpenACC/OpenACC.h"
 #include "mlir/IR/PatternMatch.h"
@@ -26,6 +27,20 @@ using namespace mlir;
 using namespace mlir::acc;
 
 namespace {
+static Value castToI32(Location loc, Value value, bool isUnsigned,
+                       ConversionPatternRewriter &rewriter) {
+  Type i32Ty = rewriter.getI32Type();
+  unsigned bitwidth = value.getType().getIntOrFloatBitWidth();
+  if (bitwidth > 32)
+    return arith::TruncIOp::create(rewriter, loc, i32Ty, value);
+  if (bitwidth < 32) {
+    if (isUnsigned)
+      return arith::ExtUIOp::create(rewriter, loc, i32Ty, value);
+    return arith::ExtSIOp::create(rewriter, loc, i32Ty, value);
+  }
+  return value;
+}
+
 template <typename OpTy>
 struct ACCExecutableDirectivePattern : public ConvertOpToLLVMPattern<OpTy> {
   ACCExecutableDirectivePattern(const LLVMTypeConverter &converter,
@@ -46,7 +61,7 @@ struct WaitOpLowering : public ACCExecutableDirectivePattern<WaitOp> {
   using ACCExecutableDirectivePattern<WaitOp>::ACCExecutableDirectivePattern;
 
   LogicalResult
-  matchAndRewrite(WaitOp op, WaitOp::Adaptor,
+  matchAndRewrite(WaitOp op, WaitOp::Adaptor adaptor,
                   ConversionPatternRewriter &rewriter) const override {
     Location loc = op->getLoc();
 
@@ -59,8 +74,16 @@ struct WaitOpLowering : public ACCExecutableDirectivePattern<WaitOp> {
       SmallVector<Value> waitValues;
       for (Value operand : op.getWaitOperands())
         waitValues.push_back(rewriter.getRemappedValue(operand));
+
+      Value deviceNum = adaptor.getWaitDevnum();
+      if (deviceNum) {
+        // The converted operand is signless, so inspect the original type.
+        bool isUnsigned = op.getWaitDevnum().getType().isUnsignedInteger();
+        deviceNum = castToI32(loc, deviceNum, isUnsigned, rewriter);
+      }
+
       return emitWaitCall(loc, waitValues, asyncQueue, rewriter,
-                          globalSymbolRegion, symbolTable, config);
+                          globalSymbolRegion, symbolTable, config, deviceNum);
     };
 
     if (failed(emitGuardedByIfCond(loc, op.getIfCond(), rewriter, emitWait)))

diff  --git a/mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVMUtils.cpp b/mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVMUtils.cpp
index 4b43087ab14f6..8e6092a7dba39 100644
--- a/mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVMUtils.cpp
+++ b/mlir/lib/Conversion/OpenACCToLLVM/ACCToLLVMUtils.cpp
@@ -241,7 +241,8 @@ LogicalResult acc::emitWaitCall(Location loc, ValueRange waitOperands,
                                 Value asyncQueue, OpBuilder &builder,
                                 Region &globalSymbolRegion,
                                 SymbolTable &symbolTable,
-                                const ACCRuntimeCallConfig &config) {
+                                const ACCRuntimeCallConfig &config,
+                                Value deviceNum) {
   Type i32Ty = builder.getI32Type();
   Type i64Ty = builder.getI64Type();
   Type ptrTy = LLVM::LLVMPointerType::get(builder.getContext());
@@ -276,7 +277,8 @@ LogicalResult acc::emitWaitCall(Location loc, ValueRange waitOperands,
   Value flags = LLVM::ConstantOp::create(builder, loc, i64Ty, 0);
   Value deviceType = LLVM::ConstantOp::create(
       builder, loc, i64Ty, config.getDeviceTypeRuntimeValue(DeviceType::None));
-  Value deviceNum = LLVM::ConstantOp::create(builder, loc, i32Ty, 0);
+  if (!deviceNum)
+    deviceNum = LLVM::ConstantOp::create(builder, loc, i32Ty, 0);
 
   return createRuntimeCall(
       loc, builder, globalSymbolRegion, symbolTable,

diff  --git a/mlir/test/Conversion/OpenACCToLLVM/data.mlir b/mlir/test/Conversion/OpenACCToLLVM/data.mlir
index 49ecea14ea16d..709094531dee3 100644
--- a/mlir/test/Conversion/OpenACCToLLVM/data.mlir
+++ b/mlir/test/Conversion/OpenACCToLLVM/data.mlir
@@ -159,7 +159,8 @@ func.func @data_if(%arg0: !llvm.ptr, %cond: i1) {
 // CHECK-LABEL: llvm.func @enter_data_wait_all
 // CHECK: %[[WAITNUM:.*]] = llvm.mlir.constant(0 : i32) : i32
 // CHECK: %[[WAITLIST:.*]] = llvm.mlir.zero : !llvm.ptr
-// CHECK: llvm.call @__tgt_acc_wait({{.*}}, %[[WAITNUM]], %[[WAITLIST]], {{.*}})
+// CHECK: %[[DEFAULT_DEVICE:.*]] = llvm.mlir.constant(0 : i32)
+// CHECK: llvm.call @__tgt_acc_wait(%{{[^,]+}}, %{{[^,]+}}, %{{[^,]+}}, %[[DEFAULT_DEVICE]], %[[WAITNUM]], %[[WAITLIST]], {{.*}})
 // CHECK: llvm.call @__tgt_acc_data_enter
 func.func @enter_data_wait_all(%arg0: !llvm.ptr) {
   %size = arith.constant 4 : i64

diff  --git a/mlir/test/Conversion/OpenACCToLLVM/wait.mlir b/mlir/test/Conversion/OpenACCToLLVM/wait.mlir
index 3533714f0a0a5..5d1bb57abf1da 100644
--- a/mlir/test/Conversion/OpenACCToLLVM/wait.mlir
+++ b/mlir/test/Conversion/OpenACCToLLVM/wait.mlir
@@ -5,7 +5,8 @@
 // CHECK: llvm.mlir.constant(-1 : i64)
 // CHECK: llvm.mlir.constant(0 : i32)
 // CHECK: llvm.mlir.zero : !llvm.ptr
-// CHECK: llvm.call @__tgt_acc_wait
+// CHECK: %[[DEFAULT_DEVICE:.*]] = llvm.mlir.constant(0 : i32)
+// CHECK: llvm.call @__tgt_acc_wait(%{{[^,]+}}, %{{[^,]+}}, %{{[^,]+}}, %[[DEFAULT_DEVICE]],
 
 module {
   func.func @test_wait_empty() {
@@ -16,6 +17,132 @@ module {
 
 // -----
 
+// An explicit device number is passed as the fourth runtime argument.
+// CHECK-LABEL: llvm.func @test_wait_devnum_i32
+// CHECK-SAME: (%[[DEVICE:.*]]: i32, %[[QUEUE:.*]]: i64)
+// CHECK: llvm.store %[[QUEUE]], %{{.*}} : i64, !llvm.ptr
+// CHECK: llvm.call @__tgt_acc_wait(%{{[^,]+}}, %{{[^,]+}}, %{{[^,]+}}, %[[DEVICE]],
+
+module {
+  func.func @test_wait_devnum_i32(%device: i32, %queue: i64) {
+    acc.wait(%queue : i64) wait_devnum(%device : i32)
+    return
+  }
+}
+
+// -----
+
+// Narrow signless device numbers are sign-extended to the runtime's i32 parameter.
+// CHECK-LABEL: llvm.func @test_wait_devnum_i16
+// CHECK-SAME: (%[[DEVICE:.*]]: i16,
+// CHECK: %[[DEVICE_I32:.*]] = llvm.sext %[[DEVICE]] : i16 to i32
+// CHECK: llvm.call @__tgt_acc_wait(%{{[^,]+}}, %{{[^,]+}}, %{{[^,]+}}, %[[DEVICE_I32]],
+
+module {
+  func.func @test_wait_devnum_i16(%device: i16, %queue: i64) {
+    acc.wait(%queue : i64) wait_devnum(%device : i16)
+    return
+  }
+}
+
+// -----
+
+// Unsigned device numbers must be zero-extended, even though the converted
+// operands are signless. In particular, ui1's value 1 must not become -1.
+// CHECK-LABEL: llvm.func @test_wait_devnum_ui1
+// CHECK-SAME: (%[[DEVICE:.*]]: i1,
+// CHECK: %[[DEVICE_I32:.*]] = llvm.zext %[[DEVICE]] : i1 to i32
+// CHECK: llvm.call @__tgt_acc_wait(%{{[^,]+}}, %{{[^,]+}}, %{{[^,]+}}, %[[DEVICE_I32]],
+
+module {
+  func.func @test_wait_devnum_ui1(%device: ui1, %queue: i64) {
+    acc.wait(%queue : i64) wait_devnum(%device : ui1)
+    return
+  }
+}
+
+// -----
+
+// CHECK-LABEL: llvm.func @test_wait_devnum_ui8
+// CHECK-SAME: (%[[DEVICE:.*]]: i8,
+// CHECK: %[[DEVICE_I32:.*]] = llvm.zext %[[DEVICE]] : i8 to i32
+// CHECK: llvm.call @__tgt_acc_wait(%{{[^,]+}}, %{{[^,]+}}, %{{[^,]+}}, %[[DEVICE_I32]],
+
+module {
+  func.func @test_wait_devnum_ui8(%device: ui8, %queue: i64) {
+    acc.wait(%queue : i64) wait_devnum(%device : ui8)
+    return
+  }
+}
+
+// -----
+
+// Explicitly signed device numbers still use sign extension.
+// CHECK-LABEL: llvm.func @test_wait_devnum_si8
+// CHECK-SAME: (%[[DEVICE:.*]]: i8,
+// CHECK: %[[DEVICE_I32:.*]] = llvm.sext %[[DEVICE]] : i8 to i32
+// CHECK: llvm.call @__tgt_acc_wait(%{{[^,]+}}, %{{[^,]+}}, %{{[^,]+}}, %[[DEVICE_I32]],
+
+module {
+  func.func @test_wait_devnum_si8(%device: si8, %queue: i64) {
+    acc.wait(%queue : i64) wait_devnum(%device : si8)
+    return
+  }
+}
+
+// -----
+
+// Wide device numbers are truncated to the runtime's i32 parameter.
+// CHECK-LABEL: llvm.func @test_wait_devnum_i64
+// CHECK-SAME: (%[[DEVICE:.*]]: i64,
+// CHECK: %[[DEVICE_I32:.*]] = llvm.trunc %[[DEVICE]] : i64 to i32
+// CHECK: llvm.call @__tgt_acc_wait(%{{[^,]+}}, %{{[^,]+}}, %{{[^,]+}}, %[[DEVICE_I32]],
+
+module {
+  func.func @test_wait_devnum_i64(%device: i64, %queue: i64) {
+    acc.wait(%queue : i64) wait_devnum(%device : i64)
+    return
+  }
+}
+
+// -----
+
+// The converted index operand is also narrowed to i32.
+// CHECK-LABEL: llvm.func @test_wait_devnum_index
+// CHECK-SAME: (%[[DEVICE:.*]]: i64,
+// CHECK: %[[DEVICE_I32:.*]] = llvm.trunc %[[DEVICE]] : i64 to i32
+// CHECK: llvm.call @__tgt_acc_wait(%{{[^,]+}}, %{{[^,]+}}, %{{[^,]+}}, %[[DEVICE_I32]],
+
+module {
+  func.func @test_wait_devnum_index(%device: index, %queue: i64) {
+    acc.wait(%queue : i64) wait_devnum(%device : index)
+    return
+  }
+}
+
+// -----
+
+// The explicit device number and async queue are preserved in the guarded call.
+// CHECK-LABEL: llvm.func @test_wait_devnum_if_async
+// CHECK-SAME: (%[[COND:.*]]: i1, %[[DEVICE:.*]]: i32, %[[QUEUE:.*]]: i64, %[[ASYNC:.*]]: i32)
+// CHECK: llvm.cond_br %[[COND]], ^[[THEN:bb[0-9]+]], ^[[CONT:bb[0-9]+]]
+// CHECK: ^[[THEN]]:
+// CHECK: %[[ASYNC_I64:.*]] = llvm.sext %[[ASYNC]] : i32 to i64
+// CHECK: llvm.store %[[QUEUE]], %{{.*}} : i64, !llvm.ptr
+// CHECK: llvm.call @__tgt_acc_wait(%{{[^,]+}}, %{{[^,]+}}, %{{[^,]+}}, %[[DEVICE]], %{{[^,]+}}, %{{[^,]+}}, %[[ASYNC_I64]])
+// CHECK: llvm.br ^[[CONT]]
+// CHECK: ^[[CONT]]:
+
+module {
+  func.func @test_wait_devnum_if_async(%cond: i1, %device: i32,
+                                     %queue: i64, %async: i32) {
+    acc.wait(%queue : i64) async(%async : i32) wait_devnum(%device : i32) if(%cond)
+    return
+  }
+}
+
+// -----
+
 // Wait operands: waitNum matches the list length, values are stored into an
 // alloca'd wait list, and the async queue remains sync (-1).
 // CHECK-LABEL: llvm.func @test_wait_operands


        


More information about the Mlir-commits mailing list