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,34 @@ return false; } +/// This function returns a constant after folding "L Op R" 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; + } + // TODO: If Op is shift and L is zero or undef, we can return zero. + 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 +273,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,32 +1,39 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py ; RUN: opt < %s -instsimplify -S | FileCheck %s -; %result = x & (1 ^ sel) = (x & 1) ^ (x & sel) -; = x ^ (select cond, false, false) -; = x ^ false = x +; 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. ; -define i1 @f(i1 %cond, i1 noundef %x, i1 %y, i1 %z) { -; CHECK-LABEL: @f( -; CHECK-NEXT: ret i1 [[X:%.*]] +; This test file checks whether instsimplify conservatively stops doing the +; transformation unless it is known to be safe. ; - %notx = xor i1 %x, 1 - %lhs = and i1 %notx, %y - %rhs = and i1 %notx, %z - %sel = select i1 %cond, i1 %lhs, i1 %rhs - %op1 = xor i1 1, %sel - %result = and i1 %x, %op1 - ret i1 %result +; %result = x & ((~x & y) ^ 1) = (x & (~x & y)) ^ (x & 1) +; = 0 ^ x = x +; +define i4 @expand_and_xor(i4 noundef %x, i4 %y) { +; CHECK-LABEL: @expand_and_xor( +; CHECK-NEXT: ret i4 [[X:%.*]] +; + %notx = xor i4 %x, -1 + %a = and i4 %notx, %y + %nota = xor i4 %a, -1 + %result = and i4 %x, %nota + ret i4 %result } -define i1 @f_dontfold(i1 %cond, i1 %x, i1 %y, i1 %z) { -; CHECK-LABEL: @f_dontfold( -; CHECK-NEXT: ret i1 [[X:%.*]] +; TODO: This can be simplified to %x as well. +define i4 @expand_and_xor2(i4 %x, i4 %y) { +; CHECK-LABEL: @expand_and_xor2( +; CHECK-NEXT: [[NOTX:%.*]] = xor i4 [[X:%.*]], -1 +; CHECK-NEXT: [[A:%.*]] = and i4 [[NOTX]], [[Y:%.*]] +; CHECK-NEXT: [[NOTA:%.*]] = xor i4 [[A]], -1 +; CHECK-NEXT: [[RESULT:%.*]] = and i4 [[X]], [[NOTA]] +; CHECK-NEXT: ret i4 [[RESULT]] ; - %notx = xor i1 %x, 1 - %lhs = and i1 %notx, %y - %rhs = and i1 %notx, %z - %sel = select i1 %cond, i1 %lhs, i1 %rhs - %op1 = xor i1 1, %sel - %result = and i1 %x, %op1 - ret i1 %result + %notx = xor i4 %x, -1 + %a = and i4 %notx, %y + %nota = xor i4 %a, -1 + %result = and i4 %x, %nota + ret i4 %result }