-
Notifications
You must be signed in to change notification settings - Fork 15k
[InstSimplify] Optimize maximumnum and minimumnum #139581
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
Merged
LewisCrawford
merged 23 commits into
llvm:main
from
LewisCrawford:instsimplify_maximumnum
Oct 7, 2025
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
b0a057b
[InstSimplify] Optimize maximumnum and minimumnum
LewisCrawford 9c05591
Add more vector tests + cleanup
LewisCrawford 35b7d08
Add vector tests for all inf/max tests
LewisCrawford aa652bf
Avoid optimization for maxnum(x, <sNaN, qNaN>)
LewisCrawford 65c4e84
Update failing AMDGPU tests
LewisCrawford a80f4f0
Tidy up if/else chain
LewisCrawford 4e7a52e
Rewrite fmax/fmin InstSimplify elementwise
LewisCrawford 97fa626
Remove unused pattern match functions
LewisCrawford 7b1b750
Fix AMDGPU tests
LewisCrawford 75cff98
Avoid elementwise testing for failed splats
LewisCrawford 23c002e
Fix comment consistency
LewisCrawford 39c6a7f
Merge branch 'main' into instsimplify_maximumnum
LewisCrawford 12c05ac
Refactor to simplify
LewisCrawford d2d307f
Merge remote-tracking branch 'origin/main' into instsimplify_maximumnum
LewisCrawford 08b0df5
Regenerate test results
LewisCrawford 349bbb3
Update test comments (remove TODOs)
LewisCrawford d3c2f6f
Rename some CHECK variables for consistentcy
LewisCrawford 989bd58
Add new mixed vector element tests
LewisCrawford 18fc2ba
Fix more variable naming inconsistencies
LewisCrawford fd1d020
Fix more variable naming issues
LewisCrawford 216a740
Merge branch 'main' into instsimplify_maximumnum
LewisCrawford 2bc2b18
Fix unused variable error
LewisCrawford 06e3062
Merge branch 'main' into instsimplify_maximumnum
LewisCrawford File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6473,7 +6473,8 @@ static Value *foldMinMaxSharedOp(Intrinsic::ID IID, Value *Op0, Value *Op1) { | |
| static Value *foldMinimumMaximumSharedOp(Intrinsic::ID IID, Value *Op0, | ||
| Value *Op1) { | ||
| assert((IID == Intrinsic::maxnum || IID == Intrinsic::minnum || | ||
| IID == Intrinsic::maximum || IID == Intrinsic::minimum) && | ||
| IID == Intrinsic::maximum || IID == Intrinsic::minimum || | ||
| IID == Intrinsic::maximumnum || IID == Intrinsic::minimumnum) && | ||
| "Unsupported intrinsic"); | ||
|
|
||
| auto *M0 = dyn_cast<IntrinsicInst>(Op0); | ||
|
|
@@ -6512,6 +6513,82 @@ static Value *foldMinimumMaximumSharedOp(Intrinsic::ID IID, Value *Op0, | |
| return nullptr; | ||
| } | ||
|
|
||
| enum class MinMaxOptResult { | ||
| CannotOptimize = 0, | ||
| UseNewConstVal = 1, | ||
| UseOtherVal = 2, | ||
| // For undef/poison, we can choose to either propgate undef/poison or | ||
| // use the LHS value depending on what will allow more optimization. | ||
| UseEither = 3 | ||
| }; | ||
| // Get the optimized value for a min/max instruction with a single constant | ||
| // input (either undef or scalar constantFP). The result may indicate to | ||
| // use the non-const LHS value, use a new constant value instead (with NaNs | ||
| // quieted), or to choose either option in the case of undef/poison. | ||
| static MinMaxOptResult OptimizeConstMinMax(const Constant *RHSConst, | ||
| const Intrinsic::ID IID, | ||
| const CallBase *Call, | ||
| Constant **OutNewConstVal) { | ||
| assert(OutNewConstVal != nullptr); | ||
|
|
||
| bool PropagateNaN = IID == Intrinsic::minimum || IID == Intrinsic::maximum; | ||
| bool PropagateSNaN = IID == Intrinsic::minnum || IID == Intrinsic::maxnum; | ||
| bool IsMin = IID == Intrinsic::minimum || IID == Intrinsic::minnum || | ||
| IID == Intrinsic::minimumnum; | ||
|
|
||
| // min/max(x, poison) -> either x or poison | ||
| if (isa<UndefValue>(RHSConst)) { | ||
| *OutNewConstVal = const_cast<Constant *>(RHSConst); | ||
| return MinMaxOptResult::UseEither; | ||
| } | ||
|
|
||
| const ConstantFP *CFP = dyn_cast<ConstantFP>(RHSConst); | ||
| if (!CFP) | ||
| return MinMaxOptResult::CannotOptimize; | ||
| APFloat CAPF = CFP->getValueAPF(); | ||
|
|
||
| // minnum(x, qnan) -> x | ||
| // maxnum(x, qnan) -> x | ||
| // minnum(x, snan) -> qnan | ||
| // maxnum(x, snan) -> qnan | ||
| // minimum(X, nan) -> qnan | ||
| // maximum(X, nan) -> qnan | ||
| // minimumnum(X, nan) -> x | ||
| // maximumnum(X, nan) -> x | ||
| if (CAPF.isNaN()) { | ||
| if (PropagateNaN || (PropagateSNaN && CAPF.isSignaling())) { | ||
| *OutNewConstVal = ConstantFP::get(CFP->getType(), CAPF.makeQuiet()); | ||
| return MinMaxOptResult::UseNewConstVal; | ||
| } | ||
| return MinMaxOptResult::UseOtherVal; | ||
| } | ||
|
|
||
| if (CAPF.isInfinity() || (Call && Call->hasNoInfs() && CAPF.isLargest())) { | ||
| // minnum(X, -inf) -> -inf (ignoring sNaN -> qNaN propagation) | ||
| // maxnum(X, +inf) -> +inf (ignoring sNaN -> qNaN propagation) | ||
| // minimum(X, -inf) -> -inf if nnan | ||
| // maximum(X, +inf) -> +inf if nnan | ||
| // minimumnum(X, -inf) -> -inf | ||
| // maximumnum(X, +inf) -> +inf | ||
| if (CAPF.isNegative() == IsMin && | ||
| (!PropagateNaN || (Call && Call->hasNoNaNs()))) { | ||
| *OutNewConstVal = const_cast<Constant *>(RHSConst); | ||
| return MinMaxOptResult::UseNewConstVal; | ||
| } | ||
|
|
||
| // minnum(X, +inf) -> X if nnan | ||
| // maxnum(X, -inf) -> X if nnan | ||
| // minimum(X, +inf) -> X (ignoring quieting of sNaNs) | ||
| // maximum(X, -inf) -> X (ignoring quieting of sNaNs) | ||
| // minimumnum(X, +inf) -> X if nnan | ||
| // maximumnum(X, -inf) -> X if nnan | ||
| if (CAPF.isNegative() != IsMin && | ||
| (PropagateNaN || (Call && Call->hasNoNaNs()))) | ||
| return MinMaxOptResult::UseOtherVal; | ||
| } | ||
| return MinMaxOptResult::CannotOptimize; | ||
| } | ||
|
|
||
| Value *llvm::simplifyBinaryIntrinsic(Intrinsic::ID IID, Type *ReturnType, | ||
| Value *Op0, Value *Op1, | ||
| const SimplifyQuery &Q, | ||
|
|
@@ -6780,49 +6857,73 @@ Value *llvm::simplifyBinaryIntrinsic(Intrinsic::ID IID, Type *ReturnType, | |
| case Intrinsic::maxnum: | ||
| case Intrinsic::minnum: | ||
| case Intrinsic::maximum: | ||
| case Intrinsic::minimum: { | ||
| // If the arguments are the same, this is a no-op. | ||
| case Intrinsic::minimum: | ||
| case Intrinsic::maximumnum: | ||
| case Intrinsic::minimumnum: { | ||
| // In several cases here, we deviate from exact IEEE 754 semantics | ||
| // to enable optimizations (as allowed by the LLVM IR spec). | ||
| // | ||
| // For instance, we may return one of the arguments unmodified instead of | ||
| // inserting an llvm.canonicalize to transform input sNaNs into qNaNs, | ||
| // or may assume all NaN inputs are qNaNs. | ||
|
Comment on lines
+6866
to
+6868
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. This comment is imprecise enough to be misleading. The new langref for minnum/maxnum require the correct snan handling |
||
|
|
||
| // If the arguments are the same, this is a no-op (ignoring NaN quieting) | ||
| if (Op0 == Op1) | ||
| return Op0; | ||
|
|
||
| // Canonicalize constant operand as Op1. | ||
| if (isa<Constant>(Op0)) | ||
| std::swap(Op0, Op1); | ||
|
|
||
| // If an argument is undef, return the other argument. | ||
| if (Q.isUndefValue(Op1)) | ||
| return Op0; | ||
| if (Constant *C = dyn_cast<Constant>(Op1)) { | ||
| MinMaxOptResult OptResult = MinMaxOptResult::CannotOptimize; | ||
| Constant *NewConst = nullptr; | ||
|
|
||
| if (VectorType *VTy = dyn_cast<VectorType>(C->getType())) { | ||
| ElementCount ElemCount = VTy->getElementCount(); | ||
|
|
||
| if (Constant *SplatVal = C->getSplatValue()) { | ||
| // Handle splat vectors (including scalable vectors) | ||
| OptResult = OptimizeConstMinMax(SplatVal, IID, Call, &NewConst); | ||
| if (OptResult == MinMaxOptResult::UseNewConstVal) | ||
| NewConst = ConstantVector::getSplat(ElemCount, NewConst); | ||
|
|
||
| } else if (ElemCount.isFixed()) { | ||
| // Storage to build up new const return value (with NaNs quieted) | ||
| SmallVector<Constant *, 16> NewC(ElemCount.getFixedValue()); | ||
|
|
||
| // Check elementwise whether we can optimize to either a constant | ||
| // value or return the LHS value. We cannot mix and match LHS + | ||
| // constant elements, as this would require inserting a new | ||
| // VectorShuffle instruction, which is not allowed in simplifyBinOp. | ||
| OptResult = MinMaxOptResult::UseEither; | ||
| for (unsigned i = 0; i != ElemCount.getFixedValue(); ++i) { | ||
| auto ElemResult = OptimizeConstMinMax(C->getAggregateElement(i), | ||
| IID, Call, &NewConst); | ||
| if (ElemResult == MinMaxOptResult::CannotOptimize || | ||
| (ElemResult != OptResult && | ||
| OptResult != MinMaxOptResult::UseEither && | ||
| ElemResult != MinMaxOptResult::UseEither)) { | ||
| OptResult = MinMaxOptResult::CannotOptimize; | ||
| break; | ||
| } | ||
| NewC[i] = NewConst; | ||
| if (ElemResult != MinMaxOptResult::UseEither) | ||
| OptResult = ElemResult; | ||
| } | ||
| if (OptResult == MinMaxOptResult::UseNewConstVal) | ||
| NewConst = ConstantVector::get(NewC); | ||
| } | ||
| } else { | ||
| // Handle scalar inputs | ||
| OptResult = OptimizeConstMinMax(C, IID, Call, &NewConst); | ||
| } | ||
|
|
||
| bool PropagateNaN = IID == Intrinsic::minimum || IID == Intrinsic::maximum; | ||
| bool IsMin = IID == Intrinsic::minimum || IID == Intrinsic::minnum; | ||
|
|
||
| // minnum(X, nan) -> X | ||
| // maxnum(X, nan) -> X | ||
| // minimum(X, nan) -> nan | ||
| // maximum(X, nan) -> nan | ||
| if (match(Op1, m_NaN())) | ||
| return PropagateNaN ? propagateNaN(cast<Constant>(Op1)) : Op0; | ||
|
|
||
| // In the following folds, inf can be replaced with the largest finite | ||
| // float, if the ninf flag is set. | ||
| const APFloat *C; | ||
| if (match(Op1, m_APFloat(C)) && | ||
| (C->isInfinity() || (Call && Call->hasNoInfs() && C->isLargest()))) { | ||
| // minnum(X, -inf) -> -inf | ||
| // maxnum(X, +inf) -> +inf | ||
| // minimum(X, -inf) -> -inf if nnan | ||
| // maximum(X, +inf) -> +inf if nnan | ||
| if (C->isNegative() == IsMin && | ||
| (!PropagateNaN || (Call && Call->hasNoNaNs()))) | ||
| return ConstantFP::get(ReturnType, *C); | ||
|
|
||
| // minnum(X, +inf) -> X if nnan | ||
| // maxnum(X, -inf) -> X if nnan | ||
| // minimum(X, +inf) -> X | ||
| // maximum(X, -inf) -> X | ||
| if (C->isNegative() != IsMin && | ||
| (PropagateNaN || (Call && Call->hasNoNaNs()))) | ||
| return Op0; | ||
| if (OptResult == MinMaxOptResult::UseOtherVal || | ||
| OptResult == MinMaxOptResult::UseEither) | ||
| return Op0; // Return the other arg (ignoring NaN quieting) | ||
| else if (OptResult == MinMaxOptResult::UseNewConstVal) | ||
| return NewConst; | ||
| } | ||
|
|
||
| // Min/max of the same operation with common operand: | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should hyphenate IEEE-754