Skip to content

[SLP] Fix assertion "Deleting out-of-tree value" for struct-typed vectorized operands#199679

Merged
alexey-bataev merged 1 commit into
mainfrom
users/alexey-bataev/spr/slp-fix-assertion-deleting-out-of-tree-value-for-struct-typed-vectorized-operands
May 26, 2026
Merged

[SLP] Fix assertion "Deleting out-of-tree value" for struct-typed vectorized operands#199679
alexey-bataev merged 1 commit into
mainfrom
users/alexey-bataev/spr/slp-fix-assertion-deleting-out-of-tree-value-for-struct-typed-vectorized-operands

Conversation

@alexey-bataev
Copy link
Copy Markdown
Member

When an external-use scalar had a struct-typed vectorized operand, OperandIsScalar
incorrectly returned true (because the struct scalar was in ValueToExtUses), causing
the scalar to be cloned into ExternalUsesAsOriginalScalar. The clone used the struct
scalar directly, but struct-typed vectorized scalars are deleted after vectorization

  • their external-use handling erases the extractvalue user rather than replacing the
    struct value via replaceAllUsesWith.

Created using spr 1.3.7
@alexey-bataev alexey-bataev merged commit 095faea into main May 26, 2026
7 of 10 checks passed
@alexey-bataev alexey-bataev deleted the users/alexey-bataev/spr/slp-fix-assertion-deleting-out-of-tree-value-for-struct-typed-vectorized-operands branch May 26, 2026 13:33
@llvmorg-github-actions
Copy link
Copy Markdown

@llvm/pr-subscribers-llvm-transforms

Author: Alexey Bataev (alexey-bataev)

Changes

When an external-use scalar had a struct-typed vectorized operand, OperandIsScalar
incorrectly returned true (because the struct scalar was in ValueToExtUses), causing
the scalar to be cloned into ExternalUsesAsOriginalScalar. The clone used the struct
scalar directly, but struct-typed vectorized scalars are deleted after vectorization

  • their external-use handling erases the extractvalue user rather than replacing the
    struct value via replaceAllUsesWith.

Full diff: https://github.com/llvm/llvm-project/pull/199679.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp (+2)
  • (added) llvm/test/Transforms/SLPVectorizer/AArch64/slp-extval-struct-operand-no-scalar-reuse.ll (+38)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index bdd5311ab55ad..caee72eb0547a 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -19708,6 +19708,8 @@ InstructionCost BoUpSLP::getTreeCost(InstructionCost TreeCost,
             return !EE->hasOneUse() || !MustGather.contains(EE);
           return true;
         }
+        if (isa<StructType>(V->getType()))
+          return false;
         return ValueToExtUses->contains(V);
       };
       bool CanBeUsedAsScalar = all_of(Inst->operands(), OperandIsScalar);
