Issue 185186
Summary [SLP] `mul/or/and` with LHS constant can be misvectorized into wrong `shl/sub` operand order
Labels new issue
Assignees
Reporter cardigan1008
    Here is a miscompilation case when reviewing https://github.com/llvm/llvm-project/pull/180457:

```llvm
define i16 @src_sub_and() {
entry:
  %p = alloca [4 x i16], align 2
  %s = alloca [4 x i16], align 2
  store i16 3, ptr %p, align 2
  %p1 = getelementptr i16, ptr %p, i64 1
  store i16 2, ptr %p1, align 2
  %p2 = getelementptr i16, ptr %p, i64 2
  store i16 3, ptr %p2, align 2
  %p3 = getelementptr i16, ptr %p, i64 3
  store i16 4, ptr %p3, align 2

  %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

  %mul0 = or i16 0, %l0
  %mul1 = shl i16 %l1, 5
  %mul2 = shl i16 %l2, 2
  %mul3 = shl i16 %l3, 3

  %s1 = getelementptr i16, ptr %s, i64 1
  %s2 = getelementptr i16, ptr %s, i64 2
 %s3 = getelementptr i16, ptr %s, i64 3

  store i16 %mul0, ptr %s, align 2
  store i16 %mul1, ptr %s1, align 2
  store i16 %mul2, ptr %s2, align 2
 store i16 %mul3, ptr %s3, align 2

  %res = load i16, ptr %s, align 2
 ret i16 %res
}

define i16 @main(i32 %argc, ptr %argv) {
entry:
  %r = call i16 @sub_and()
  ret i16 %r
}
```

With opt built on this patch, it's transformed into:

```llvm
define i16 @sub_and() {
entry:
  %p = alloca [4 x i16], align 2
  %s = alloca [4 x i16], align 2
  store <4 x i16> <i16 3, i16 2, i16 3, i16 4>, ptr %p, align 2
  %0 = load <4 x i16>, ptr %p, align 2
  %1 = shufflevector <4 x i16> %0, <4 x i16> <i16 0, i16 poison, i16 poison, i16 poison>, <4 x i32> <i32 4, i32 1, i32 2, i32 3>
  %2 = shufflevector <4 x i16> %0, <4 x i16> <i16 poison, i16 5, i16 2, i16 3>, <4 x i32> <i32 0, i32 5, i32 6, i32 7>
  %3 = shl <4 x i16> %1, %2
  store <4 x i16> %3, ptr %s, align 2
  %res = load i16, ptr %s, align 2
  ret i16 %res
}

define i16 @main(i32 %argc, ptr %argv) {
entry:
  %r = call i16 @sub_and()
  ret i16 %r
}
```

Ran lli on them, we got different results:

```sh
$ lli src.ll
$ echo $?
3
$ lli tgt.ll
$ echo $?
0
```

Alive2 proof: https://alive2.llvm.org/ce/z/QQxz8D

> Note: This is a review assisted with a self-built agent. The reproducer was validated manually. Please let me know if anything is wrong.

**Bug Triggering Analysis:**

The provided test case triggers the bug because it includes a scalar lane of the form `or i16 0, %l0`, which is an identity operation and should evaluate to `%l0`. In the Alive2 counterexample, `%l0 = 3`, so the source function returns `3`.

After SLP vectorization, this lane is rewritten into a `shl` lane, but the operands are constructed in the wrong order when the constant is originally on the left-hand side (`Pos == 0`). As a result, the transformation produces `shl i16 0, %l0` instead of the intended `shl i16 %l0, 0`.

This changes the semantics of the program: `or i16 0, 3 = 3`, but `shl i16 0, 3 = 0`. Alive2 confirms this mismatch directly, showing that the source returns `3` while the target returns `0`.

**Fix Weakness Analysis:**

The weakness in the fix is that it computes the replacement identity constant correctly, but does not preserve the correct operand order for non-commutative target opcodes such as `shl` and `sub`.

In particular, when the original constant is in operand 0 (`Pos == 0`), the helper returns `{RHS, LHS}` for most cases. This is incorrect for transformations like:

- `or 0, X` -> should become `shl X, 0`
- `and -1, X` -> should become `shl X, 0`
- `mul 2, X` -> should become `shl X, 1`

but the patched logic instead produces:

- `shl 0, X`
- `shl 0, X`
- `shl 1, X`

These are not equivalent to the original expressions. The Alive2 counterexample exposes exactly this flaw: the patch preserves the identity constant, but places it in the wrong operand position, making the transformation unsound.

cc @bababuck 
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to