[Mlir-commits] [mlir] [mlir][spirv] Add InBoundsAccessChain op (PR #216682)
Hsiangkai Wang
llvmlistbot at llvm.org
Mon Aug 17 03:13:37 PDT 2026
https://github.com/Hsiangkai created https://github.com/llvm/llvm-project/pull/216682
Add the SPIR-V opcode, ODS definition, verifier hook, and canonicalization support for spirv.InBoundsAccessChain.
>From d5792e03dd9ad297c5dad0e64980a068d2b88f40 Mon Sep 17 00:00:00 2001
From: Hsiangkai Wang <hsiangkai.wang at arm.com>
Date: Mon, 17 Aug 2026 10:30:30 +0100
Subject: [PATCH] [mlir][spirv] Add InBoundsAccessChain op
Add the SPIR-V opcode, ODS definition, verifier hook, and canonicalization
support for spirv.InBoundsAccessChain.
---
.../mlir/Dialect/SPIRV/IR/SPIRVBase.td | 3 +-
.../mlir/Dialect/SPIRV/IR/SPIRVMemoryOps.td | 34 +++++++++++++++++++
mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp | 15 ++++++++
.../SPIRV/IR/SPIRVCanonicalization.cpp | 34 +++++++++++++++++++
mlir/test/Dialect/SPIRV/IR/memory-ops.mlir | 11 ++++++
.../SPIRV/Transforms/canonicalize.mlir | 19 +++++++++++
mlir/test/Target/SPIRV/memory-ops.mlir | 12 ++++++-
7 files changed, 126 insertions(+), 2 deletions(-)
diff --git a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
index 527e6082c5fcd..92653117a451a 100644
--- a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
+++ b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
@@ -4493,6 +4493,7 @@ def SPIRV_OC_OpLoad : I32EnumAttrCase<"OpLoad", 61>;
def SPIRV_OC_OpStore : I32EnumAttrCase<"OpStore", 62>;
def SPIRV_OC_OpCopyMemory : I32EnumAttrCase<"OpCopyMemory", 63>;
def SPIRV_OC_OpAccessChain : I32EnumAttrCase<"OpAccessChain", 65>;
+def SPIRV_OC_OpInBoundsAccessChain : I32EnumAttrCase<"OpInBoundsAccessChain", 66>;
def SPIRV_OC_OpPtrAccessChain : I32EnumAttrCase<"OpPtrAccessChain", 67>;
def SPIRV_OC_OpInBoundsPtrAccessChain : I32EnumAttrCase<"OpInBoundsPtrAccessChain", 70>;
def SPIRV_OC_OpDecorate : I32EnumAttrCase<"OpDecorate", 71>;
@@ -4744,7 +4745,7 @@ def SPIRV_OpcodeAttr :
SPIRV_OC_OpSpecConstantOp, SPIRV_OC_OpFunction, SPIRV_OC_OpFunctionParameter,
SPIRV_OC_OpFunctionEnd, SPIRV_OC_OpFunctionCall, SPIRV_OC_OpVariable,
SPIRV_OC_OpLoad, SPIRV_OC_OpStore, SPIRV_OC_OpCopyMemory,
- SPIRV_OC_OpAccessChain, SPIRV_OC_OpPtrAccessChain,
+ SPIRV_OC_OpAccessChain, SPIRV_OC_OpInBoundsAccessChain, SPIRV_OC_OpPtrAccessChain,
SPIRV_OC_OpInBoundsPtrAccessChain, SPIRV_OC_OpDecorate,
SPIRV_OC_OpMemberDecorate, SPIRV_OC_OpVectorExtractDynamic,
SPIRV_OC_OpVectorInsertDynamic, SPIRV_OC_OpVectorShuffle,
diff --git a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVMemoryOps.td b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVMemoryOps.td
index e13909e8eeeae..5ae6c8af83003 100644
--- a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVMemoryOps.td
+++ b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVMemoryOps.td
@@ -81,6 +81,40 @@ def SPIRV_AccessChainOp : SPIRV_Op<"AccessChain", [Pure]> {
// -----
+def SPIRV_InBoundsAccessChainOp : SPIRV_Op<"InBoundsAccessChain", [Pure]> {
+ let summary = [{
+ Create a pointer into a composite object that is known to stay within the
+ base object.
+ }];
+
+ let description = [{
+ Has the same operands, result, and type rules as `spirv.AccessChain`, with
+ the additional contract that the resulting pointer points within the base
+ object.
+ }];
+
+ let arguments = (ins
+ SPIRV_AnyPtr:$base_ptr,
+ Variadic<SPIRV_Integer>:$indices
+ );
+
+ let results = (outs
+ SPIRV_AnyPtr:$component_ptr
+ );
+
+ let builders = [OpBuilder<(ins "Value":$basePtr, "ValueRange":$indices)>];
+
+ let hasCanonicalizer = 1;
+
+ let hasCustomAssemblyFormat = 0;
+
+ let assemblyFormat = [{
+ $base_ptr `[` $indices `]` attr-dict `:` type($base_ptr) `,` type($indices) `->` type(results)
+ }];
+}
+
+// -----
+
def SPIRV_CopyMemoryOp : SPIRV_Op<"CopyMemory", [DeclareOpInterfaceMethods<AlignmentAttrOpInterface>]> {
let summary = [{
Copy from the memory pointed to by Source to the memory pointed to by
diff --git a/mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp b/mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp
index f9c03bf3b88c0..4d315fe735aef 100644
--- a/mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp
@@ -351,6 +351,21 @@ LogicalResult AccessChainOp::verify() {
return verifyAccessChain(*this, getIndices());
}
+//===----------------------------------------------------------------------===//
+// spirv.InBoundsAccessChainOp
+//===----------------------------------------------------------------------===//
+
+void InBoundsAccessChainOp::build(OpBuilder &builder, OperationState &state,
+ Value basePtr, ValueRange indices) {
+ auto type = getElementPtrType(basePtr.getType(), indices, state.location);
+ assert(type && "Unable to deduce return type based on basePtr and indices");
+ build(builder, state, type, basePtr, indices);
+}
+
+LogicalResult InBoundsAccessChainOp::verify() {
+ return verifyAccessChain(*this, getIndices());
+}
+
//===----------------------------------------------------------------------===//
// spirv.LoadOp
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/SPIRV/IR/SPIRVCanonicalization.cpp b/mlir/lib/Dialect/SPIRV/IR/SPIRVCanonicalization.cpp
index 2d5c4d7d3fd0e..12347d07abd63 100644
--- a/mlir/lib/Dialect/SPIRV/IR/SPIRVCanonicalization.cpp
+++ b/mlir/lib/Dialect/SPIRV/IR/SPIRVCanonicalization.cpp
@@ -121,6 +121,40 @@ void spirv::AccessChainOp::getCanonicalizationPatterns(
results.add<CombineChainedAccessChain>(context);
}
+namespace {
+
+/// Combines chained `spirv::InBoundsAccessChainOp` operations while retaining
+/// the in-bounds contract of both segments.
+struct CombineChainedInBoundsAccessChain final
+ : OpRewritePattern<spirv::InBoundsAccessChainOp> {
+ using Base::Base;
+
+ LogicalResult matchAndRewrite(spirv::InBoundsAccessChainOp accessChainOp,
+ PatternRewriter &rewriter) const override {
+ auto parentAccessChainOp =
+ accessChainOp.getBasePtr()
+ .getDefiningOp<spirv::InBoundsAccessChainOp>();
+
+ if (!parentAccessChainOp)
+ return failure();
+
+ SmallVector<Value, 4> indices(parentAccessChainOp.getIndices());
+ llvm::append_range(indices, accessChainOp.getIndices());
+
+ rewriter.replaceOpWithNewOp<spirv::InBoundsAccessChainOp>(
+ accessChainOp, parentAccessChainOp.getBasePtr(), indices);
+
+ return success();
+ }
+};
+
+} // namespace
+
+void spirv::InBoundsAccessChainOp::getCanonicalizationPatterns(
+ RewritePatternSet &results, MLIRContext *context) {
+ results.add<CombineChainedInBoundsAccessChain>(context);
+}
+
//===----------------------------------------------------------------------===//
// spirv.IAddCarry / spirv.ISubBorrow
//===----------------------------------------------------------------------===//
diff --git a/mlir/test/Dialect/SPIRV/IR/memory-ops.mlir b/mlir/test/Dialect/SPIRV/IR/memory-ops.mlir
index a3b96c698a344..c58f22cea5a4b 100644
--- a/mlir/test/Dialect/SPIRV/IR/memory-ops.mlir
+++ b/mlir/test/Dialect/SPIRV/IR/memory-ops.mlir
@@ -35,6 +35,17 @@ func.func @access_chain_2D_array_2(%arg0 : i32) -> () {
return
}
+//===----------------------------------------------------------------------===//
+// spirv.InBoundsAccessChain
+//===----------------------------------------------------------------------===//
+
+func.func @inbounds_access_chain(%arg0 : i32) -> () {
+ %0 = spirv.Variable : !spirv.ptr<!spirv.array<4xf32>, Function>
+ // CHECK: spirv.InBoundsAccessChain {{.*}}[{{.*}}] : !spirv.ptr<!spirv.array<4 x f32>, Function>
+ %1 = spirv.InBoundsAccessChain %0[%arg0] : !spirv.ptr<!spirv.array<4xf32>, Function>, i32 -> !spirv.ptr<f32, Function>
+ return
+}
+
func.func @access_chain_rtarray(%arg0 : i32) -> () {
%0 = spirv.Variable : !spirv.ptr<!spirv.rtarray<f32>, Function>
// CHECK: spirv.AccessChain {{.*}}[{{.*}}] : !spirv.ptr<!spirv.rtarray<f32>, Function>
diff --git a/mlir/test/Dialect/SPIRV/Transforms/canonicalize.mlir b/mlir/test/Dialect/SPIRV/Transforms/canonicalize.mlir
index 94d9c53db0bbc..e80145419f8a8 100644
--- a/mlir/test/Dialect/SPIRV/Transforms/canonicalize.mlir
+++ b/mlir/test/Dialect/SPIRV/Transforms/canonicalize.mlir
@@ -19,6 +19,25 @@ func.func @combine_full_access_chain() -> f32 {
// -----
+//===----------------------------------------------------------------------===//
+// spirv.InBoundsAccessChain
+//===----------------------------------------------------------------------===//
+
+func.func @combine_full_inbounds_access_chain() -> f32 {
+ // CHECK: %[[INDEX:.*]] = spirv.Constant 0
+ // CHECK-NEXT: %[[VAR:.*]] = spirv.Variable
+ // CHECK-NEXT: %[[PTR:.*]] = spirv.InBoundsAccessChain %[[VAR]][%[[INDEX]], %[[INDEX]], %[[INDEX]]]
+ // CHECK-NEXT: spirv.Load "Function" %[[PTR]]
+ %c0 = spirv.Constant 0: i32
+ %0 = spirv.Variable : !spirv.ptr<!spirv.struct<(!spirv.array<4x!spirv.array<4xf32>>, !spirv.array<4xi32>)>, Function>
+ %1 = spirv.InBoundsAccessChain %0[%c0] : !spirv.ptr<!spirv.struct<(!spirv.array<4x!spirv.array<4xf32>>, !spirv.array<4xi32>)>, Function>, i32 -> !spirv.ptr<!spirv.array<4x!spirv.array<4xf32>>, Function>
+ %2 = spirv.InBoundsAccessChain %1[%c0, %c0] : !spirv.ptr<!spirv.array<4x!spirv.array<4xf32>>, Function>, i32, i32 -> !spirv.ptr<f32, Function>
+ %3 = spirv.Load "Function" %2 : f32
+ spirv.ReturnValue %3 : f32
+}
+
+// -----
+
func.func @combine_access_chain_multi_use() -> !spirv.array<4xf32> {
// CHECK: %[[INDEX:.*]] = spirv.Constant 0
// CHECK-NEXT: %[[VAR:.*]] = spirv.Variable
diff --git a/mlir/test/Target/SPIRV/memory-ops.mlir b/mlir/test/Target/SPIRV/memory-ops.mlir
index 2d18394818611..fa786f18aac18 100644
--- a/mlir/test/Target/SPIRV/memory-ops.mlir
+++ b/mlir/test/Target/SPIRV/memory-ops.mlir
@@ -40,6 +40,17 @@ spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader, Linkage], []> {
// -----
+spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader, Linkage], []> {
+ spirv.func @inbounds_access_chain(%arg0 : !spirv.ptr<!spirv.array<4xf32>, Function>, %arg1 : i32) "None" {
+ // CHECK: {{%.*}} = spirv.InBoundsAccessChain {{%.*}}[{{%.*}}] : !spirv.ptr<!spirv.array<4 x f32>, Function>
+ %0 = spirv.InBoundsAccessChain %arg0[%arg1] : !spirv.ptr<!spirv.array<4xf32>, Function>, i32 -> !spirv.ptr<f32, Function>
+ %1 = spirv.Load "Function" %0 : f32
+ spirv.Return
+ }
+}
+
+// -----
+
spirv.module Logical GLSL450 requires #spirv.vce<v1.0, [Shader, Linkage], [SPV_KHR_storage_buffer_storage_class]> {
spirv.func @load_store_zero_rank_float(%arg0: !spirv.ptr<!spirv.struct<(!spirv.array<1 x f32, stride=4> [0]), Block>, StorageBuffer>, %arg1: !spirv.ptr<!spirv.struct<(!spirv.array<1 x f32, stride=4> [0]), Block>, StorageBuffer>) "None" {
// CHECK: [[LOAD_PTR:%.*]] = spirv.AccessChain {{%.*}}[{{%.*}}, {{%.*}}] : !spirv.ptr<!spirv.struct<(!spirv.array<1 x f32, stride=4> [0]), Block>, StorageBuffer>
@@ -122,4 +133,3 @@ spirv.module Logical GLSL450 requires #spirv.vce<v1.4, [Shader, Linkage], []> {
spirv.Return
}
}
-
More information about the Mlir-commits
mailing list