diff --git a/llvm/test/Transforms/SLPVectorizer/AArch64/slp-extval-struct-operand-no-scalar-reuse.ll b/llvm/test/Transforms/SLPVectorizer/AArch64/slp-extval-struct-operand-no-scalar-reuse.ll
new file mode 100644
index 0000000000000..2f84803994136
--- /dev/null
+++ b/llvm/test/Transforms/SLPVectorizer/AArch64/slp-extval-struct-operand-no-scalar-reuse.ll
@@ -0,0 +1,38 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S --passes=slp-vectorizer -slp-threshold=-10 -mtriple=arm64-apple-macosx12.0.0 < %s | FileCheck %s
+
+
+define [2 x i64] @test() {
+; CHECK-LABEL: define [2 x i64] @test() {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call <2 x i32> @llvm.sadd.sat.v2i32(<2 x i32> zeroinitializer, <2 x i32> zeroinitializer)
+; CHECK-NEXT:    [[TMP1:%.*]] = call { <2 x i32>, <2 x i1> } @llvm.ssub.with.overflow.v2i32(<2 x i32> zeroinitializer, <2 x i32> [[TMP0]])
+; CHECK-NEXT:    [[TMP2:%.*]] = extractvalue { <2 x i32>, <2 x i1> } [[TMP1]], 0
+; CHECK-NEXT:    [[TMP3:%.*]] = extractelement <2 x i32> [[TMP2]], i32 0
+; CHECK-NEXT:    [[TMP4:%.*]] = extractvalue { <2 x i32>, <2 x i1> } [[TMP1]], 1
+; CHECK-NEXT:    [[TMP5:%.*]] = extractelement <2 x i1> [[TMP4]], i32 0
+; CHECK-NEXT:    [[RETVAL_SROA_5_8_INSERT_SHIFT_I52_I:%.*]] = select i1 [[TMP5]], i64 0, i64 0
+; CHECK-NEXT:    [[TMP6:%.*]] = zext <2 x i1> [[TMP4]] to <2 x i64>
+; CHECK-NEXT:    store <2 x i64> [[TMP6]], ptr getelementptr inbounds nuw (i8, ptr null, i64 104), align 8
+; CHECK-NEXT:    ret [2 x i64] zeroinitializer
+;
+entry:
+  %retval.0.i.i.i.i.i14.i.i.i = tail call i32 @llvm.sadd.sat.i32(i32 0, i32 0)
+  %0 = tail call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 0, i32 %retval.0.i.i.i.i.i14.i.i.i)
+  %1 = extractvalue { i32, i1 } %0, 0
+  %2 = extractvalue { i32, i1 } %0, 1
+  %3 = zext i1 %2 to i64
+  store i64 %3, ptr getelementptr inbounds nuw (i8, ptr null, i64 104), align 8
+  %retval.sroa.5.8.insert.shift.i52.i = select i1 %2, i64 0, i64 0
+  %retval.0.i.i.i.i.i13.i.i.i = tail call i32 @llvm.sadd.sat.i32(i32 0, i32 0)
+  %4 = tail call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 0, i32 %retval.0.i.i.i.i.i13.i.i.i)
+  %5 = extractvalue { i32, i1 } %4, 1
+  %6 = zext i1 %5 to i64
+  store i64 %6, ptr getelementptr inbounds nuw (i8, ptr null, i64 112), align 8
+  ret [2 x i64] zeroinitializer
+}
+
+declare { i32, i1 } @llvm.ssub.with.overflow.i32(i32, i32)
+
+declare i32 @llvm.sadd.sat.i32(i32, i32)
+

@llvmorg-github-actions
Copy link
Copy Markdown

@llvm/pr-subscribers-vectorizers

Author: Alexey Bataev (alexey-bataev)

Changes

When an external-use scalar had a struct-typed vectorized operand, OperandIsScalar
incorrectly returned true (because the struct scalar was in ValueToExtUses), causing
the scalar to be cloned into ExternalUsesAsOriginalScalar. The clone used the struct
scalar directly, but struct-typed vectorized scalars are deleted after vectorization

  • their external-use handling erases the extractvalue user rather than replacing the
    struct value via replaceAllUsesWith.

Full diff: https://github.com/llvm/llvm-project/pull/199679.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp (+2)
  • (added) llvm/test/Transforms/SLPVectorizer/AArch64/slp-extval-struct-operand-no-scalar-reuse.ll (+38)
diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index bdd5311ab55ad..caee72eb0547a 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -19708,6 +19708,8 @@ InstructionCost BoUpSLP::getTreeCost(InstructionCost TreeCost,
             return !EE->hasOneUse() || !MustGather.contains(EE);
           return true;
         }
+        if (isa<StructType>(V->getType()))
+          return false;
         return ValueToExtUses->contains(V);
       };
       bool CanBeUsedAsScalar = all_of(Inst->operands(), OperandIsScalar);
