[Mlir-commits] [mlir] [mlir][spirv] Add in-bounds access chain conversion (PR #216096)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Thu Aug 13 09:00:10 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-mlir
Author: Hsiangkai Wang (Hsiangkai)
<details>
<summary>Changes</summary>
Add spirv.InBoundsAccessChain and use it for static StorageBuffer
accesses whose linearized range fits the declared SPIR-V object.
Keep plain access chains for dynamic layouts and packed sub-16-bit storage.
---
Patch is 38.78 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/216096.diff
13 Files Affected:
- (modified) mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td (+2-1)
- (modified) mlir/include/mlir/Dialect/SPIRV/IR/SPIRVMemoryOps.td (+34)
- (modified) mlir/include/mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h (+30-1)
- (modified) mlir/lib/Conversion/TensorToSPIRV/TensorToSPIRV.cpp (+7-1)
- (modified) mlir/lib/Conversion/VectorToSPIRV/VectorToSPIRV.cpp (+6-6)
- (modified) mlir/lib/Dialect/SPIRV/IR/MemoryOps.cpp (+15)
- (modified) mlir/lib/Dialect/SPIRV/IR/SPIRVCanonicalization.cpp (+34)
- (modified) mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp (+151-8)
- (modified) mlir/test/Conversion/MemRefToSPIRV/memref-to-spirv.mlir (+54-4)
- (modified) mlir/test/Conversion/VectorToSPIRV/vector-to-spirv.mlir (+17-6)
- (modified) mlir/test/Dialect/SPIRV/IR/memory-ops.mlir (+11)
- (modified) mlir/test/Dialect/SPIRV/Transforms/canonicalize.mlir (+19)
- (modified) mlir/test/Target/SPIRV/memory-ops.mlir (+11-1)
``````````diff
diff --git a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
index 68a0ee470709d..34d0d0b2b4d3b 100644
--- a/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
+++ b/mlir/include/mlir/Dialect/SPIRV/IR/SPIRVBase.td
@@ -4492,6 +4492,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>;
@@ -4743,7 +4744,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/include/mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h b/mlir/include/mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h
index 03ae54a8ae30a..cfd0340fbe510 100644
--- a/mlir/include/mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h
+++ b/mlir/include/mlir/Dialect/SPIRV/Transforms/SPIRVConversion.h
@@ -168,11 +168,26 @@ Value getPushConstantValue(Operation *op, unsigned elementCount,
unsigned offset, Type integerType,
OpBuilder &builder);
+/// No-wrap guarantees proven for a linearized index calculation.
+struct LinearizedIndexNoWrapFlags {
+ bool noSignedWrap = false;
+ bool noUnsignedWrap = false;
+};
+
/// Generates IR to perform index linearization with the given `indices` and
/// their corresponding `strides`, adding an initial `offset`.
Value linearizeIndex(ValueRange indices, ArrayRef<int64_t> strides,
int64_t offset, Type integerType, Location loc,
- OpBuilder &builder);
+ OpBuilder &builder,
+ LinearizedIndexNoWrapFlags noWrapFlags = {});
+
+/// Returns no-wrap guarantees for an in-bounds index into the static layout
+/// described by `shape`, `strides`, and `offset` when linearized as
+/// `integerType`.
+LinearizedIndexNoWrapFlags
+getLinearizedIndexNoWrapFlags(ArrayRef<int64_t> shape,
+ ArrayRef<int64_t> strides, int64_t offset,
+ Type integerType);
/// Performs the index computation to get to the element at `indices` of the
/// memory pointed to by `basePtr`, using the layout map of `baseType`.
@@ -184,6 +199,13 @@ Value getElementPtr(const SPIRVTypeConverter &typeConverter,
MemRefType baseType, Value basePtr, ValueRange indices,
Location loc, OpBuilder &builder);
+/// As above, with the number of contiguous memref elements accessed through
+/// the pointer. This lets vector conversions retain their full access range.
+Value getElementPtr(const SPIRVTypeConverter &typeConverter,
+ MemRefType baseType, Value basePtr, ValueRange indices,
+ Location loc, OpBuilder &builder,
+ uint64_t accessElementCount);
+
// GetElementPtr implementation for Kernel/OpenCL flavored SPIR-V.
Value getOpenCLElementPtr(const SPIRVTypeConverter &typeConverter,
MemRefType baseType, Value basePtr,
@@ -194,6 +216,13 @@ Value getVulkanElementPtr(const SPIRVTypeConverter &typeConverter,
MemRefType baseType, Value basePtr,
ValueRange indices, Location loc, OpBuilder &builder);
+/// As above, with the number of contiguous memref elements accessed through
+/// the pointer.
+Value getVulkanElementPtr(const SPIRVTypeConverter &typeConverter,
+ MemRefType baseType, Value basePtr,
+ ValueRange indices, Location loc, OpBuilder &builder,
+ uint64_t accessElementCount);
+
// Find the largest factor of size among {2,3,4} for the lowest dimension of
// the target shape.
int getComputeVectorSize(int64_t size);
diff --git a/mlir/lib/Conversion/TensorToSPIRV/TensorToSPIRV.cpp b/mlir/lib/Conversion/TensorToSPIRV/TensorToSPIRV.cpp
index f24972f6b6ee1..a5e5b7c3c658f 100644
--- a/mlir/lib/Conversion/TensorToSPIRV/TensorToSPIRV.cpp
+++ b/mlir/lib/Conversion/TensorToSPIRV/TensorToSPIRV.cpp
@@ -81,8 +81,14 @@ class TensorExtractPattern final
auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
auto indexType = typeConverter.getIndexType();
+ spirv::LinearizedIndexNoWrapFlags noWrapFlags;
+ if (typeConverter.getTargetEnv().allows(
+ spirv::Extension::SPV_KHR_no_integer_wrap_decoration))
+ noWrapFlags = spirv::getLinearizedIndexNoWrapFlags(
+ tensorType.getShape(), strides, /*offset=*/0, indexType);
Value index = spirv::linearizeIndex(adaptor.getIndices(), strides,
- /*offset=*/0, indexType, loc, rewriter);
+ /*offset=*/0, indexType, loc, rewriter,
+ noWrapFlags);
auto acOp = spirv::AccessChainOp::create(rewriter, loc, varOp, index);
rewriter.replaceOpWithNewOp<spirv::LoadOp>(extractOp, acOp);
diff --git a/mlir/lib/Conversion/VectorToSPIRV/VectorToSPIRV.cpp b/mlir/lib/Conversion/VectorToSPIRV/VectorToSPIRV.cpp
index 78693e924c4d9..0808d13620be0 100644
--- a/mlir/lib/Conversion/VectorToSPIRV/VectorToSPIRV.cpp
+++ b/mlir/lib/Conversion/VectorToSPIRV/VectorToSPIRV.cpp
@@ -743,9 +743,9 @@ struct VectorLoadOpConverter final
const auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
auto loc = loadOp.getLoc();
- Value accessChain =
- spirv::getElementPtr(typeConverter, memrefType, adaptor.getBase(),
- adaptor.getIndices(), loc, rewriter);
+ Value accessChain = spirv::getElementPtr(
+ typeConverter, memrefType, adaptor.getBase(), adaptor.getIndices(), loc,
+ rewriter, loadOp.getVectorType().getNumElements());
if (!accessChain)
return rewriter.notifyMatchFailure(
loadOp, "failed to get memref element pointer");
@@ -809,9 +809,9 @@ struct VectorStoreOpConverter final
const auto &typeConverter = *getTypeConverter<SPIRVTypeConverter>();
auto loc = storeOp.getLoc();
- Value accessChain =
- spirv::getElementPtr(typeConverter, memrefType, adaptor.getBase(),
- adaptor.getIndices(), loc, rewriter);
+ Value accessChain = spirv::getElementPtr(
+ typeConverter, memrefType, adaptor.getBase(), adaptor.getIndices(), loc,
+ rewriter, storeOp.getVectorType().getNumElements());
if (!accessChain)
return rewriter.notifyMatchFailure(
storeOp, "failed to get memref element pointer");
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/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp b/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp
index ef4d79c827bb2..6a27e9d16bb32 100644
--- a/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp
+++ b/mlir/lib/Dialect/SPIRV/Transforms/SPIRVConversion.cpp
@@ -28,9 +28,11 @@
#include "mlir/Support/LLVM.h"
#include "mlir/Transforms/DialectConversion.h"
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
+#include "llvm/ADT/APInt.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/CheckedArithmetic.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/MathExtras.h"
@@ -1283,6 +1285,91 @@ struct ReturnOpVectorUnroll final : OpRewritePattern<func::ReturnOp> {
}
};
+static void addNoWrapDecorations(Operation *op,
+ spirv::LinearizedIndexNoWrapFlags flags,
+ OpBuilder &builder) {
+ if (flags.noSignedWrap)
+ op->setAttr(spirv::getDecorationString(spirv::Decoration::NoSignedWrap),
+ builder.getUnitAttr());
+ if (flags.noUnsignedWrap)
+ op->setAttr(spirv::getDecorationString(spirv::Decoration::NoUnsignedWrap),
+ builder.getUnitAttr());
+}
+
+static std::optional<uint64_t> getMaxLinearizedIndex(ArrayRef<int64_t> shape,
+ ArrayRef<int64_t> strides,
+ int64_t offset) {
+ if (shape.size() != strides.size() || offset < 0)
+ return std::nullopt;
+
+ uint64_t maxLinearIndex = offset;
+ for (auto [dimension, stride] : llvm::zip(shape, strides)) {
+ if (dimension <= 0 || stride < 0)
+ return std::nullopt;
+ std::optional<uint64_t> nextMaxLinearIndex = llvm::checkedMulAddUnsigned(
+ static_cast<uint64_t>(dimension - 1), static_cast<uint64_t>(stride),
+ maxLinearIndex);
+ if (!nextMaxLinearIndex)
+ return std::nullopt;
+ maxLinearIndex = *nextMaxLinearIndex;
+ }
+ return maxLinearIndex;
+}
+
+static std::optional<uint64_t> getStorageBufferElementCount(Value basePtr) {
+ auto pointerType = dyn_cast<spirv::PointerType>(basePtr.getType());
+ if (!pointerType ||
+ pointerType.getStorageClass() != spirv::StorageClass::StorageBuffer)
+ return std::nullopt;
+
+ Type pointeeType = pointerType.getPointeeType();
+ if (auto structType = dyn_cast<spirv::StructType>(pointeeType)) {
+ if (structType.getNumElements() != 1)
+ return std::nullopt;
+ pointeeType = structType.getElementType(0);
+ }
+ auto arrayType = dyn_cast<spirv::ArrayType>(pointeeType);
+ if (!arrayType)
+ return std::nullopt;
+ return arrayType.getNumElements();
+}
+
+static bool shouldEmitInBoundsAccessChain(MemRefType baseType, Value basePtr,
+ ArrayRef<int64_t> strides,
+ int64_t offset,
+ uint64_t accessElementCount) {
+ // Sub-16-bit integer memrefs may be stored using a wider SPIR-V array element
+ // than the source element. Keep a plain access chain so later bitwidth
+ // emulation can adjust the final index in storage-element units.
+ if (auto integerType = dyn_cast<IntegerType>(baseType.getElementType()))
+ if (integerType.getWidth() < 16)
+ return false;
+
+ std::optional<uint64_t> maxLinearIndex =
+ getMaxLinearizedIndex(baseType.getShape(), strides, offset);
+ std::optional<uint64_t> objectElementCount =
+ getStorageBufferElementCount(basePtr);
+ if (!maxLinearIndex || !objectElementCount || !accessElementCount ||
+ accessElementCount > *objectElementCount)
+ return false;
+
+ // The source memory operation guarantees that its dynamic indices, including
+ // a vector access width, are in bounds. The static layout proof here ensures
+ // that this contract describes the same fixed-size SPIR-V buffer object.
+ return *maxLinearIndex < *objectElementCount;
+}
+
+static spirv::LinearizedIndexNoWrapFlags
+shouldEmitNoWrapDecorations(const SPIRVTypeConverter &typeConverter,
+ MemRefType baseType, ArrayRef<int64_t> strides,
+ int64_t offset, Type indexType) {
+ if (!typeConverter.getTargetEnv().allows(
+ spirv::Extension::SPV_KHR_no_integer_wrap_decoration))
+ return {};
+ return spirv::getLinearizedIndexNoWrapFlags(baseType.getShape(), strides,
+ offset, indexType);
+}
+
} // namespace
//===----------------------------------------------------------------------===//
@@ -1336,9 +1423,32 @@ Value spirv::getPushConstantValue(Operation *op, unsigned elementCount,
// Public functions for index calculation
//===----------------------------------------------------------------------===//
+mlir::spirv::LinearizedIndexNoWrapFlags
+mlir::spirv::getLinearizedIndexNoWrapFlags(ArrayRef<int64_t> shape,
+ ArrayRef<int64_t> strides,
+ int64_t offset, Type integerType) {
+ LinearizedIndexNoWrapFlags flags;
+ auto integer = dyn_cast<IntegerType>(integerType);
+ if (!integer)
+ return flags;
+
+ std::optional<uint64_t> maxLinearIndex =
+ getMaxLinearizedIndex(shape, strides, offset);
+ if (!maxLinearIndex)
+ return flags;
+
+ flags.noSignedWrap =
+ *maxLinearIndex <=
+ APInt::getSignedMaxValue(integer.getWidth()).getZExtValue();
+ flags.noUnsignedWrap =
+ *maxLinearIndex <= APInt::getMaxValue(integer.getWidth()).getZExtValue();
+ return flags;
+}
+
Value mlir::spirv::linearizeIndex(ValueRange indices, ArrayRef<int64_t> strides,
int64_t offset, Type integerType,
- Location loc, OpBuilder &builder) {
+ Location loc, OpBuilder &builder,
+ LinearizedIndexNoWrapFlags noWrapFlags) {
assert(indices.size() == strides.size() &&
"must provide indices for all dimensions");
@@ -1355,8 +1465,15 @@ Value mlir::spirv::linearizeIndex(ValueRange indices, ArrayRef<int64_t> strides,
IntegerAttr::get(integerType, strides[index.index()]));
Value update =
builder.createOrFold<spirv::IMulOp>(loc, index.value(), strideVal);
+ if (noWrapFlags.noSignedWrap || noWrapFlags.noUnsignedWrap)
+ if (auto mul = update.getDefiningOp<spirv::IMulOp>())
+ addNoWrapDecorations(mul, noWrapFlags, builder);
+
linearizedIndex =
builder.createOrFold<spirv::IAddOp>(loc, update, linearizedIndex);
+ if (noWrapFlags.noSignedWrap || noWrapFlags.noUnsignedWrap)
+ if (auto add = linearizedIndex.getDefiningOp<spirv::IAddOp>())
+ addNoWrapDecorations(add, noWrapFlags, builder);
}
return linearizedIndex;
}
@@ -1364,7 +1481,8 @@ Value mlir::spirv::linearizeIndex(ValueRange indices, ArrayRef<int64_t> strides,
Value mlir::spirv::getVulkanElementPtr(const SPIRVTypeConverter &typeConverter,
MemRefType baseType, Value basePtr,
ValueRange indices, Location loc,
- OpBuilder &builder) {
+ OpBuilder &builder,
+ uint64_t accessElementCount) {
// Get base and offset of the MemRefType and verify they are static.
int64_t offset;
@@ -1376,6 +1494,8 @@ Value mlir::spirv::getVulkanElementPtr(const SPIRVTypeConverter &typeConverter,
}
auto indexType = typeConverter.getIndexType();
+ LinearizedIndexNoWrapFlags noWrapFlags = shouldEmitNoWrapDecorations(
+ typeConverter, baseType, strides, offset, indexType);
SmallVector<Value, 2> linearizedIndices;
auto zero = spirv::ConstantOp::getZero(indexType, loc, builder);
@@ -1383,8 +1503,8 @@ Value mlir::spirv::getVulkanElementPtr(const SPIRVTypeConverter &typeConverter,
if (baseType.getRank() == 0) {
linearizedIndices.push_back(zero);
} else {
- linearizedIndices.push_back(
- linearizeIndex(indices, strides, offset, indexType, loc, builder));
+ linearizedIndices.push_back(linearizeIndex(
+ indices, strides, offset, indexType, loc, builder, noWrapFlags));
}
const Type pointeeType =
@@ -1392,9 +1512,21 @@ Value mlir::spirv::getVulkanElementPtr(const SPIRVTypeConverter &typeConverter,
// Interface memrefs are wrapped in...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/216096
More information about the Mlir-commits
mailing list