[llvm] [InstCombine] Canonicalize complex boolean expressions into ~((y | z) ^ x) via 3-input truth table (PR #149530)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 7 05:58:38 PDT 2025
================
@@ -47,6 +49,202 @@ 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, bool HasOneUse) {
+ uint8_t TruthValue = Table.to_ulong();
+
+ // Skip transformation if expression is already simple (at most 2 levels
+ // deep).
+ if (Root->hasOneUse() && isa<BinaryOperator>(Root)) {
+ if (auto *BO = dyn_cast<BinaryOperator>(Root)) {
+ bool IsSimple = !isa<BinaryOperator>(BO->getOperand(0)) ||
+ !isa<BinaryOperator>(BO->getOperand(1));
+ if (IsSimple)
+ return nullptr;
+ }
+ }
+
+ auto FoldConstant = [&](bool Val) {
+ Constant *Res = Val ? Builder.getTrue() : Builder.getFalse();
+ if (Op0->getType()->isVectorTy())
+ Res = ConstantVector::getSplat(
+ cast<VectorType>(Op0->getType())->getElementCount(), Res);
+ return Res;
+ };
+
+ Value *Result = nullptr;
+ switch (TruthValue) {
+ default:
+ return nullptr;
+
+ case 0x00: // Always FALSE
+ Result = FoldConstant(false);
+ break;
+
+ case 0xFF: // Always TRUE
+ Result = FoldConstant(true);
+ break;
+
+ case 0xE1: // ~((Op1 | Op2) ^ Op0)
+ if (!HasOneUse)
+ return nullptr;
+ {
+ Value *Or = Builder.CreateOr(Op1, Op2);
+ Value *Xor = Builder.CreateXor(Or, Op0);
+ Result = Builder.CreateNot(Xor);
+ }
+ break;
+
+ case 0x60: // Op0 & (Op1 ^ Op2)
+ if (!HasOneUse)
+ return nullptr;
+ {
+ Value *Xor = Builder.CreateXor(Op1, Op2);
+ Result = Builder.CreateAnd(Op0, Xor);
+ }
+ break;
+
+ case 0xD2: // ((Op1 | Op2) ^ Op0) ^ Op1
+ if (!HasOneUse)
+ return nullptr;
+ {
+ 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) {
+ std::set<Value *> Variables;
+ unsigned NodeCount = 0;
+ const unsigned MaxNodes =
+ 50; // To prevent exponential blowup (see bitwise-hang.ll)
+
+ std::function<void(Value *)> Collect = [&](Value *V) {
+ if (++NodeCount > MaxNodes)
+ return;
+
+ Value *NotV;
+ if (match(V, m_Not(m_Value(NotV)))) {
+ Collect(NotV);
+ return;
+ }
+ if (auto *BO = dyn_cast<BinaryOperator>(V)) {
+ Collect(BO->getOperand(0));
+ Collect(BO->getOperand(1));
+ } else if (isa<Argument>(V) || isa<Instruction>(V)) {
+ if (!isa<Constant>(V) && V != Root) {
+ Variables.insert(V);
+ }
+ }
+ };
+
+ Collect(Root);
+
+ // Bail if we hit the node limit
+ if (NodeCount > MaxNodes)
+ return {nullptr, nullptr, nullptr};
+
+ if (Variables.size() == 3) {
+ auto It = Variables.begin();
+ Value *Op0 = *It++;
+ Value *Op1 = *It++;
+ Value *Op2 = *It;
+ return {Op0, Op1, Op2};
+ }
+ return {nullptr, nullptr, nullptr};
+}
+
+/// Evaluate a boolean expression with concrete variable values.
+static std::optional<bool>
+evaluateBooleanExpression(Value *Expr, const std::map<Value *, bool> &Values) {
+ if (auto It = Values.find(Expr); It != Values.end()) {
+ return It->second;
+ }
+ Value *NotExpr;
+ if (match(Expr, m_Not(m_Value(NotExpr)))) {
+ auto Operand = evaluateBooleanExpression(NotExpr, Values);
+ if (Operand)
+ return !*Operand;
+ return std::nullopt;
+ }
+ if (auto *BO = dyn_cast<BinaryOperator>(Expr)) {
+ auto LHS = evaluateBooleanExpression(BO->getOperand(0), Values);
----------------
yafet-a wrote:
Thanks for the suggestion. I have implemented a version of this that removes recursion and sorts them topologically with `Instruction::comesBefore`.
However, although I initially considered using the dominator tree I went with the approach in commit [4c86e54](https://github.com/llvm/llvm-project/pull/149530/commits/4c86e5467b464c5887195fbfc492a48e43b6f675) since afaiu, the `root &I` is always a single instruction in a BB and since `DominatorTree::dominates` falls back to `comesBefore()` for cases in the same BB anyways, using it here would yield the same result with just more overhead.
I am happy to go with the dominator tree approach though if my current understanding of the matter is incorrect
https://github.com/llvm/llvm-project/pull/149530
More information about the llvm-commits
mailing list