diff --git a/llvm/test/Transforms/SLPVectorizer/AArch64/slp-extval-struct-operand-no-scalar-reuse.ll b/llvm/test/Transforms/SLPVectorizer/AArch64/slp-extval-struct-operand-no-scalar-reuse.ll
new file mode 100644
index 0000000000000..2f84803994136
--- /dev/null
+++ b/llvm/test/Transforms/SLPVectorizer/AArch64/slp-extval-struct-operand-no-scalar-reuse.ll
@@ -0,0 +1,38 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S --passes=slp-vectorizer -slp-threshold=-10 -mtriple=arm64-apple-macosx12.0.0 < %s | FileCheck %s
+
+
+define [2 x i64] @test() {
+; CHECK-LABEL: define [2 x i64] @test() {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call <2 x i32> @llvm.sadd.sat.v2i32(<2 x i32> zeroinitializer, <2 x i32> zeroinitializer)
+; CHECK-NEXT:    [[TMP1:%.*]] = call { <2 x i32>, <2 x i1> } @llvm.ssub.with.overflow.v2i32(<2 x i32> zeroinitializer, <2 x i32> [[TMP0]])
+; CHECK-NEXT:    [[TMP2:%.*]] = extractvalue { <2 x i32>, <2 x i1> } [[TMP1]], 0
+; CHECK-NEXT:    [[TMP3:%.*]] = extractelement <2 x i32> [[TMP2]], i32 0
+; CHECK-NEXT:    [[TMP4:%.*]] = extractvalue { <2 x i32>, <2 x i1> } [[TMP1]], 1
+; CHECK-NEXT:    [[TMP5:%.*]] = extractelement <2 x i1> [[TMP4]], i32 0
+; CHECK-NEXT:    [[RETVAL_SROA_5_8_INSERT_SHIFT_I52_I:%.*]] = select i1 [[TMP5]], i64 0, i64 0
+; CHECK-NEXT:    [[TMP6:%.*]] = zext <2 x i1> [[TMP4]] to <2 x i64>
+; CHECK-NEXT:    store <2 x i64> [[TMP6]], ptr getelementptr inbounds nuw (i8, ptr null, i64 104), align 8
+; CHECK-NEXT:    ret [2 x i64] zeroinitializer
+;
+entry:
+  %retval.0.i.i.i.i.i14.i.i.i = tail call i32 @llvm.sadd.sat.i32(i32 0, i32 0)
+  %0 = tail call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 0, i32 %retval.0.i.i.i.i.i14.i.i.i)
+  %1 = extractvalue { i32, i1 } %0, 0
+  %2 = extractvalue { i32, i1 } %0, 1
+  %3 = zext i1 %2 to i64
+  store i64 %3, ptr getelementptr inbounds nuw (i8, ptr null, i64 104), align 8
+  %retval.sroa.5.8.insert.shift.i52.i = select i1 %2, i64 0, i64 0
+  %retval.0.i.i.i.i.i13.i.i.i = tail call i32 @llvm.sadd.sat.i32(i32 0, i32 0)
+  %4 = tail call { i32, i1 } @llvm.ssub.with.overflow.i32(i32 0, i32 %retval.0.i.i.i.i.i13.i.i.i)
+  %5 = extractvalue { i32, i1 } %4, 1
+  %6 = zext i1 %5 to i64
+  store i64 %6, ptr getelementptr inbounds nuw (i8, ptr null, i64 112), align 8
+  ret [2 x i64] zeroinitializer
+}
+
+declare { i32, i1 } @llvm.ssub.with.overflow.i32(i32, i32)
+
+declare i32 @llvm.sadd.sat.i32(i32, i32)
+

llvm-upstreamsync Bot pushed a commit to qualcomm/cpullvm-toolchain that referenced this pull request May 26, 2026
…t-typed vectorized operands

When an external-use scalar had a struct-typed vectorized operand, OperandIsScalar
incorrectly returned true (because the struct scalar was in ValueToExtUses), causing
the scalar to be cloned into ExternalUsesAsOriginalScalar. The clone used the struct
scalar directly, but struct-typed vectorized scalars are deleted after vectorization
- their external-use handling erases the extractvalue user rather than replacing the
struct value via replaceAllUsesWith.

Reviewers:

Pull Request: llvm/llvm-project#199679
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request May 26, 2026
…t-typed vectorized operands

When an external-use scalar had a struct-typed vectorized operand, OperandIsScalar
incorrectly returned true (because the struct scalar was in ValueToExtUses), causing
the scalar to be cloned into ExternalUsesAsOriginalScalar. The clone used the struct
scalar directly, but struct-typed vectorized scalars are deleted after vectorization
- their external-use handling erases the extractvalue user rather than replacing the
struct value via replaceAllUsesWith.

Reviewers:

Pull Request: llvm/llvm-project#199679
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant