InstCombine now simplifies binary operations, whose operands involve a select instruction and a cast of the select condition, into a select with folded arguments.
Fixes #63472.
Differential D153963
[InstCombine] Fold binop of select and cast of select condition antoniofrighetto on Jun 28 2023, 4:39 AM. Authored by
Details InstCombine now simplifies binary operations, whose operands involve a select instruction and a cast of the select condition, into a select with folded arguments. Fixes #63472.
Diff Detail
Event TimelineComment Actions Can you add alive2 link for this? I think it also works a lot more binops than just add. If so, can you split to a helper so it can be used elsewhere.
Comment Actions Added proofs, added further tests, reviewed doc-comment, and added support for uitofp/sitofp conversion as well.
Comment Actions Introduced further comments, dropped support at the granularity of floating point casts.
Comment Actions Should be a bit more on track now, thanks for the suggestion! Also polished up a bit, and updated proofs. I wonder if this should be extended to sdiv/udiv and have undef in the false arm (although, admittedly, I don't see much the point). Comment Actions So I'm going to propose something slightly different for accomplishing this patch. I would suggest something along the following lines (pseudo code): if (TrueVal->hasOneUse()) { SmallVector<std::optional<bool>> TrueReplacements; bool DoReplace = false; for (Value *Op : TrueVal->Operands()) { if (Op == CondVal || match(Op, m_ZExt(m_Specific(CondVal)))) { TrueReplacements.emplace_back(true); DoReplace = true; } else if (match(Op, m_SExt(m_Specific(CondVal)))) { TrueReplacements.emplace_back(false); DoReplace = true; } else TrueReplacements.emplace_back(std::nullopt); } if (DoReplace) { SmallVector<Value *> TrueOps; for (unsigned I = 0; I < TrueVal->numOperands(); ++I) { if (TrueReplacements[I]) { TrueOps->emplace_back(*TrueReplacement[I] ? Constant::getOneValue(Ty) : Constant::getAllOnes(Ty)); } else { TrueOps->emplace_back(TrueVal->getOperand(I); } } NewTrue = Builder.Create(TrueVal->getOpcode(), TrueOps); } } For false its always replace with zero. Edit: Comment Actions I guess with TrueVal->Operands() we actually mean the (unique) user of TrueVal? I'm not exactly following how we would benefit from this, where the operands of the select are constants in most of our use cases, here's just an example: define i64 @src(i1 %c) { %sel = select i1 %c, i64 64, i64 1 %ext = zext i1 %c to i64 %add = add i64 %sel, %ext ret i64 %add } Conversely, wouldn't the proposed change be trying to catch the following orthogonal test case? define i64 @src(i1 %c, i64 %a, i64 %b, i64 %d) { %ext = zext i1 %c to i64 %add = add i64 %a, %ext %sub = sub i64 %b, %ext %sel = select i1 %c, i64 %add, i64 %sub ret i64 %sel } Where %add and %sub may be optimized respectively with %1 = add i64 %a, 1 and %2 = sub i64 %b, 0. Comment Actions No I mean the actual operands. The point is the select condition can be constant propagated to instruction on the true/false arm (usually). You can't do this for instructions that are not speculatable (although same is true for binops in your current patch).
Yes, what I'm proposing covers this case, it just doesn't stop at binops. So instead of matching BinOps, just take any speculatable instruction, iterate through its Comment Actions @goldstein.w.n The transform needs to be called from somewhere. Currently it is called for a number of binops. The same transform is indeed possible for non-binop instructions, but I'm not sure it makes sense to plaster it everywhere. The main sensible way of doing that I can see is to extend FoldOpIntoSelect() to handle this. We could extend constantFoldOperationIntoSelectOperand() to determine the constant for zext/sext of the condition, similar to the special case for icmps it currently has. However, this would also require relaxing various checks where we currently only call FoldOpIntoSelect() if we have a constant operand. I guess if there is no compile-time impact to calling FoldOpIntoSelect() for non-constants, then doing that would be a pretty clean and general way to go about it. Comment Actions I see, thats a fair point.
Okay, guess we can leave that as a todo for the future. I may look into when I have the time. @antoniofrighetto for now I think its okay to keep this only for binops, but please add a todo that
Comment Actions @goldstein.w.n, added multiuse select and non-const sides tests (last three tests), proofs updated (https://alive2.llvm.org/ce/z/m_vGWd). Tests are on a dedicated commit. It seems RHS ext & LHS sel operands are canonicalized as such in SimplifyAssociativeOrCommutative for add/mul. When the binop is a subtraction, being non-commutative, the semantic of the operation will change if the operands are swapped. I tried extending the helper to support swapped operands too (LHS ext & RHS sel), yet, the transformation we obtain appears to be correct only when the select has both const sides. With non-const sides, the transformation fails (proof: https://alive2.llvm.org/ce/z/BZU9cR, 2 transformations failed out of 4). I don't believe we should introduce specialization in the helper for const-only subs. Comment Actions Okay thats fine (also other binops).
non-const is fine AFAICT, youre proof is just a little buggy: https://alive2.llvm.org/ce/z/4yWy3R Comment Actions @goldstein.w.n, thanks for pointing that out, updated to support swapped operands too, added tests for it. (EDIT: Updated proofs as well)
Comment Actions Simplified, and opened a patch for tests at: https://reviews.llvm.org/D155510, thanks!
Comment Actions We don't see any test changes as a result of the impl.
That way we can see exact what your change actually changes.. Comment Actions @goldstein.w.n, totally makes sense, my bad, for some reason I thought the diff would show up automatically when chaining reviews by setting the child. We should be on track now. Comment Actions @goldstein.w.n, it seems like the order of the two simplified instructions for the lasts two tests on Windows is reversed compared to the ones it should expect, although I'm not able to reproduce the issue locally. Any idea? Comment Actions Sorry dont really understand what you mean. But personally have no idea about the subtle differences between LLVM on windows vs linux. Comment Actions @goldstein.w.n, looking at the CI failure on Windows, it seems like that for test sub_select_sext_op_swapped_non_const_args it expects sub i6 0, [[ARGF]] to be the first instruction, as opposed to the one on Linux, where that sub appears as second instruction. That may be the reason why FileCheck fails. Comment Actions Oh, I wouldn't worry about that. I don't think the CI for phabricator is really used. AFAIK we only do post commit testing for LLVM (atlthough some sub projects do pre commit testing). Comment Actions Hi @antoniofrighetto, this change seems to be causing the compiler to hit an assertion failure in one of our internal tests. I have put the details in bug #64040, can you take a look? |
I had a lot of trouble following this comment can you make say:
then some format for sext.