diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp --- a/llvm/lib/Analysis/InstructionSimplify.cpp +++ b/llvm/lib/Analysis/InstructionSimplify.cpp @@ -228,6 +228,44 @@ return false; } +// CanFoldToConstantUsingOneOperand returns a constant after folding "L Op R" +// after using only one of L or R, but not both. +// Formally, it returns a constant X s.t. "L Op Y --> X" or "Y Op R --> X" holds +// for any Y (E --> V means E can be simplifed to V). +// +// ex) Given "0 & R", X is 0. +// ex2) Given "L | -1", X is -1. +// ex3) Given "0 + 0", there is no such X. +// +// If L or R is undef, whenever undef in L is chosen to be a specific +// constant, it should be assumed that R's value has changed (and vice versa). +// +// ex4) Given "undef + 0", X cannot be 0: if L = undef is chosen to be 0, +// R cannot be 0 anymore. +// ex5) Given "undef + undef", X cannot be 0: If L = undef is chosen to be 0, +// R may have changed into 1, causing X to be 1. +// ex6) Given "undef & R", X is 0. If L = undef is chosen to be 0, the result +// is 0 regardless of R. +static Constant *FoldUsingOneOperand(Instruction::BinaryOps Op, Value *L, + Value *R) { + if (auto C = ConstantExpr::getBinOpAbsorber(Op, L->getType())) { + if (L == C || R == C || isa(L) || isa(R)) + return C; + } + switch (Op) { + case Instruction::Shl: + case Instruction::AShr: + case Instruction::LShr: + // If L is zero, we can always fold the result into zero. + // (If shiftamt >= LHS's bitwidth, the result is poison, but it can be + // folded into zero) + return match(L, m_Zero()) ? Constant::getNullValue(L->getType()) : nullptr; + default: + break; + } + return nullptr; +} + /// Try to simplify a binary operator of form "V op OtherOp" where V is /// "(B0 opex B1)" by distributing 'op' across 'opex' as /// "(B0 op OtherOp) opex (B1 op OtherOp)". @@ -245,6 +283,21 @@ if (!R) return nullptr; + // If OtherOp contains undef in its subexpression, the undef should be folded + // into the same constant by the two previous SimplifyBinOp calls. + // Implementing this 'entanglement' is non-trivial, however. + // So, use a simpler approach instead: + // (1) If "L Op' R" can be folded into a constant by using only L + // or R, fold it. This is valid because undef folds from either + // "(B0 op OtherOp)" or "(B1 op OtherOp)" are considered, but not both. + if (Constant *Res = FoldUsingOneOperand(OpcodeToExpand, L, R)) { + ++NumExpand; + return Res; + } + // (2) If OtherOp may be undef or poison, give up. + if (!isGuaranteedNotToBeUndefOrPoison(OtherOp, Q.CxtI, Q.DT)) + return nullptr; + // Does the expanded pair of binops simplify to the existing binop? if ((L == B0 && R == B1) || (Instruction::isCommutative(OpcodeToExpand) && L == B1 && R == B0)) { diff --git a/llvm/test/Transforms/InstCombine/and-or-icmps.ll b/llvm/test/Transforms/InstCombine/and-or-icmps.ll --- a/llvm/test/Transforms/InstCombine/and-or-icmps.ll +++ b/llvm/test/Transforms/InstCombine/and-or-icmps.ll @@ -218,12 +218,14 @@ ; CHECK-NEXT: [[C7:%.*]] = icmp slt i16 [[L7]], 0 ; CHECK-NEXT: [[B15:%.*]] = xor i1 [[C7]], [[C10]] ; CHECK-NEXT: [[B19:%.*]] = xor i1 [[C11]], [[B15]] -; CHECK-NEXT: [[TMP2:%.*]] = and i1 [[C10]], [[C5]] -; CHECK-NEXT: [[C3:%.*]] = and i1 [[TMP2]], [[B19]] -; CHECK-NEXT: [[TMP3:%.*]] = xor i1 [[C10]], true -; CHECK-NEXT: [[C18:%.*]] = or i1 [[C7]], [[TMP3]] -; CHECK-NEXT: [[TMP4:%.*]] = sext i1 [[C3]] to i64 -; CHECK-NEXT: [[G26:%.*]] = getelementptr i1, i1* null, i64 [[TMP4]] +; CHECK-NEXT: [[TMP2:%.*]] = xor i1 [[C11]], true +; CHECK-NEXT: [[C6:%.*]] = or i1 [[B19]], [[TMP2]] +; CHECK-NEXT: [[TMP3:%.*]] = and i1 [[C10]], [[C5]] +; CHECK-NEXT: [[C3:%.*]] = and i1 [[TMP3]], [[C6]] +; CHECK-NEXT: [[TMP4:%.*]] = xor i1 [[C10]], true +; CHECK-NEXT: [[C18:%.*]] = or i1 [[C7]], [[TMP4]] +; CHECK-NEXT: [[TMP5:%.*]] = sext i1 [[C3]] to i64 +; CHECK-NEXT: [[G26:%.*]] = getelementptr i1, i1* null, i64 [[TMP5]] ; CHECK-NEXT: store i16 [[L7]], i16* undef, align 2 ; CHECK-NEXT: store i1 [[C18]], i1* undef, align 1 ; CHECK-NEXT: store i1* [[G26]], i1** undef, align 8 diff --git a/llvm/test/Transforms/InstSimplify/distribute.ll b/llvm/test/Transforms/InstSimplify/distribute.ll --- a/llvm/test/Transforms/InstSimplify/distribute.ll +++ b/llvm/test/Transforms/InstSimplify/distribute.ll @@ -1,6 +1,12 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py ; RUN: opt < %s -instsimplify -S | FileCheck %s +; A transformation (x op y) op' z -> (x op' z) op (y op' z) is incorrect in +; general. After z is copied, each instance of undefs in z may be instantiated +; into different values. +; The tranformation is correct if z is never undef. +; In @f, since x is never undef, %result can be optimized to x. +; ; %result = x & (1 ^ sel) = (x & 1) ^ (x & sel) ; = x ^ (select cond, false, false) ; = x ^ false = x @@ -20,7 +26,13 @@ define i1 @f_dontfold(i1 %cond, i1 %x, i1 %y, i1 %z) { ; CHECK-LABEL: @f_dontfold( -; CHECK-NEXT: ret i1 [[X:%.*]] +; CHECK-NEXT: [[NOTX:%.*]] = xor i1 [[X:%.*]], true +; CHECK-NEXT: [[LHS:%.*]] = and i1 [[NOTX]], [[Y:%.*]] +; CHECK-NEXT: [[RHS:%.*]] = and i1 [[NOTX]], [[Z:%.*]] +; CHECK-NEXT: [[SEL:%.*]] = select i1 [[COND:%.*]], i1 [[LHS]], i1 [[RHS]] +; CHECK-NEXT: [[OP1:%.*]] = xor i1 true, [[SEL]] +; CHECK-NEXT: [[RESULT:%.*]] = and i1 [[X]], [[OP1]] +; CHECK-NEXT: ret i1 [[RESULT]] ; %notx = xor i1 %x, 1 %lhs = and i1 %notx, %y