-
Notifications
You must be signed in to change notification settings - Fork 15.3k
[InstCombine] Canonicalize complex boolean expressions into ~((y | z) ^ x) via 3-input truth table #149530
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?
[InstCombine] Canonicalize complex boolean expressions into ~((y | z) ^ x) via 3-input truth table #149530
Changes from 13 commits
cf2f9db
3a55b19
02807e3
af90743
d066a85
4c86e54
7a2dc67
6772db5
5405486
cb1e164
650a7ab
18e576e
28d4a0f
1fee55f
a39a3b4
23feb15
1a94bba
d19190d
9296d9b
48bd1ca
464d95e
2a905fb
fc2aac4
d557827
5c40046
7bf8caf
abd628d
64fbafd
ecd9669
2b18e01
b238960
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |||||
| #include "llvm/IR/PatternMatch.h" | ||||||
| #include "llvm/Transforms/InstCombine/InstCombiner.h" | ||||||
| #include "llvm/Transforms/Utils/Local.h" | ||||||
| #include <bitset> | ||||||
|
|
||||||
| using namespace llvm; | ||||||
| using namespace PatternMatch; | ||||||
|
|
@@ -50,6 +51,205 @@ static Value *getFCmpValue(unsigned Code, Value *LHS, Value *RHS, | |||||
| return Builder.CreateFCmpFMF(NewPred, LHS, RHS, FMF); | ||||||
| } | ||||||
|
|
||||||
| /// This is to create optimal 3-variable boolean logic from truth tables. | ||||||
| /// currently it supports the cases pertaining to the issue 97044. More cases | ||||||
| /// can be added based on real-world justification for specific 3 input cases | ||||||
| /// or with reviewer approval all 256 cases can be added (choose the | ||||||
| /// canonicalizations found | ||||||
| /// in x86InstCombine.cpp?) | ||||||
| static Value *createLogicFromTable3Var(const std::bitset<8> &Table, Value *Op0, | ||||||
| Value *Op1, Value *Op2, Value *Root, | ||||||
| IRBuilderBase &Builder) { | ||||||
| uint8_t TruthValue = Table.to_ulong(); | ||||||
| auto FoldConstant = [&](bool Val) { | ||||||
| Type *Ty = Op0->getType(); | ||||||
| return Val ? ConstantInt::getTrue(Ty) : ConstantInt::getFalse(Ty); | ||||||
| }; | ||||||
|
|
||||||
| Value *Result = nullptr; | ||||||
| switch (TruthValue) { | ||||||
| default: | ||||||
| return nullptr; | ||||||
| case 0x00: // Always FALSE | ||||||
yafet-a marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| Result = FoldConstant(false); | ||||||
|
Contributor
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.
Suggested change
I don't think we need a helper function for this... (also for true below). |
||||||
| break; | ||||||
| case 0xFF: // Always TRUE | ||||||
| Result = FoldConstant(true); | ||||||
| break; | ||||||
| case 0xE1: // ~((Op1 | Op2) ^ Op0) | ||||||
| { | ||||||
| Value *Or = Builder.CreateOr(Op1, Op2); | ||||||
| Value *Xor = Builder.CreateXor(Or, Op0); | ||||||
| Result = Builder.CreateNot(Xor); | ||||||
| } break; | ||||||
| case 0x60: // Op0 & (Op1 ^ Op2) | ||||||
| { | ||||||
| Value *Xor = Builder.CreateXor(Op1, Op2); | ||||||
| Result = Builder.CreateAnd(Op0, Xor); | ||||||
| } break; | ||||||
| case 0xD2: // ((Op1 | Op2) ^ Op0) ^ Op1 | ||||||
| { | ||||||
| Value *Or = Builder.CreateOr(Op1, Op2); | ||||||
| Value *Xor1 = Builder.CreateXor(Or, Op0); | ||||||
| Result = Builder.CreateXor(Xor1, Op1); | ||||||
| } break; | ||||||
| } | ||||||
|
|
||||||
| return Result; | ||||||
| } | ||||||
|
|
||||||
| static std::tuple<Value *, Value *, Value *> | ||||||
| extractThreeVariables(Value *Root) { | ||||||
| SmallPtrSet<Value *, 3> Variables; | ||||||
| SmallPtrSet<Value *, 32> Visited; // Prevent hanging during loop unrolling | ||||||
| // (see bitreverse-hang.ll) | ||||||
| SmallVector<Value *> Worklist; | ||||||
| Worklist.push_back(Root); | ||||||
|
|
||||||
| // Track all instructions to ensure they're in the same BB | ||||||
| BasicBlock *FirstBB = nullptr; | ||||||
|
|
||||||
| while (!Worklist.empty()) { | ||||||
| Value *V = Worklist.pop_back_val(); | ||||||
|
|
||||||
yafet-a marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| Value *NotV; | ||||||
| if (match(V, m_Not(m_Value(NotV)))) { | ||||||
| Visited.insert(NotV); | ||||||
| if (V == Root || | ||||||
| V->hasOneUse()) { // Due to lack of cost-based heuristic, only | ||||||
| // traverse if it belongs to this expression tree | ||||||
| Worklist.push_back(NotV); | ||||||
| } | ||||||
| continue; | ||||||
| } | ||||||
| if (auto *BO = dyn_cast<BinaryOperator>(V)) { | ||||||
yafet-a marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| if (!BO->isBitwiseLogicOp()) | ||||||
| return {nullptr, nullptr, nullptr}; | ||||||
|
|
||||||
| // Check BB consistency | ||||||
| if (!FirstBB) | ||||||
| FirstBB = BO->getParent(); | ||||||
| else if (BO->getParent() != FirstBB) | ||||||
| return {nullptr, nullptr, nullptr}; | ||||||
|
|
||||||
| if (V == Root || V->hasOneUse()) { | ||||||
| Visited.insert(BO->getOperand(0)); | ||||||
| Visited.insert(BO->getOperand(1)); | ||||||
| Worklist.push_back(BO->getOperand(0)); | ||||||
yafet-a marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| Worklist.push_back(BO->getOperand(1)); | ||||||
| } | ||||||
| } else if (isa<Argument>(V) || isa<Instruction>(V)) { | ||||||
| if (!isa<Constant>(V) && V != Root) { | ||||||
| Variables.insert(V); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (Variables.size() == 3) { | ||||||
yafet-a marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| // Sort variables by instruction order | ||||||
| SmallVector<Value *, 3> SortedVars(Variables.begin(), Variables.end()); | ||||||
| llvm::sort(SortedVars, [](Value *A, Value *B) { | ||||||
| if (auto *IA = dyn_cast<Instruction>(A)) | ||||||
| if (auto *IB = dyn_cast<Instruction>(B)) | ||||||
| return IA->comesBefore(IB); | ||||||
| return A < B; | ||||||
yafet-a marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| }); | ||||||
yafet-a marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| return {SortedVars[0], SortedVars[1], SortedVars[2]}; | ||||||
| } | ||||||
| return {nullptr, nullptr, nullptr}; | ||||||
| } | ||||||
|
|
||||||
| /// Evaluate a boolean expression with bit-vector inputs for all 8 combinations. | ||||||
| static std::optional<std::bitset<8>> | ||||||
| evaluateBooleanExpression(Value *Expr, Value *Op0, Value *Op1, Value *Op2) { | ||||||
| // Post-order traversal of the expression tree | ||||||
yafet-a marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| SmallVector<Instruction *> Instructions; | ||||||
| SmallVector<Value *> ToVisit; | ||||||
| SmallPtrSet<Instruction *, 8> Seen; | ||||||
|
|
||||||
| ToVisit.push_back(Expr); | ||||||
| while (!ToVisit.empty()) { | ||||||
| Value *V = ToVisit.pop_back_val(); | ||||||
| if (auto *I = dyn_cast<Instruction>(V)) { | ||||||
| if (Seen.insert(I).second) { | ||||||
| Instructions.push_back(I); | ||||||
| for (Value *Op : I->operands()) { | ||||||
| ToVisit.push_back(Op); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
Contributor
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. Do I understand correctly that what this actually does is discarding cases with constant operands, as everything else should have already been handled in the initial loop? If so, can we bail out on constant operands there already? |
||||||
|
|
||||||
| // Sort instructions within the same BB | ||||||
| llvm::sort(Instructions, | ||||||
| [](Instruction *A, Instruction *B) { return A->comesBefore(B); }); | ||||||
|
|
||||||
| // Initialize bit-vector values for the 3 variables | ||||||
| // Op0: 0b11110000 (true for combinations 000,001,010,011) | ||||||
| // Op1: 0b11001100 (true for combinations 000,001,100,101) | ||||||
| // Op2: 0b10101010 (true for combinations 000,010,100,110) | ||||||
| SmallDenseMap<Value *, std::bitset<8>> Computed; | ||||||
| Computed[Op0] = std::bitset<8>(0xF0); // 11110000 | ||||||
| Computed[Op1] = std::bitset<8>(0xCC); // 11001100 | ||||||
| Computed[Op2] = std::bitset<8>(0xAA); // 10101010 | ||||||
|
|
||||||
| for (Instruction *I : Instructions) { | ||||||
| Value *NotV; | ||||||
| if (match(I, m_Not(m_Value(NotV)))) { | ||||||
| if (!Computed.count(NotV)) | ||||||
| return std::nullopt; | ||||||
yafet-a marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| Computed[I] = ~Computed.at(NotV); // Bitwise NOT | ||||||
| } else if (auto *BO = dyn_cast<BinaryOperator>(I)) { | ||||||
| if (!Computed.count(BO->getOperand(0)) || | ||||||
| !Computed.count(BO->getOperand(1))) | ||||||
| return std::nullopt; | ||||||
yafet-a marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
|
|
||||||
| auto &LHS = Computed.at(BO->getOperand(0)); | ||||||
| auto &RHS = Computed.at(BO->getOperand(1)); | ||||||
|
|
||||||
| switch (BO->getOpcode()) { | ||||||
| case Instruction::And: | ||||||
| Computed[I] = LHS & RHS; // Bitwise AND | ||||||
| break; | ||||||
| case Instruction::Or: | ||||||
| Computed[I] = LHS | RHS; // Bitwise OR | ||||||
| break; | ||||||
| case Instruction::Xor: | ||||||
| Computed[I] = LHS ^ RHS; // Bitwise XOR | ||||||
| break; | ||||||
| default: | ||||||
| return std::nullopt; | ||||||
yafet-a marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| } | ||||||
| } | ||||||
|
Contributor
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. What about the case where it's neither Not nor BinaryOperator? I assume it can't happen, in which case we should make that dyn_cast above a cast. |
||||||
| } | ||||||
|
|
||||||
| auto It = Computed.find(Expr); | ||||||
| return It != Computed.end() ? std::optional<std::bitset<8>>(It->second) | ||||||
| : std::nullopt; | ||||||
yafet-a marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||
| } | ||||||
|
|
||||||
| /// Try to canonicalize 3-variable boolean expressions using truth table lookup. | ||||||
| static Value *foldThreeVarBoolExpr(Instruction &Root, | ||||||
yafet-a marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| InstCombiner::BuilderTy &Builder) { | ||||||
|
|
||||||
| auto &BO = cast<BinaryOperator>(Root); | ||||||
|
Contributor
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. Directly accept BinaryOperator as the argument? (It will downcast to Instruction where needed.) |
||||||
| assert(BO.isBitwiseLogicOp() && "Unexpected opcode for boolean expression"); | ||||||
|
|
||||||
| if (!isa<BinaryOperator>(BO.getOperand(0)) || | ||||||
| !isa<BinaryOperator>(BO.getOperand(1))) | ||||||
| return nullptr; | ||||||
|
|
||||||
| auto [Op0, Op1, Op2] = extractThreeVariables(&Root); | ||||||
| if (!Op0 || !Op1 || !Op2) | ||||||
| return nullptr; | ||||||
|
|
||||||
| auto Table = evaluateBooleanExpression(&Root, Op0, Op1, Op2); | ||||||
| if (!Table) | ||||||
| return nullptr; | ||||||
|
|
||||||
| return createLogicFromTable3Var(*Table, Op0, Op1, Op2, &Root, Builder); | ||||||
| } | ||||||
|
|
||||||
| /// Emit a computation of: (V >= Lo && V < Hi) if Inside is true, otherwise | ||||||
| /// (V < Lo || V >= Hi). This method expects that Lo < Hi. IsSigned indicates | ||||||
| /// whether to treat V, Lo, and Hi as signed or not. | ||||||
|
|
@@ -2400,6 +2600,9 @@ Instruction *InstCombinerImpl::visitAnd(BinaryOperator &I) { | |||||
| if (Instruction *Phi = foldBinopWithPhiOperands(I)) | ||||||
| return Phi; | ||||||
|
|
||||||
| if (Value *Canonical = foldThreeVarBoolExpr(I, Builder)) | ||||||
| return replaceInstUsesWith(I, Canonical); | ||||||
|
|
||||||
| // See if we can simplify any instructions used by the instruction whose sole | ||||||
| // purpose is to compute bits we don't care about. | ||||||
| if (SimplifyDemandedInstructionBits(I)) | ||||||
|
|
@@ -3905,6 +4108,9 @@ Instruction *InstCombinerImpl::visitOr(BinaryOperator &I) { | |||||
| if (Instruction *Phi = foldBinopWithPhiOperands(I)) | ||||||
| return Phi; | ||||||
|
|
||||||
| if (Value *Canonical = foldThreeVarBoolExpr(I, Builder)) | ||||||
| return replaceInstUsesWith(I, Canonical); | ||||||
|
|
||||||
| // See if we can simplify any instructions used by the instruction whose sole | ||||||
| // purpose is to compute bits we don't care about. | ||||||
| if (SimplifyDemandedInstructionBits(I)) | ||||||
|
|
@@ -5055,6 +5261,9 @@ Instruction *InstCombinerImpl::visitXor(BinaryOperator &I) { | |||||
| if (Instruction *Phi = foldBinopWithPhiOperands(I)) | ||||||
| return Phi; | ||||||
|
|
||||||
| if (Value *Canonical = foldThreeVarBoolExpr(I, Builder)) | ||||||
| return replaceInstUsesWith(I, Canonical); | ||||||
|
|
||||||
| if (Instruction *NewXor = foldXorToXor(I, Builder)) | ||||||
| return NewXor; | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,154 @@ | ||
| ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py | ||
| ; RUN: opt < %s -passes=instcombine -S | FileCheck %s | ||
| ; Tests for GitHub issue #97044 - Boolean expression canonicalization | ||
| define i32 @test0_4way_or(i32 %x, i32 %y, i32 %z) { | ||
yafet-a marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ; CHECK-LABEL: @test0_4way_or( | ||
| ; CHECK-NEXT: [[TMP1:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]] | ||
| ; CHECK-NEXT: [[TMP2:%.*]] = xor i32 [[TMP1]], [[X:%.*]] | ||
| ; CHECK-NEXT: [[OR13:%.*]] = xor i32 [[TMP2]], -1 | ||
| ; CHECK-NEXT: ret i32 [[OR13]] | ||
| ; | ||
| %not = xor i32 %z, -1 | ||
| %and = and i32 %y, %not | ||
| %and1 = and i32 %and, %x | ||
| %not2 = xor i32 %y, -1 | ||
| %and3 = and i32 %x, %not2 | ||
| %and4 = and i32 %and3, %z | ||
| %or = or i32 %and1, %and4 | ||
| %not5 = xor i32 %x, -1 | ||
| %not6 = xor i32 %y, -1 | ||
| %and7 = and i32 %not5, %not6 | ||
| %not8 = xor i32 %z, -1 | ||
| %and9 = and i32 %and7, %not8 | ||
| %or10 = or i32 %or, %and9 | ||
| %and11 = and i32 %x, %y | ||
| %and12 = and i32 %and11, %z | ||
| %or13 = or i32 %or10, %and12 | ||
| ret i32 %or13 | ||
| } | ||
| define i32 @test1_xor_pattern(i32 %x, i32 %y, i32 %z) { | ||
| ; CHECK-LABEL: @test1_xor_pattern( | ||
| ; CHECK-NEXT: [[TMP1:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]] | ||
| ; CHECK-NEXT: [[TMP2:%.*]] = xor i32 [[TMP1]], [[X:%.*]] | ||
| ; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[TMP2]], -1 | ||
| ; CHECK-NEXT: ret i32 [[XOR]] | ||
| ; | ||
| %not = xor i32 %z, -1 | ||
| %and = and i32 %x, %y | ||
| %not1 = xor i32 %x, -1 | ||
| %not2 = xor i32 %y, -1 | ||
| %and3 = and i32 %not1, %not2 | ||
| %or = or i32 %and, %and3 | ||
| %and4 = and i32 %not, %or | ||
| %and5 = and i32 %x, %y | ||
| %and6 = and i32 %x, %not2 | ||
| %or7 = or i32 %and5, %and6 | ||
| %and8 = and i32 %z, %or7 | ||
| %xor = xor i32 %and4, %and8 | ||
| ret i32 %xor | ||
| } | ||
| define i32 @test2_nested_xor(i32 %x, i32 %y, i32 %z) { | ||
| ; CHECK-LABEL: @test2_nested_xor( | ||
| ; CHECK-NEXT: [[TMP1:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]] | ||
| ; CHECK-NEXT: [[TMP2:%.*]] = xor i32 [[TMP1]], [[X:%.*]] | ||
| ; CHECK-NEXT: [[TMP3:%.*]] = xor i32 [[TMP2]], [[Y]] | ||
| ; CHECK-NEXT: ret i32 [[TMP3]] | ||
| ; | ||
| %and = and i32 %x, %y | ||
| %not = xor i32 %x, -1 | ||
| %not1 = xor i32 %y, -1 | ||
| %and2 = and i32 %not, %not1 | ||
| %or = or i32 %and, %and2 | ||
| %and3 = and i32 %x, %y | ||
| %not4 = xor i32 %y, -1 | ||
| %and5 = and i32 %x, %not4 | ||
| %or6 = or i32 %and3, %and5 | ||
| %xor = xor i32 %or, %or6 | ||
| %not7 = xor i32 %y, -1 | ||
| %and8 = and i32 %z, %not7 | ||
| %and9 = and i32 %xor, %and8 | ||
| %xor10 = xor i32 %or, %and9 | ||
| %xor11 = xor i32 %xor10, %y | ||
| %xor12 = xor i32 %xor11, -1 | ||
| ret i32 %xor12 | ||
| } | ||
| define i32 @test3_already_optimal(i32 %x, i32 %y, i32 %z) { | ||
| ; CHECK-LABEL: @test3_already_optimal( | ||
| ; CHECK-NEXT: [[OR:%.*]] = or i32 [[Y:%.*]], [[Z:%.*]] | ||
| ; CHECK-NEXT: [[XOR:%.*]] = xor i32 [[OR]], [[X:%.*]] | ||
| ; CHECK-NEXT: [[NOT:%.*]] = xor i32 [[XOR]], -1 | ||
| ; CHECK-NEXT: ret i32 [[NOT]] | ||
| ; | ||
| %or = or i32 %y, %z | ||
| %xor = xor i32 %or, %x | ||
| %not = xor i32 %xor, -1 | ||
| ret i32 %not | ||
| } | ||
| ; Negative Tests | ||
| ; Test with non-bitwise operation (should not transform - add/sub not supported) | ||
| define i32 @negative_non_bitwise_add(i32 %x, i32 %y, i32 %z) { | ||
| ; CHECK-LABEL: @negative_non_bitwise_add( | ||
| ; CHECK-NEXT: [[ADD1:%.*]] = add i32 [[X:%.*]], [[Y:%.*]] | ||
| ; CHECK-NEXT: [[ADD2:%.*]] = add i32 [[ADD1]], [[Z:%.*]] | ||
| ; CHECK-NEXT: ret i32 [[ADD2]] | ||
| ; | ||
| %add1 = add i32 %x, %y | ||
| %add2 = add i32 %add1, %z | ||
| ret i32 %add2 | ||
| } | ||
| ; Test with only 2 variables (should not transform - needs exactly 3 variables) | ||
| define i32 @negative_two_variables(i32 %x, i32 %y) { | ||
| ; CHECK-LABEL: @negative_two_variables( | ||
| ; CHECK-NEXT: [[AND:%.*]] = and i32 [[X:%.*]], [[Y:%.*]] | ||
| ; CHECK-NEXT: [[NOT:%.*]] = xor i32 [[AND]], -1 | ||
| ; CHECK-NEXT: ret i32 [[NOT]] | ||
| ; | ||
| %and = and i32 %x, %y | ||
| %not = xor i32 %and, -1 | ||
| ret i32 %not | ||
| } | ||
| ; Test with 4 variables (should not transform - needs exactly 3 variables) | ||
| define i32 @negative_four_variables(i32 %x, i32 %y, i32 %z, i32 %w) { | ||
| ; CHECK-LABEL: @negative_four_variables( | ||
| ; CHECK-NEXT: [[AND1:%.*]] = and i32 [[X:%.*]], [[Y:%.*]] | ||
| ; CHECK-NEXT: [[AND2:%.*]] = and i32 [[Z:%.*]], [[W:%.*]] | ||
| ; CHECK-NEXT: [[OR:%.*]] = or i32 [[AND1]], [[AND2]] | ||
| ; CHECK-NEXT: ret i32 [[OR]] | ||
| ; | ||
| %and1 = and i32 %x, %y | ||
| %and2 = and i32 %z, %w | ||
| %or = or i32 %and1, %and2 | ||
| ret i32 %or | ||
| } | ||
| ; Test with simple 2-level expression (should not transform - not complex enough) | ||
| define i32 @negative_simple_expression(i32 %x, i32 %y, i32 %z) { | ||
| ; CHECK-LABEL: @negative_simple_expression( | ||
| ; CHECK-NEXT: [[AND:%.*]] = and i32 [[X:%.*]], [[Y:%.*]] | ||
| ; CHECK-NEXT: [[OR:%.*]] = or i32 [[AND]], [[Z:%.*]] | ||
| ; CHECK-NEXT: ret i32 [[OR]] | ||
| ; | ||
| %and = and i32 %x, %y | ||
| %or = or i32 %and, %z | ||
| ret i32 %or | ||
| } | ||
| ; Test with instructions in different basic blocks (should not transform) | ||
| define i32 @negative_different_basic_blocks(i32 %x, i32 %y, i32 %z, i1 %cond) { | ||
| ; CHECK-LABEL: @negative_different_basic_blocks( | ||
| ; CHECK-NEXT: entry: | ||
| ; CHECK-NEXT: [[AND1:%.*]] = and i32 [[X:%.*]], [[Y:%.*]] | ||
| ; CHECK-NEXT: br i1 [[COND:%.*]], label [[IF_TRUE:%.*]], label [[IF_FALSE:%.*]] | ||
| ; CHECK: if.true: | ||
| ; CHECK-NEXT: [[AND2:%.*]] = and i32 [[AND1]], [[Z:%.*]] | ||
| ; CHECK-NEXT: ret i32 [[AND2]] | ||
| ; CHECK: if.false: | ||
| ; CHECK-NEXT: ret i32 [[AND1]] | ||
| ; | ||
| entry: | ||
| %and1 = and i32 %x, %y | ||
| br i1 %cond, label %if.true, label %if.false | ||
| if.true: | ||
| %and2 = and i32 %and1, %z | ||
| ret i32 %and2 | ||
| if.false: | ||
| ret i32 %and1 | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.