-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[InstCombine] Fold (X == 0 ? Y : 0) | X to X == 0 ? Y : X #138373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1714,6 +1714,35 @@ Instruction *InstCombinerImpl::FoldOpIntoSelect(Instruction &Op, SelectInst *SI, | |||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// Optimize patterns where an OR operation combines a select-based zero check | ||||||||||||||||||||||||||||||||||||||||||||
// with its condition value. This handles both scalar and vector types. | ||||||||||||||||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||||||||||||||||
// Given: | ||||||||||||||||||||||||||||||||||||||||||||
// (X == 0 ? Y : 0) | X --> X == 0 ? Y : X | ||||||||||||||||||||||||||||||||||||||||||||
// X | (X == 0 ? Y : 0) --> X == 0 ? Y : X | ||||||||||||||||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||||||||||||||||
// Also handles cases where X might be wrapped in zero/sign extensions. | ||||||||||||||||||||||||||||||||||||||||||||
if (Op.getOpcode() == Instruction::Or) { | ||||||||||||||||||||||||||||||||||||||||||||
// Check both operand orders to handle commutative OR | ||||||||||||||||||||||||||||||||||||||||||||
// The other operand in the OR operation (potentially X or extended X) | ||||||||||||||||||||||||||||||||||||||||||||
Value *Other = Op.getOperand(0) == SI ? Op.getOperand(1) : Op.getOperand(0); | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
CmpPredicate Pred; | ||||||||||||||||||||||||||||||||||||||||||||
Value *X, *Y; | ||||||||||||||||||||||||||||||||||||||||||||
// Attempt to match the select pattern: | ||||||||||||||||||||||||||||||||||||||||||||
// select(icmp eq X, 0), Y, 0 | ||||||||||||||||||||||||||||||||||||||||||||
// Where X might be: | ||||||||||||||||||||||||||||||||||||||||||||
// - Original value | ||||||||||||||||||||||||||||||||||||||||||||
// - Zero extended value (zext) | ||||||||||||||||||||||||||||||||||||||||||||
// - Sign extended value (sext) | ||||||||||||||||||||||||||||||||||||||||||||
if (match(SI, m_Select(m_ICmp(Pred, m_Value(X), m_Zero()), m_Value(Y), | ||||||||||||||||||||||||||||||||||||||||||||
m_Zero())) && | ||||||||||||||||||||||||||||||||||||||||||||
Pred == ICmpInst::ICMP_EQ && | ||||||||||||||||||||||||||||||||||||||||||||
match(Other, m_ZExtOrSExtOrSelf(m_Specific(X)))) { | ||||||||||||||||||||||||||||||||||||||||||||
return SelectInst::Create(SI->getCondition(), Y, Other); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we ignore the sext/zext case, is this additional code even needed? The reason I suggested FoldOpIntoSelect is that the code in llvm-project/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp Lines 1658 to 1678 in 5f32b8f
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// Make sure that one of the select arms folds successfully. | ||||||||||||||||||||||||||||||||||||||||||||
Value *NewTV = simplifyOperationIntoSelectOperand(Op, SI, /*IsTrueArm=*/true); | ||||||||||||||||||||||||||||||||||||||||||||
Value *NewFV = | ||||||||||||||||||||||||||||||||||||||||||||
|
nikic marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py | ||
; RUN: opt < %s -passes=instcombine -S | FileCheck %s | ||
|
||
; Basic functional test | ||
define i32 @basic(i32 %a, i32 %b) { | ||
; CHECK-LABEL: define i32 @basic( | ||
; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) { | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[A]], 0 | ||
; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP]], i32 [[B]], i32 [[A]] | ||
; CHECK-NEXT: ret i32 [[RES]] | ||
; | ||
%cmp = icmp eq i32 %a, 0 | ||
%sel = select i1 %cmp, i32 %b, i32 0 | ||
%or = or i32 %sel, %a | ||
ret i32 %or | ||
} | ||
|
||
; Operand order swap test | ||
define i32 @swap_operand_order(i32 %x, i32 %y) { | ||
; CHECK-LABEL: define i32 @swap_operand_order( | ||
; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) { | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[X]], 0 | ||
; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP]], i32 [[Y]], i32 [[X]] | ||
; CHECK-NEXT: ret i32 [[RES]] | ||
; | ||
%cmp = icmp eq i32 %x, 0 | ||
%sel = select i1 %cmp, i32 %y, i32 0 | ||
%or = or i32 %x, %sel | ||
ret i32 %or | ||
} | ||
|
||
; Negative test: Non-zero false value in select | ||
define i32 @negative_non_zero_false_val(i32 %a, i32 %b) { | ||
; CHECK-LABEL: define i32 @negative_non_zero_false_val( | ||
; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) { | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[A]], 0 | ||
; CHECK-NEXT: [[TMP1:%.*]] = or i32 [[A]], 1 | ||
; CHECK-NEXT: [[OR:%.*]] = select i1 [[CMP]], i32 [[B]], i32 [[TMP1]] | ||
; CHECK-NEXT: ret i32 [[OR]] | ||
; | ||
%cmp = icmp eq i32 %a, 0 | ||
%sel = select i1 %cmp, i32 %b, i32 1 | ||
%or = or i32 %sel, %a | ||
ret i32 %or | ||
} | ||
|
||
; Negative test: Incorrect comparison predicate (NE) | ||
define i32 @negative_wrong_predicate(i32 %a, i32 %b) { | ||
; CHECK-LABEL: define i32 @negative_wrong_predicate( | ||
; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) { | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[A]], 0 | ||
; CHECK-NEXT: [[TMP1:%.*]] = or i32 [[B]], [[A]] | ||
; CHECK-NEXT: [[OR:%.*]] = select i1 [[CMP]], i32 0, i32 [[TMP1]] | ||
; CHECK-NEXT: ret i32 [[OR]] | ||
; | ||
%cmp = icmp ne i32 %a, 0 | ||
%sel = select i1 %cmp, i32 %b, i32 0 | ||
%or = or i32 %sel, %a | ||
ret i32 %or | ||
} | ||
|
||
; Comparison direction swap test (0 == X) | ||
define i32 @cmp_swapped(i32 %x, i32 %y) { | ||
; CHECK-LABEL: define i32 @cmp_swapped( | ||
; CHECK-SAME: i32 [[X:%.*]], i32 [[Y:%.*]]) { | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[X]], 0 | ||
; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP]], i32 [[Y]], i32 [[X]] | ||
; CHECK-NEXT: ret i32 [[RES]] | ||
; | ||
%cmp = icmp eq i32 0, %x | ||
%sel = select i1 %cmp, i32 %y, i32 0 | ||
%or = or i32 %x, %sel | ||
ret i32 %or | ||
} | ||
|
||
; Complex expression test | ||
define i32 @complex_expression(i32 %a, i32 %b) { | ||
; CHECK-LABEL: define i32 @complex_expression( | ||
; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) { | ||
; CHECK-NEXT: [[X:%.*]] = add i32 [[A]], 1 | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[X]], 0 | ||
; CHECK-NEXT: [[RES:%.*]] = select i1 [[CMP]], i32 [[B]], i32 [[X]] | ||
; CHECK-NEXT: ret i32 [[RES]] | ||
; | ||
%x = add i32 %a, 1 | ||
%cmp = icmp eq i32 %x, 0 | ||
%sel = select i1 %cmp, i32 %b, i32 0 | ||
%or = or i32 %sel, %x | ||
ret i32 %or | ||
} | ||
|
||
; zext test | ||
define i32 @zext_cond(i8 %a, i32 %b) { | ||
; CHECK-LABEL: define i32 @zext_cond( | ||
; CHECK-SAME: i8 [[A:%.*]], i32 [[B:%.*]]) { | ||
; CHECK-NEXT: [[Z:%.*]] = zext i8 [[A]] to i32 | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[A]], 0 | ||
; CHECK-NEXT: [[OR:%.*]] = select i1 [[CMP]], i32 [[B]], i32 [[Z]] | ||
; CHECK-NEXT: ret i32 [[OR]] | ||
; | ||
%z = zext i8 %a to i32 | ||
%cmp = icmp eq i8 %a, 0 | ||
%sel = select i1 %cmp, i32 %b, i32 0 | ||
%or = or i32 %sel, %z | ||
ret i32 %or | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this actually tests your zext handling. Don't you need something like this?
That is, the icmp needs to be on the non-zext value. |
||
|
||
; sext test | ||
define i32 @sext_cond(i8 %a, i32 %b) { | ||
; CHECK-LABEL: define i32 @sext_cond( | ||
; CHECK-SAME: i8 [[A:%.*]], i32 [[B:%.*]]) { | ||
; CHECK-NEXT: [[S:%.*]] = sext i8 [[A]] to i32 | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i8 [[A]], 0 | ||
; CHECK-NEXT: [[OR:%.*]] = select i1 [[CMP]], i32 [[B]], i32 [[S]] | ||
; CHECK-NEXT: ret i32 [[OR]] | ||
; | ||
%s = sext i8 %a to i32 | ||
%cmp = icmp eq i8 %a, 0 | ||
%sel = select i1 %cmp, i32 %b, i32 0 | ||
%or = or i32 %sel, %s | ||
ret i32 %or | ||
} | ||
|
||
; Vector type test | ||
define <2 x i32> @vector_type(<2 x i32> %a, <2 x i32> %b) { | ||
; CHECK-LABEL: define <2 x i32> @vector_type( | ||
; CHECK-SAME: <2 x i32> [[A:%.*]], <2 x i32> [[B:%.*]]) { | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq <2 x i32> [[A]], zeroinitializer | ||
; CHECK-NEXT: [[RES:%.*]] = select <2 x i1> [[CMP]], <2 x i32> [[B]], <2 x i32> [[A]] | ||
; CHECK-NEXT: ret <2 x i32> [[RES]] | ||
; | ||
%cmp = icmp eq <2 x i32> %a, zeroinitializer | ||
%sel = select <2 x i1> %cmp, <2 x i32> %b, <2 x i32> zeroinitializer | ||
%or = or <2 x i32> %sel, %a | ||
ret <2 x i32> %or | ||
} | ||
|
||
; Pointer type test (should not trigger optimization) | ||
define ptr @pointer_type(ptr %p, ptr %q) { | ||
; CHECK-LABEL: define ptr @pointer_type( | ||
; CHECK-SAME: ptr [[P:%.*]], ptr [[Q:%.*]]) { | ||
; CHECK-NEXT: [[A:%.*]] = ptrtoint ptr [[P]] to i64 | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq ptr [[P]], null | ||
; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], ptr [[Q]], ptr null | ||
; CHECK-NEXT: [[SEL_INT:%.*]] = ptrtoint ptr [[SEL]] to i64 | ||
; CHECK-NEXT: [[OR:%.*]] = or i64 [[A]], [[SEL_INT]] | ||
; CHECK-NEXT: [[RET:%.*]] = inttoptr i64 [[OR]] to ptr | ||
; CHECK-NEXT: ret ptr [[RET]] | ||
; | ||
%a = ptrtoint ptr %p to i64 | ||
%cmp = icmp eq i64 %a, 0 | ||
%sel = select i1 %cmp, ptr %q, ptr null | ||
%sel_int = ptrtoint ptr %sel to i64 | ||
%or_val = or i64 %a, %sel_int | ||
%ret = inttoptr i64 %or_val to ptr | ||
ret ptr %ret | ||
} | ||
|
||
; Multi-use test (should not trigger optimization) | ||
define i32 @multi_use_test(i32 %x, i32 %m) { | ||
; CHECK-LABEL: define i32 @multi_use_test( | ||
; CHECK-SAME: i32 [[X:%.*]], i32 [[M:%.*]]) { | ||
; CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[X]], 0 | ||
; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], i32 [[M]], i32 0 | ||
; CHECK-NEXT: [[OR:%.*]] = or i32 [[SEL]], [[X]] | ||
; CHECK-NEXT: [[ADD:%.*]] = add i32 [[SEL]], [[X]] | ||
; CHECK-NEXT: [[O2:%.*]] = sub i32 [[OR]], [[ADD]] | ||
; CHECK-NEXT: ret i32 [[O2]] | ||
; | ||
%cmp = icmp eq i32 %x, 0 | ||
%sel = select i1 %cmp, i32 %m, i32 0 | ||
%or = or i32 %sel, %x | ||
%add = add i32 %sel, %x | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might not be the best test, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it like this? define i32 @multi_use_test(i32 %x, i32 %m) {
%cmp = icmp eq i32 %x, 0
%sel = select i1 %cmp, i32 %m, i32 0
%or1 = or i32 %sel, %x
%or2 = or i32 %sel, %x
%res = add i32 %or1, %or2
ret i32 %res
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I seem to understand your point, so would it be appropriate for me to modify it like this? define i32 @multi_use_test(i32 %x, i32 %m) {
%cmp = icmp eq i32 %x, 0
%sel = select i1 %cmp, i32 %m, i32 0
%or = or i32 %sel, %x
%mul = mul i32 %sel, %x
%res = add i32 %or, %mul
ret i32 %res
} I think the mul should not be CSEd. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the most reliable way of adding an extra use is to pass it to a call to some There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, I've seen this written in other IR tests.
It's my first time to write such a test and I'm a novice regarding IR. |
||
%res = sub i32 %or, %add | ||
ret i32 %res | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are already calling FoldOpIntoSelect via foldBinOpIntoSelectOrPhi. But that function currently only calls FoldOpIntoSelect if the RHS is a constant.
I think to enable this fold be basically just need to lift that restriction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we ignore the sext/zext case and lift constant restriction, then do the test I get the following result:
I think the failed may be well handled , but the timed out is tricky, and there should seem to be infinite loops when folding. I don't know if I'm right about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd try to lift the constant restrict only for
or
to start with (add an extra parameter AllowNonConstant or something), so we don't have to deal with too many things at once.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Followed your tips and made changes accordingly:
Changed
foldBinOpIntoSelectOrPhi
:Changed
visitOr
:Then do the test, there is still a time out as last time.
So, I added some rough debug messages to some functions:
And I took the example from
vec_sext.ll
that caused time out:Test result:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the reverse transformation is done by
foldSelectIntoOp()
.See the comment near
getSelectFoldableOperands()
(initially introduced by 56e4d3d back in 2004).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting! A way to prevent the infinite loop would be to allow the non-constant case only in case both select operands fold (rather than only one).