[Mlir-commits] [mlir] [mlir][gpu][nvvm] Lower subgroup query ops to NVVM (PR #209091)
Johnny Lin
llvmlistbot at llvm.org
Sun Jul 12 23:31:44 PDT 2026
https://github.com/johnny19436 created https://github.com/llvm/llvm-project/pull/209091
`convert-gpu-to-nvvm` currently lacks lowering coverage for `gpu.subgroup_id`, `gpu.num_subgroups`, and `gpu.subgroup_size`, while these subgroup query ops are already part of the GPU dialect.
This PR adds NVVM lowering for the three ops by mapping them to NVVM special registers (`warpid`, `nwarpid`, and `warpsize`), propagates `upper_bound` to range attributes where representable, and keeps index-bitwidth handling consistent with existing GPUToNVVM lowering patterns.
It also adds/extends `GPUToNVVM` conversion tests to cover:
- positive lowering of all three subgroup query ops,
- `upper_bound`/range behavior, and
- index-bitwidth=32 coverage.
Tested with:
- `ninja -C build mlir-opt`
- `build/bin/llvm-lit -sv mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm.mlir mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm-32b.mlir`
>From 483660e080a0487944b373b5087ea6be0cc167b7 Mon Sep 17 00:00:00 2001
From: johnny19436 <johnny194369672 at gmail.com>
Date: Mon, 13 Jul 2026 10:20:28 +0800
Subject: [PATCH 1/2] [mlir][gpu][nvvm] Lower subgroup query ops in GPUToNVVM
---
.../GPUToNVVM/LowerGpuOpsToNVVMOps.cpp | 123 ++++++++++++++++--
.../Conversion/GPUToNVVM/gpu-to-nvvm-32b.mlir | 9 +-
.../Conversion/GPUToNVVM/gpu-to-nvvm.mlir | 27 +++-
3 files changed, 137 insertions(+), 22 deletions(-)
diff --git a/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp b/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp
index 80420c26537c3..939b933dfd1be 100644
--- a/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp
+++ b/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp
@@ -35,9 +35,12 @@
#include "mlir/Transforms/DialectConversion.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
+#include "llvm/Support/MathExtras.h"
+
#include "../GPUCommon/GPUOpsLowering.h"
#include "../GPUCommon/IndexIntrinsicsOpLowering.h"
#include "../GPUCommon/OpToFuncCallLowering.h"
+#include <limits>
#include <optional>
namespace mlir {
@@ -101,6 +104,31 @@ static constexpr llvm::StringLiteral kNVVMNamedBarrierIdPrefix =
static constexpr int32_t kNVVMFirstNamedBarrierId = 1;
static constexpr int32_t kNVVMLastNamedBarrierId = 15;
static constexpr int32_t kNVVMWarpSize = 32;
+static constexpr uint64_t kMaxDim = std::numeric_limits<uint32_t>::max();
+static constexpr uint64_t kMaxSubgroupSize = 128;
+
+// Truncate or extend the result depending on the index bitwidth specified by
+// the LLVMTypeConverter options.
+static Value truncOrExtToIndexBitwidth(ConversionPatternRewriter &rewriter,
+ Location loc, Value value,
+ const LLVMTypeConverter &converter) {
+ auto valueType = cast<IntegerType>(value.getType());
+ unsigned valueBitwidth = valueType.getWidth();
+ unsigned indexBitwidth = converter.getIndexTypeBitwidth();
+ if (indexBitwidth > valueBitwidth) {
+ return LLVM::SExtOp::create(rewriter, loc,
+ IntegerType::get(rewriter.getContext(),
+ indexBitwidth),
+ value);
+ }
+ if (indexBitwidth < valueBitwidth) {
+ return LLVM::TruncOp::create(rewriter, loc,
+ IntegerType::get(rewriter.getContext(),
+ indexBitwidth),
+ value);
+ }
+ return value;
+}
static FailureOr<StringAttr>
createNVVMNamedBarrierIdGlobal(gpu::InitializeNamedBarrierOp op,
@@ -256,7 +284,6 @@ struct GPULaneIdOpToNVVM : ConvertOpToLLVMPattern<gpu::LaneIdOp> {
matchAndRewrite(gpu::LaneIdOp op, gpu::LaneIdOp::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
auto loc = op->getLoc();
- MLIRContext *context = rewriter.getContext();
LLVM::ConstantRangeAttr bounds = nullptr;
if (std::optional<APInt> upperBound = op.getUpperBound())
bounds = rewriter.getAttr<LLVM::ConstantRangeAttr>(
@@ -266,21 +293,89 @@ struct GPULaneIdOpToNVVM : ConvertOpToLLVMPattern<gpu::LaneIdOp> {
/*bitWidth=*/32, /*lower=*/0, /*upper=*/kWarpSize);
Value newOp =
NVVM::LaneIdOp::create(rewriter, loc, rewriter.getI32Type(), bounds);
- // Truncate or extend the result depending on the index bitwidth specified
- // by the LLVMTypeConverter options.
- const unsigned indexBitwidth = getTypeConverter()->getIndexTypeBitwidth();
- if (indexBitwidth > 32) {
- newOp = LLVM::SExtOp::create(
- rewriter, loc, IntegerType::get(context, indexBitwidth), newOp);
- } else if (indexBitwidth < 32) {
- newOp = LLVM::TruncOp::create(
- rewriter, loc, IntegerType::get(context, indexBitwidth), newOp);
- }
+ newOp = truncOrExtToIndexBitwidth(rewriter, loc, newOp, *getTypeConverter());
rewriter.replaceOp(op, {newOp});
return success();
}
};
+struct GPUSubgroupSizeOpToNVVM
+ : ConvertOpToLLVMPattern<gpu::SubgroupSizeOp> {
+ using ConvertOpToLLVMPattern::ConvertOpToLLVMPattern;
+
+ LogicalResult
+ matchAndRewrite(gpu::SubgroupSizeOp op, gpu::SubgroupSizeOp::Adaptor adaptor,
+ ConversionPatternRewriter &rewriter) const override {
+ LLVM::ConstantRangeAttr bounds = nullptr;
+ if (std::optional<APInt> upperBound = op.getUpperBound()) {
+ bounds = rewriter.getAttr<LLVM::ConstantRangeAttr>(
+ /*bitWidth=*/32, /*lower=*/1,
+ /*upper=*/llvm::SaturatingAdd(upperBound->getZExtValue(), 1ULL));
+ } else {
+ bounds = rewriter.getAttr<LLVM::ConstantRangeAttr>(
+ /*bitWidth=*/32, /*lower=*/1, /*upper=*/kMaxSubgroupSize + 1);
+ }
+
+ Value subgroupSize =
+ NVVM::WarpSizeOp::create(rewriter, op.getLoc(), rewriter.getI32Type(),
+ bounds);
+ subgroupSize = truncOrExtToIndexBitwidth(rewriter, op.getLoc(), subgroupSize,
+ *getTypeConverter());
+ rewriter.replaceOp(op, subgroupSize);
+ return success();
+ }
+};
+
+struct GPUSubgroupIdOpToNVVM : ConvertOpToLLVMPattern<gpu::SubgroupIdOp> {
+ using ConvertOpToLLVMPattern::ConvertOpToLLVMPattern;
+
+ LogicalResult
+ matchAndRewrite(gpu::SubgroupIdOp op, gpu::SubgroupIdOp::Adaptor adaptor,
+ ConversionPatternRewriter &rewriter) const override {
+ // TODO: Derive tighter bounds from known launch dimensions when available.
+ uint64_t upperBound = kMaxDim;
+ if (std::optional<APInt> specifiedBound = op.getUpperBound())
+ upperBound = specifiedBound->getZExtValue();
+ LLVM::ConstantRangeAttr bounds = rewriter.getAttr<LLVM::ConstantRangeAttr>(
+ /*bitWidth=*/32, /*lower=*/0, /*upper=*/upperBound);
+
+ Value subgroupId =
+ NVVM::WarpIdOp::create(rewriter, op.getLoc(), rewriter.getI32Type(),
+ bounds);
+ subgroupId = truncOrExtToIndexBitwidth(rewriter, op.getLoc(), subgroupId,
+ *getTypeConverter());
+ rewriter.replaceOp(op, subgroupId);
+ return success();
+ }
+};
+
+struct GPUNumSubgroupsOpToNVVM
+ : ConvertOpToLLVMPattern<gpu::NumSubgroupsOp> {
+ using ConvertOpToLLVMPattern::ConvertOpToLLVMPattern;
+
+ LogicalResult matchAndRewrite(gpu::NumSubgroupsOp op,
+ gpu::NumSubgroupsOp::Adaptor adaptor,
+ ConversionPatternRewriter &rewriter) const
+ override {
+ LLVM::ConstantRangeAttr bounds = nullptr;
+ if (std::optional<APInt> upperBound = op.getUpperBound()) {
+ bounds = rewriter.getAttr<LLVM::ConstantRangeAttr>(
+ /*bitWidth=*/32, /*lower=*/1,
+ /*upper=*/llvm::SaturatingAdd(upperBound->getZExtValue(), 1ULL));
+ }
+ // Without an explicit upper bound, the target-independent default for
+ // gpu.num_subgroups cannot be encoded as a finite i32 upper-exclusive range.
+
+ Value numSubgroups =
+ NVVM::WarpDimOp::create(rewriter, op.getLoc(), rewriter.getI32Type(),
+ bounds);
+ numSubgroups = truncOrExtToIndexBitwidth(rewriter, op.getLoc(),
+ numSubgroups, *getTypeConverter());
+ rewriter.replaceOp(op, numSubgroups);
+ return success();
+ }
+};
+
struct GPUBallotOpToNVVM : public ConvertOpToLLVMPattern<gpu::BallotOp> {
using ConvertOpToLLVMPattern<gpu::BallotOp>::ConvertOpToLLVMPattern;
@@ -666,8 +761,10 @@ void mlir::populateGpuToNVVMConversionPatterns(
patterns.add<gpu::index_lowering::OpLowering<
gpu::GridDimOp, NVVM::GridDimXOp, NVVM::GridDimYOp, NVVM::GridDimZOp>>(
converter, IndexKind::Grid, IntrType::Dim, benefit);
- patterns.add<GPULaneIdOpToNVVM, GPUBallotOpToNVVM, GPUShuffleOpLowering,
- GPUReturnOpLowering>(converter, benefit);
+ patterns.add<GPULaneIdOpToNVVM, GPUSubgroupIdOpToNVVM,
+ GPUNumSubgroupsOpToNVVM, GPUSubgroupSizeOpToNVVM,
+ GPUBallotOpToNVVM, GPUShuffleOpLowering, GPUReturnOpLowering>(
+ converter, benefit);
patterns.add<GPUDynamicSharedMemoryOpLowering>(
converter, NVVM::kSharedMemoryAlignmentBit, benefit);
diff --git a/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm-32b.mlir b/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm-32b.mlir
index 1bc7acfe46a4a..62bb55af5b026 100644
--- a/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm-32b.mlir
+++ b/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm-32b.mlir
@@ -7,7 +7,7 @@ gpu.module @test_module_0 {
func.func @gpu_index_ops()
-> (index, index, index, index, index, index,
index, index, index, index, index, index,
- index) {
+ index, index, index, index) {
%tIdX = gpu.thread_id x
%tIdY = gpu.thread_id y
%tIdZ = gpu.thread_id z
@@ -26,13 +26,16 @@ gpu.module @test_module_0 {
// CHECK-NOT: = llvm.sext %{{.*}} : i32 to i64
%laneId = gpu.lane_id
+ %subgroupSize = gpu.subgroup_size : index
+ %subgroupId = gpu.subgroup_id : index
+ %numSubgroups = gpu.num_subgroups : index
func.return %tIdX, %tIdY, %tIdZ, %bDimX, %bDimY, %bDimZ,
%bIdX, %bIdY, %bIdZ, %gDimX, %gDimY, %gDimZ,
- %laneId
+ %laneId, %subgroupSize, %subgroupId, %numSubgroups
: index, index, index, index, index, index,
index, index, index, index, index, index,
- index
+ index, index, index, index
}
}
diff --git a/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm.mlir b/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm.mlir
index b96069ac41a44..c3894d04741e6 100644
--- a/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm.mlir
+++ b/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm.mlir
@@ -9,7 +9,7 @@ gpu.module @test_module_0 {
func.func @gpu_index_ops()
-> (index, index, index, index, index, index,
index, index, index, index, index, index,
- index) {
+ index, index, index, index) {
// CHECK: = nvvm.read.ptx.sreg.tid.x : i32
// CHECK: = llvm.sext %{{.*}} : i32 to i64
@@ -55,13 +55,22 @@ gpu.module @test_module_0 {
// CHECK: = nvvm.read.ptx.sreg.laneid range <i32, 0, 32> : i32
// CHECK: = llvm.sext %{{.*}} : i32 to i64
%laneId = gpu.lane_id
+ // CHECK: = nvvm.read.ptx.sreg.warpsize range <i32, 1, 129> : i32
+ // CHECK: = llvm.sext %{{.*}} : i32 to i64
+ %subgroupSize = gpu.subgroup_size : index
+ // CHECK: = nvvm.read.ptx.sreg.warpid range <i32, 0, 4294967295> : i32
+ // CHECK: = llvm.sext %{{.*}} : i32 to i64
+ %subgroupId = gpu.subgroup_id : index
+ // CHECK: = nvvm.read.ptx.sreg.nwarpid : i32
+ // CHECK: = llvm.sext %{{.*}} : i32 to i64
+ %numSubgroups = gpu.num_subgroups : index
func.return %tIdX, %tIdY, %tIdZ, %bDimX, %bDimY, %bDimZ,
%bIdX, %bIdY, %bIdZ, %gDimX, %gDimY, %gDimZ,
- %laneId
+ %laneId, %subgroupSize, %subgroupId, %numSubgroups
: index, index, index, index, index, index,
index, index, index, index, index, index,
- index
+ index, index, index, index
}
}
@@ -998,15 +1007,21 @@ gpu.module @test_module_48 {
gpu.module @test_module_49 {
// CHECK-LABEL: func @explicit_id_bounds()
- func.func @explicit_id_bounds() -> (index, index, index) {
+ func.func @explicit_id_bounds() -> (index, index, index, index, index, index) {
// CHECK: = nvvm.read.ptx.sreg.tid.x range <i32, 0, 32> : i32
%0 = gpu.thread_id x upper_bound 32
// CHECK: = nvvm.read.ptx.sreg.ntid.x range <i32, 1, 33> : i32
%1 = gpu.block_dim x upper_bound 32
// CHECK: = nvvm.read.ptx.sreg.laneid range <i32, 0, 16> : i32
%2 = gpu.lane_id upper_bound 16
-
- return %0, %1, %2 : index, index, index
+ // CHECK: = nvvm.read.ptx.sreg.warpsize range <i32, 1, 33> : i32
+ %3 = gpu.subgroup_size upper_bound 32 : index
+ // CHECK: = nvvm.read.ptx.sreg.warpid range <i32, 0, 16> : i32
+ %4 = gpu.subgroup_id upper_bound 16 : index
+ // CHECK: = nvvm.read.ptx.sreg.nwarpid range <i32, 1, 17> : i32
+ %5 = gpu.num_subgroups upper_bound 16 : index
+
+ return %0, %1, %2, %3, %4, %5 : index, index, index, index, index, index
}
}
>From 06235877c8f53f93492716c366017e4a77d4fc42 Mon Sep 17 00:00:00 2001
From: johnny19436 <johnny194369672 at gmail.com>
Date: Mon, 13 Jul 2026 14:31:08 +0800
Subject: [PATCH 2/2] fix
---
.../GPUToNVVM/LowerGpuOpsToNVVMOps.cpp | 16 ++++++++++------
mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm.mlir | 2 +-
2 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp b/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp
index 939b933dfd1be..52271ddd333f0 100644
--- a/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp
+++ b/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp
@@ -104,8 +104,8 @@ static constexpr llvm::StringLiteral kNVVMNamedBarrierIdPrefix =
static constexpr int32_t kNVVMFirstNamedBarrierId = 1;
static constexpr int32_t kNVVMLastNamedBarrierId = 15;
static constexpr int32_t kNVVMWarpSize = 32;
-static constexpr uint64_t kMaxDim = std::numeric_limits<uint32_t>::max();
-static constexpr uint64_t kMaxSubgroupSize = 128;
+static constexpr uint32_t kMaxDim = std::numeric_limits<uint32_t>::max();
+static constexpr uint32_t kMaxSubgroupSize = 128;
// Truncate or extend the result depending on the index bitwidth specified by
// the LLVMTypeConverter options.
@@ -308,9 +308,11 @@ struct GPUSubgroupSizeOpToNVVM
ConversionPatternRewriter &rewriter) const override {
LLVM::ConstantRangeAttr bounds = nullptr;
if (std::optional<APInt> upperBound = op.getUpperBound()) {
+ uint32_t subgroupUpperBound =
+ static_cast<uint32_t>(upperBound->getLimitedValue(kMaxDim));
bounds = rewriter.getAttr<LLVM::ConstantRangeAttr>(
/*bitWidth=*/32, /*lower=*/1,
- /*upper=*/llvm::SaturatingAdd(upperBound->getZExtValue(), 1ULL));
+ /*upper=*/llvm::SaturatingAdd(subgroupUpperBound, uint32_t{1}));
} else {
bounds = rewriter.getAttr<LLVM::ConstantRangeAttr>(
/*bitWidth=*/32, /*lower=*/1, /*upper=*/kMaxSubgroupSize + 1);
@@ -333,9 +335,9 @@ struct GPUSubgroupIdOpToNVVM : ConvertOpToLLVMPattern<gpu::SubgroupIdOp> {
matchAndRewrite(gpu::SubgroupIdOp op, gpu::SubgroupIdOp::Adaptor adaptor,
ConversionPatternRewriter &rewriter) const override {
// TODO: Derive tighter bounds from known launch dimensions when available.
- uint64_t upperBound = kMaxDim;
+ uint32_t upperBound = kMaxDim;
if (std::optional<APInt> specifiedBound = op.getUpperBound())
- upperBound = specifiedBound->getZExtValue();
+ upperBound = static_cast<uint32_t>(specifiedBound->getLimitedValue(kMaxDim));
LLVM::ConstantRangeAttr bounds = rewriter.getAttr<LLVM::ConstantRangeAttr>(
/*bitWidth=*/32, /*lower=*/0, /*upper=*/upperBound);
@@ -359,9 +361,11 @@ struct GPUNumSubgroupsOpToNVVM
override {
LLVM::ConstantRangeAttr bounds = nullptr;
if (std::optional<APInt> upperBound = op.getUpperBound()) {
+ uint32_t numSubgroupsUpperBound =
+ static_cast<uint32_t>(upperBound->getLimitedValue(kMaxDim));
bounds = rewriter.getAttr<LLVM::ConstantRangeAttr>(
/*bitWidth=*/32, /*lower=*/1,
- /*upper=*/llvm::SaturatingAdd(upperBound->getZExtValue(), 1ULL));
+ /*upper=*/llvm::SaturatingAdd(numSubgroupsUpperBound, uint32_t{1}));
}
// Without an explicit upper bound, the target-independent default for
// gpu.num_subgroups cannot be encoded as a finite i32 upper-exclusive range.
diff --git a/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm.mlir b/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm.mlir
index c3894d04741e6..c18bb0e363d64 100644
--- a/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm.mlir
+++ b/mlir/test/Conversion/GPUToNVVM/gpu-to-nvvm.mlir
@@ -58,7 +58,7 @@ gpu.module @test_module_0 {
// CHECK: = nvvm.read.ptx.sreg.warpsize range <i32, 1, 129> : i32
// CHECK: = llvm.sext %{{.*}} : i32 to i64
%subgroupSize = gpu.subgroup_size : index
- // CHECK: = nvvm.read.ptx.sreg.warpid range <i32, 0, 4294967295> : i32
+ // CHECK: = nvvm.read.ptx.sreg.warpid range <i32, 0, -1> : i32
// CHECK: = llvm.sext %{{.*}} : i32 to i64
%subgroupId = gpu.subgroup_id : index
// CHECK: = nvvm.read.ptx.sreg.nwarpid : i32
More information about the Mlir-commits
mailing list