[llvm] [SLP] Fix misvectorization in commutative to non-commutative conversion (PR #185230)

via llvm-commits llvm-commits at lists.llvm.org
Sat Mar 7 13:38:31 PST 2026


https://github.com/tudinhh created https://github.com/llvm/llvm-project/pull/185230

**Summary**
Fixes a miscompilation where commutative operations (e.g., or, and, mul) with a left-hand side constant were incorrectly transformed into non-commutative operations (e.g., shl, sub).

**The Problem**
In BinOpSameOpcodeHelper::getOperand, when a constant is at Pos == 0, the helper was failing to swap operand order for new non-commutative target opcodes. This resulted in inverted logic, such as transforming `or 0, %x` into `shl 0, %x` (resulting in 0) instead of the correct `%x << 0`.

**The Fix**
The existing logic only protected the Sub opcode. This patch generalizes the fix to all non-commutative instructions by using !Instruction::isCommutative(ToOpcode). This ensures that for any directional operation, the variable is correctly placed on the LHS and the constant on the RHS.

**Changes**
SLPVectorizer.cpp: Replaced the specific Sub check with a general isCommutative check.

Regression Test: Added lhs-constant-non-cummutative.ll to cover shl, sub, and ashr targets.

Fixes #185186

>From 887bc7cb47ca042728073e4d39c20e30c5bcb59c Mon Sep 17 00:00:00 2001
From: tudinhh <anhtu.dinh1202 at gmail.com>
Date: Sat, 7 Mar 2026 22:25:12 +0100
Subject: [PATCH] [SLP] Fix misvectorization when converting commutative to
 non-commutative opcodes

The BinOpSameOpcodeHelper::getOperand function was incorrectly preserving
the original operand order when converting a commutative operation
(like Or/And/Mul) with a constant on the left-hand side into a
non-commutative operation (like Shl/Sub).

When the original constant was at position 0 (Pos == 0), the helper
returned {Constant, Variable}. For non-commutative target opcodes, this
resulted in mathematically incorrect IR, such as 'shl 0, %x' instead of
'%x << 0'.

This patch widens the existing fix (which previously only handled Sub) to
any non-commutative instruction using Instruction::isCommutative. This
ensures that for any directional operation, the variable is forced to
the left-hand side and the constant to the right-hand side.

Fixes #185186
---
 .../Transforms/Vectorize/SLPVectorizer.cpp    | 10 ++---
 .../X86/lhs-constant-non-cummutative.ll       | 39 +++++++++++++++++++
 2 files changed, 43 insertions(+), 6 deletions(-)
 create mode 100644 llvm/test/Transforms/SLPVectorizer/X86/lhs-constant-non-cummutative.ll

diff --git a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
index 631af61e2cfba..df7885e31741e 100644
--- a/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
+++ b/llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp
@@ -1132,13 +1132,11 @@ class BinOpSameOpcodeHelper {
         break;
       }
       Value *LHS = I->getOperand(1 - Pos);
-      // constant + x cannot be -constant - x
-      // instead, it should be x - -constant
-      if (Pos == 1 ||
-          ((FromOpcode == Instruction::Add || FromOpcode == Instruction::Or ||
-            FromOpcode == Instruction::Xor) &&
-           ToOpcode == Instruction::Sub))
+      // If the target opcode is non-commutative (e.g., shl, sub),
+      // force the variable to the left and the constant to the right.
+      if (Pos == 1 || !Instruction::isCommutative(ToOpcode))
         return SmallVector<Value *>({LHS, RHS});
+
       return SmallVector<Value *>({RHS, LHS});
     }
   };
diff --git a/llvm/test/Transforms/SLPVectorizer/X86/lhs-constant-non-cummutative.ll b/llvm/test/Transforms/SLPVectorizer/X86/lhs-constant-non-cummutative.ll
new file mode 100644
index 0000000000000..5a33d5f5bac5b
--- /dev/null
+++ b/llvm/test/Transforms/SLPVectorizer/X86/lhs-constant-non-cummutative.ll
@@ -0,0 +1,39 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S --passes=slp-vectorizer -slp-threshold=-50 -mtriple=x86_64-unknown-linux-gnu < %s | FileCheck %s
+
+define void @or_lhs_to_shl(ptr %p, ptr %s) {
+; CHECK-LABEL: define void @or_lhs_to_shl(
+; CHECK-SAME: ptr [[P:%.*]], ptr [[S:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = load <4 x i16>, ptr [[P]], align 2
+; CHECK-NEXT:    [[TMP1:%.*]] = shl <4 x i16> [[TMP0]], <i16 1, i16 2, i16 3, i16 0>
+; CHECK-NEXT:    store <4 x i16> [[TMP1]], ptr [[S]], align 2
+; CHECK-NEXT:    ret void
+;
+entry:
+  %p1 = getelementptr i16, ptr %p, i64 1
+  %p2 = getelementptr i16, ptr %p, i64 2
+  %p3 = getelementptr i16, ptr %p, i64 3
+
+  %l0 = load i16, ptr %p, align 2
+  %l1 = load i16, ptr %p1, align 2
+  %l2 = load i16, ptr %p2, align 2
+  %l3 = load i16, ptr %p3, align 2
+
+  ; 3 shl instructions force the vectorizer to choose shl as the target
+  %op0 = shl i16 %l0, 1
+  %op1 = shl i16 %l1, 2
+  %op2 = shl i16 %l2, 3
+  %op3 = or i16 0, %l3 ; The buggy instruction
+
+  %s1 = getelementptr i16, ptr %s, i64 1
+  %s2 = getelementptr i16, ptr %s, i64 2
+  %s3 = getelementptr i16, ptr %s, i64 3
+
+  store i16 %op0, ptr %s, align 2
+  store i16 %op1, ptr %s1, align 2
+  store i16 %op2, ptr %s2, align 2
+  store i16 %op3, ptr %s3, align 2
+
+  ret void
+}



More information about the llvm-commits mailing list