[llvm] e2c698c - [InstCombine] Fix miscompilation in `sinkNotIntoLogicalOp` (#142727)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 4 02:48:05 PDT 2025
Author: Yingwei Zheng
Date: 2025-06-04T17:48:01+08:00
New Revision: e2c698c7e836306f1a25c67597ae9e25a1fcc575
URL: https://github.com/llvm/llvm-project/commit/e2c698c7e836306f1a25c67597ae9e25a1fcc575
DIFF: https://github.com/llvm/llvm-project/commit/e2c698c7e836306f1a25c67597ae9e25a1fcc575.diff
LOG: [InstCombine] Fix miscompilation in `sinkNotIntoLogicalOp` (#142727)
Consider the following case:
```
define i1 @src(i8 %x) {
%cmp = icmp slt i8 %x, -1
%not1 = xor i1 %cmp, true
%or = or i1 %cmp, %not1
%not2 = xor i1 %or, true
ret i1 %not2
}
```
`sinkNotIntoLogicalOp(%or)` calls `freelyInvert(%cmp,
/*IgnoredUser=*/%or)` first. However, as `%cmp` is also used by `Op1 =
%not1`, the RHS of `%or` is set to `%cmp.not = xor i1 %cmp, true`. Thus
`Op1` is out of date in the second call to `freelyInvert`. Similarly,
the second call may change `Op0`. According to the analysis above, I
decided to avoid this fold when one of the operands is also a user of
the other.
Closes https://github.com/llvm/llvm-project/issues/142518.
Added:
llvm/test/Transforms/InstCombine/pr142518.ll
Modified:
llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
index 2fb4bfecda8aa..c6c231f81c4ab 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp
@@ -4513,6 +4513,12 @@ bool InstCombinerImpl::sinkNotIntoLogicalOp(Instruction &I) {
if (Op0 == Op1)
return false;
+ // If one of the operands is a user of the other,
+ // freelyInvert->freelyInvertAllUsersOf will change the operands of I, which
+ // may cause miscompilation.
+ if (match(Op0, m_Not(m_Specific(Op1))) || match(Op1, m_Not(m_Specific(Op0))))
+ return false;
+
Instruction::BinaryOps NewOpc =
match(&I, m_LogicalAnd()) ? Instruction::Or : Instruction::And;
bool IsBinaryOp = isa<BinaryOperator>(I);
diff --git a/llvm/test/Transforms/InstCombine/pr142518.ll b/llvm/test/Transforms/InstCombine/pr142518.ll
new file mode 100644
index 0000000000000..980ed24b5eb98
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/pr142518.ll
@@ -0,0 +1,43 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+define i8 @pr142518(ptr %p, i8 %x, i1 %c) "instcombine-no-verify-fixpoint" {
+; CHECK-LABEL: define i8 @pr142518(
+; CHECK-SAME: ptr [[P:%.*]], i8 [[X:%.*]], i1 [[C:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[CMP1:%.*]] = icmp slt i8 [[X]], -1
+; CHECK-NEXT: br label %[[LOOP:.*]]
+; CHECK: [[LOOP]]:
+; CHECK-NEXT: [[NOT1:%.*]] = xor i1 [[CMP1]], true
+; CHECK-NEXT: [[OR:%.*]] = or i1 [[CMP1]], [[NOT1]]
+; CHECK-NEXT: [[CMP:%.*]] = xor i1 [[OR]], true
+; CHECK-NEXT: [[EXT2:%.*]] = zext i1 [[CMP]] to i8
+; CHECK-NEXT: store i8 [[EXT2]], ptr [[P]], align 1
+; CHECK-NEXT: br i1 false, label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: [[NOT3:%.*]] = xor i1 [[OR]], true
+; CHECK-NEXT: [[EXT3:%.*]] = zext i1 [[NOT3]] to i8
+; CHECK-NEXT: ret i8 [[EXT3]]
+;
+entry:
+ %flag = alloca i8, align 1
+ %cmp = icmp slt i8 %x, -1
+ br label %loop
+
+loop:
+ %phi = phi i1 [ %cmp, %entry ], [ %c, %loop ]
+ %not1 = xor i1 %phi, true
+ %or = or i1 %cmp, %not1
+ %not2 = xor i1 %or, true
+ %ext2 = zext i1 %not2 to i8
+ store i8 %ext2, ptr %p, align 1
+ store i8 1, ptr %flag, align 1
+ %flagv = load i8, ptr %flag, align 1
+ %cond = icmp eq i8 %flagv, 0
+ br i1 %cond, label %loop, label %exit
+
+exit:
+ %not3 = xor i1 %or, true
+ %ext3 = zext i1 %not3 to i8
+ ret i8 %ext3
+}
More information about the llvm-commits
mailing list