[Mlir-commits] [mlir] [mlir][shape] Don't crash on poison operands in folders (PR #226671)

Chaitanya Mamatha Ananda llvmlistbot at llvm.org
Sat Sep 26 03:26:47 PDT 2026


chaitanyaupp18 wrote:

Thanks! I have read the AI Tool Use Policy, the Code-Review Policy and the
Developer Policy. I used Claude to help me understand the code and review
my changes, this is noted in the PR description. I reproduced each crash,
wrote and verified the fix and tests myself, and can answer questions about
any part of the change.

On Sat, Sep 26, 2026 at 3:23 AM llvmorg-github-actions[bot] <
***@***.***> wrote:

> *llvmorg-github-actions[bot]* left a comment (llvm/llvm-project#226671)
> <https://github.com/llvm/llvm-project/pull/226671#issuecomment-5845449755>
>
> @llvm/pr-subscribers-mlir-shape
> <https://github.com/orgs/llvm/teams/pr-subscribers-mlir-shape>
>
> Author: Chaitanya Mamatha Ananda (chaitanyaupp18)
> Changes
>
> Several shape folders used an unchecked llvm::cast on their folded
> operands, assuming any non-null attribute was a DenseIntElementsAttr (or
> IntegerAttr). A ub.poison operand folds to #ub.poison, so these casts
> asserted.
>
> This change checks with dyn_cast_if_present first and skips folding if
> the attribute is anything else, like the other folders in this file already
> do.
> Folders fixed
>
>    - ConcatOp and CstrBroadcastableOp (including the
>    hasAtMostSingleNonScalar helper), reported in the issue
>    - NumElementsOp, SplitAtOp and ToExtentTensorOp, found while fixing
>    the reported ones
>
> Notes
>
>    - In hasAtMostSingleNonScalar, poison is treated like a non-constant
>    operand. Folding to true is still valid there, because the result
>    doesn't depend on that operand's value.
>    - Each new test in mlir/test/Dialect/Shape/canonicalize.mlir crashes
>    without this change and passes with it.
>
> Part of #226067 (the affine and scf cases are not addressed here).
>
> Assisted-by: Claude
> ------------------------------
>
> Full diff: https://github.com/llvm/llvm-project/pull/226671.diff
>
> 2 Files Affected:
>
>    - (modified) mlir/lib/Dialect/Shape/IR/Shape.cpp (+24-20)
>    - (modified) mlir/test/Dialect/Shape/canonicalize.mlir (+89)
>
> diff --git a/mlir/lib/Dialect/Shape/IR/Shape.cpp b/mlir/lib/Dialect/Shape/IR/Shape.cpp
> index e01b705e5610a..71d1ce9b39631 100644--- a/mlir/lib/Dialect/Shape/IR/Shape.cpp+++ b/mlir/lib/Dialect/Shape/IR/Shape.cpp@@ -877,12 +877,12 @@ void BroadcastOp::getCanonicalizationPatterns(RewritePatternSet &patterns,
>  //===----------------------------------------------------------------------===//
>
>  OpFoldResult ConcatOp::fold(FoldAdaptor adaptor) {-  if (!adaptor.getLhs() || !adaptor.getRhs())+  auto lhs = llvm::dyn_cast_if_present<DenseIntElementsAttr>(adaptor.getLhs());+  auto rhs = llvm::dyn_cast_if_present<DenseIntElementsAttr>(adaptor.getRhs());+  if (!lhs || !rhs)
>      return nullptr;-  auto lhsShape = llvm::to_vector<6>(-      llvm::cast<DenseIntElementsAttr>(adaptor.getLhs()).getValues<int64_t>());-  auto rhsShape = llvm::to_vector<6>(-      llvm::cast<DenseIntElementsAttr>(adaptor.getRhs()).getValues<int64_t>());+  auto lhsShape = llvm::to_vector<6>(lhs.getValues<int64_t>());+  auto rhsShape = llvm::to_vector<6>(rhs.getValues<int64_t>());
>    SmallVector<int64_t, 6> resultShape;
>    resultShape.append(lhsShape.begin(), lhsShape.end());
>    resultShape.append(rhsShape.begin(), rhsShape.end());@@ -983,7 +983,9 @@ void CstrBroadcastableOp::getCanonicalizationPatterns(
>  static bool hasAtMostSingleNonScalar(ArrayRef<Attribute> attributes) {
>    bool nonScalarSeen = false;
>    for (Attribute a : attributes) {-    if (!a || llvm::cast<DenseIntElementsAttr>(a).getNumElements() != 0) {+    // Treat non-constant and poison operands as potentially non-scalar.+    auto dense = llvm::dyn_cast_if_present<DenseIntElementsAttr>(a);+    if (!dense || dense.getNumElements() != 0) {
>        if (nonScalarSeen)
>          return false;
>        nonScalarSeen = true;@@ -1000,10 +1002,10 @@ OpFoldResult CstrBroadcastableOp::fold(FoldAdaptor adaptor) {
>    if ([&] {
>          SmallVector<SmallVector<int64_t, 6>, 6> extents;
>          for (const auto &operand : adaptor.getShapes()) {-          if (!operand)+          auto dense = llvm::dyn_cast_if_present<DenseIntElementsAttr>(operand);+          if (!dense)
>              return false;-          extents.push_back(llvm::to_vector<6>(-              llvm::cast<DenseIntElementsAttr>(operand).getValues<int64_t>()));+          extents.push_back(llvm::to_vector<6>(dense.getValues<int64_t>()));
>          }
>          return OpTrait::util::staticallyKnownBroadcastable(extents);
>        }())@@ -1571,12 +1573,13 @@ LogicalResult shape::RankOp::verify() { return verifySizeOrIndexOp(*this); }
>  OpFoldResult NumElementsOp::fold(FoldAdaptor adaptor) {
>
>    // Fold only when argument constant.-  Attribute shape = adaptor.getShape();+  auto shape =+      llvm::dyn_cast_if_present<DenseIntElementsAttr>(adaptor.getShape());
>    if (!shape)
>      return {};
>
>    APInt product(64, 1);-  for (auto value : llvm::cast<DenseIntElementsAttr>(shape))+  for (auto value : shape)
>      product *= value;
>    Builder builder(getContext());
>    return builder.getIndexAttr(product.getLimitedValue());@@ -1913,13 +1916,14 @@ LogicalResult shape::YieldOp::verify() {
>
>  LogicalResult SplitAtOp::fold(FoldAdaptor adaptor,
>                                SmallVectorImpl<OpFoldResult> &results) {-  if (!adaptor.getOperand() || !adaptor.getIndex())+  auto shapeAttr =+      llvm::dyn_cast_if_present<DenseIntElementsAttr>(adaptor.getOperand());+  auto indexAttr = llvm::dyn_cast_if_present<IntegerAttr>(adaptor.getIndex());+  if (!shapeAttr || !indexAttr)
>      return failure();-  auto shapeVec =-      llvm::to_vector<6>(llvm::cast<DenseIntElementsAttr>(adaptor.getOperand())-                             .getValues<int64_t>());+  auto shapeVec = llvm::to_vector<6>(shapeAttr.getValues<int64_t>());
>    auto shape = llvm::ArrayRef(shapeVec);-  auto splitPoint = llvm::cast<IntegerAttr>(adaptor.getIndex()).getInt();+  auto splitPoint = indexAttr.getInt();
>    // Verify that the split point is in the correct range.
>    // TODO: Constant fold to an "error".
>    int64_t rank = shape.size();@@ -1938,12 +1942,12 @@ LogicalResult SplitAtOp::fold(FoldAdaptor adaptor,
>  //===----------------------------------------------------------------------===//
>
>  OpFoldResult ToExtentTensorOp::fold(FoldAdaptor adaptor) {-  if (!adaptor.getInput())+  auto inputAttr =+      llvm::dyn_cast_if_present<DenseIntElementsAttr>(adaptor.getInput());+  if (!inputAttr)
>      return OpFoldResult();
>    Builder builder(getContext());-  auto shape =-      llvm::to_vector<6>(llvm::cast<DenseIntElementsAttr>(adaptor.getInput())-                             .getValues<int64_t>());+  auto shape = llvm::to_vector<6>(inputAttr.getValues<int64_t>());
>    auto type = RankedTensorType::get({static_cast<int64_t>(shape.size())},
>                                      builder.getIndexType());
>    return DenseIntElementsAttr::get(type, shape);diff --git a/mlir/test/Dialect/Shape/canonicalize.mlir b/mlir/test/Dialect/Shape/canonicalize.mlir
> index 89aa167360168..b8399e23ab811 100644--- a/mlir/test/Dialect/Shape/canonicalize.mlir+++ b/mlir/test/Dialect/Shape/canonicalize.mlir@@ -1685,3 +1685,92 @@ func.func @extract_from_shape_of_memref_no_fold(%arg0: memref<?xf32>) -> index {
>    %dim = tensor.extract %shape[%c0] : tensor<1xindex>
>    return %dim : index
>  }++// -----++// Regression tests for https://github.com/llvm/llvm-project/issues/226067:+// several shape folders used to crash with an unchecked cast when an operand+// was ub.poison (a non-DenseIntElementsAttr attribute). The folds must bail+// out gracefully instead.+// CHECK-LABEL: func @concat_poison+func.func @concat_poison() -> tensor<4xindex> {+  // CHECK: shape.concat+  %lhs = shape.const_shape [0, 1] : tensor<2xindex>+  %rhs = ub.poison : tensor<2xindex>+  %0 = shape.concat %lhs, %rhs : tensor<2xindex>, tensor<2xindex> -> tensor<4xindex>+  return %0 : tensor<4xindex>+}++// -----++// A poison operand must not crash the folder.+// CHECK-LABEL: func @cstr_broadcastable_poison+func.func @cstr_broadcastable_poison(%arg : tensor<?x4xf32>) {+  // CHECK: shape.cstr_broadcastable+  %0 = shape.shape_of %arg : tensor<?x4xf32> -> tensor<2xindex>+  %1 = ub.poison : tensor<1xindex>+  %2 = shape.cstr_broadcastable %0, %1 : tensor<2xindex>, tensor<1xindex>+  "use"(%2) : (!shape.witness) -> ()+  return+}++// -----++// A poison operand must not crash the folder.+// CHECK-LABEL: func @num_elements_poison+func.func @num_elements_poison() -> index {+  // CHECK: shape.num_elements+  %s = ub.poison : tensor<2xindex>+  %n = shape.num_elements %s : tensor<2xindex> -> index+  return %n : index+}++// -----++// A poison operand must not crash the folder.+// CHECK-LABEL: func @split_at_poison_shape+// CHECK: shape.split_at+func.func @split_at_poison_shape() -> (!shape.shape, !shape.shape) {+  %c2 = arith.constant 2 : index+  %0 = ub.poison : !shape.shape+  %head, %tail = "shape.split_at"(%0, %c2) : (!shape.shape, index) -> (!shape.shape, !shape.shape)+  return %head, %tail : !shape.shape, !shape.shape+}++// -----++// A poison operand must not crash the folder.+// CHECK-LABEL: func @split_at_poison_index+// CHECK: shape.split_at+func.func @split_at_poison_index() -> (!shape.shape, !shape.shape) {+  %c2 = ub.poison : index+  %0 = shape.const_shape [2, 3, 4, 5] : !shape.shape+  %head, %tail = "shape.split_at"(%0, %c2) : (!shape.shape, index) -> (!shape.shape, !shape.shape)+  return %head, %tail : !shape.shape, !shape.shape+}++// -----++// A poison operand must not crash the folder.+// CHECK-LABEL: func @to_extent_tensor_poison+// CHECK: shape.to_extent_tensor+func.func @to_extent_tensor_poison() -> tensor<2xindex> {+  %cs = ub.poison : !shape.shape+  %0 = shape.to_extent_tensor %cs : !shape.shape -> tensor<2xindex>+  return %0 : tensor<2xindex>+}++// -----++// A poison operand must not crash the folder, even when it is only reached+// after the scalar check has already given up.+// CHECK-LABEL: func @cstr_broadcastable_poison_after_non_scalars+func.func @cstr_broadcastable_poison_after_non_scalars() {+  // CHECK: shape.cstr_broadcastable+  %0 = shape.const_shape [1, 3] : tensor<2xindex>+  %1 = shape.const_shape [3] : tensor<1xindex>+  %2 = ub.poison : tensor<1xindex>+  %3 = shape.cstr_broadcastable %0, %1, %2 : tensor<2xindex>, tensor<1xindex>, tensor<1xindex>+  "use"(%3) : (!shape.witness) -> ()+  return+}
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/llvm/llvm-project/pull/226671?email_source=notifications&email_token=AKNR56DE42DVBG4NJLIYKF35Q6KK3A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKOBUGU2DIOJXGU22M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5845449755>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AKNR56CDYN2EZLQ47UKJBUL5Q6KK3AVCNFSNUABEKJSXA33TNF2G64TZHM3TKOBSGE2DGMR3JFZXG5LFHM2TKOJTGI2DMNJUHGQXMAQ>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>


https://github.com/llvm/llvm-project/pull/226671


More information about the Mlir-commits mailing list