-
Notifications
You must be signed in to change notification settings - Fork 14.7k
[LV] Vectorize FMax via OrderedFCmpSelect w/o fast-math flags. #146711
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?
Changes from 4 commits
9d28282
5675396
ec473e5
caae126
92ebac1
25a1f39
d50a372
43ff8ed
a3ae508
52b72e7
88e581e
b92dde2
e59025f
d3a4ca9
6605452
32baf05
02156d9
6793b20
f579116
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 |
---|---|---|
|
@@ -47,6 +47,9 @@ enum class RecurKind { | |
FMul, ///< Product of floats. | ||
FMin, ///< FP min implemented in terms of select(cmp()). | ||
FMax, ///< FP max implemented in terms of select(cmp()). | ||
FCmpOGTSelect, ///< FP max implemented in terms of select(cmp()), but without | ||
/// any fast-math flags. Users need to handle NaNs and signed | ||
/// zeros when generating code. | ||
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. Pattern and how ("users need") to handle it should indeed be explained, but preferably elsewhere. Pattern seems to suggest how to handle FP reductions (min, max, possibly others as well?) in the presence of NaNs and/or signed zeroes (both equally challenging?), which is evaded in the presence of certain fast-math flags (namely absence of nans and signed zeroes?). Does the following sound right: The vector of partial subset reduction results of case (a) contain only non NaN's, and is subject to standard final reduction. In case (b), this vector holds only NaN's or only the initial value, which provides the respective final value. Case (c) requires "tie breaking" based on index? What if the initial value is NaN? 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.
Explanation is currently interleaved in
Yep, update to require ordered predicates, so it would be the start value if all-NaNs.
Yep, restricted to just ordered for now.
Yep for cases a) and b). For case c), if there is any non-NaN value (either start or any value in the loop), the reduction result is non-NaN. If any lane is non-NaN in the partial reduction vector, it will get selected. The tie-breaking is mainly needed for signed zeros, where we need to pick the first one. Without tie-breaking, horizontal fmax will return +0.0 if it contains both -0.0 and +0.0, but if -0.0 has been seen first it needs to be selected first according to the index. |
||
FMinimum, ///< FP min with llvm.minimum semantics | ||
FMaximum, ///< FP max with llvm.maximum semantics | ||
FMinimumNum, ///< FP min with llvm.minimumnum semantics | ||
|
@@ -250,8 +253,9 @@ class RecurrenceDescriptor { | |
/// Returns true if the recurrence kind is a floating-point min/max kind. | ||
static bool isFPMinMaxRecurrenceKind(RecurKind Kind) { | ||
return Kind == RecurKind::FMin || Kind == RecurKind::FMax || | ||
Kind == RecurKind::FMinimum || Kind == RecurKind::FMaximum || | ||
Kind == RecurKind::FMinimumNum || Kind == RecurKind::FMaximumNum; | ||
Kind == RecurKind::FCmpOGTSelect || Kind == RecurKind::FMinimum || | ||
Kind == RecurKind::FMaximum || Kind == RecurKind::FMinimumNum || | ||
Kind == RecurKind::FMaximumNum; | ||
} | ||
|
||
/// Returns true if the recurrence kind is any min/max kind. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -819,7 +819,8 @@ RecurrenceDescriptor::isMinMaxPattern(Instruction *I, RecurKind Kind, | |
if (match(I, m_OrdOrUnordFMin(m_Value(), m_Value()))) | ||
return InstDesc(Kind == RecurKind::FMin, I); | ||
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. Only max is handled by OrderedFCmpSelect, not min? Can start w/ FMaxOGT only. |
||
if (match(I, m_OrdOrUnordFMax(m_Value(), m_Value()))) | ||
return InstDesc(Kind == RecurKind::FMax, I); | ||
return InstDesc(Kind == RecurKind::FMax || Kind == RecurKind::FCmpOGTSelect, | ||
I); | ||
if (match(I, m_FMinNum(m_Value(), m_Value()))) | ||
return InstDesc(Kind == RecurKind::FMin, I); | ||
if (match(I, m_FMaxNum(m_Value(), m_Value()))) | ||
|
@@ -941,10 +942,15 @@ RecurrenceDescriptor::InstDesc RecurrenceDescriptor::isRecurrenceInstr( | |
m_Intrinsic<Intrinsic::minimumnum>(m_Value(), m_Value())) || | ||
match(I, m_Intrinsic<Intrinsic::maximumnum>(m_Value(), m_Value())); | ||
}; | ||
if (isIntMinMaxRecurrenceKind(Kind) || | ||
(HasRequiredFMF() && isFPMinMaxRecurrenceKind(Kind))) | ||
if (isIntMinMaxRecurrenceKind(Kind)) | ||
return isMinMaxPattern(I, Kind, Prev); | ||
else if (isFMulAddIntrinsic(I)) | ||
if (isFPMinMaxRecurrenceKind(Kind)) { | ||
if (HasRequiredFMF()) | ||
return isMinMaxPattern(I, Kind, Prev); | ||
if ((Kind == RecurKind::FMax || Kind == RecurKind::FCmpOGTSelect) && | ||
isMinMaxPattern(I, Kind, Prev).isRecurrence()) | ||
return InstDesc(I, RecurKind::FCmpOGTSelect); | ||
} else if (isFMulAddIntrinsic(I)) | ||
return InstDesc(Kind == RecurKind::FMulAdd, I, | ||
I->hasAllowReassoc() ? nullptr : I); | ||
return InstDesc(false, I); | ||
|
@@ -1207,6 +1213,7 @@ unsigned RecurrenceDescriptor::getOpcode(RecurKind Kind) { | |
case RecurKind::UMin: | ||
return Instruction::ICmp; | ||
case RecurKind::FMax: | ||
case RecurKind::FCmpOGTSelect: | ||
case RecurKind::FMin: | ||
case RecurKind::FMaximum: | ||
case RecurKind::FMinimum: | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -25,6 +25,7 @@ | |||||||||||||||||||||||||||||||||||||||||||
#define DEBUG_TYPE "vplan" | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
using namespace llvm; | ||||||||||||||||||||||||||||||||||||||||||||
using namespace VPlanPatternMatch; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
namespace { | ||||||||||||||||||||||||||||||||||||||||||||
// Class that is used to build the plain CFG for the incoming IR. | ||||||||||||||||||||||||||||||||||||||||||||
|
@@ -427,7 +428,6 @@ static void createLoopRegion(VPlan &Plan, VPBlockBase *HeaderVPB) { | |||||||||||||||||||||||||||||||||||||||||||
static void addCanonicalIVRecipes(VPlan &Plan, VPBasicBlock *HeaderVPBB, | ||||||||||||||||||||||||||||||||||||||||||||
VPBasicBlock *LatchVPBB, Type *IdxTy, | ||||||||||||||||||||||||||||||||||||||||||||
DebugLoc DL) { | ||||||||||||||||||||||||||||||||||||||||||||
using namespace VPlanPatternMatch; | ||||||||||||||||||||||||||||||||||||||||||||
Value *StartIdx = ConstantInt::get(IdxTy, 0); | ||||||||||||||||||||||||||||||||||||||||||||
auto *StartV = Plan.getOrAddLiveIn(StartIdx); | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
|
@@ -628,3 +628,118 @@ void VPlanTransforms::attachCheckBlock(VPlan &Plan, Value *Cond, | |||||||||||||||||||||||||||||||||||||||||||
Term->addMetadata(LLVMContext::MD_prof, BranchWeights); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
bool VPlanTransforms::handleFMaxReductionsWithoutFastMath(VPlan &Plan) { | ||||||||||||||||||||||||||||||||||||||||||||
VPRegionBlock *LoopRegion = Plan.getVectorLoopRegion(); | ||||||||||||||||||||||||||||||||||||||||||||
VPReductionPHIRecipe *RedPhiR = nullptr; | ||||||||||||||||||||||||||||||||||||||||||||
VPRecipeWithIRFlags *MaxOp = nullptr; | ||||||||||||||||||||||||||||||||||||||||||||
VPWidenIntOrFpInductionRecipe *WideIV = nullptr; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// Check if there are any FCmpOGTSelect reductions using wide selects that we | ||||||||||||||||||||||||||||||||||||||||||||
// can fix up. To do so, we also need a wide canonical IV to keep track of | ||||||||||||||||||||||||||||||||||||||||||||
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
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. Updated, thanks |
||||||||||||||||||||||||||||||||||||||||||||
// the indices of the max values. | ||||||||||||||||||||||||||||||||||||||||||||
for (auto &R : LoopRegion->getEntryBasicBlock()->phis()) { | ||||||||||||||||||||||||||||||||||||||||||||
// We need a wide canonical IV | ||||||||||||||||||||||||||||||||||||||||||||
if (auto *CurIV = dyn_cast<VPWidenIntOrFpInductionRecipe>(&R)) { | ||||||||||||||||||||||||||||||||||||||||||||
if (!CurIV->isCanonical()) | ||||||||||||||||||||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||||||||||||||||||||
WideIV = CurIV; | ||||||||||||||||||||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// And a single FCmpOGTSelect reduction phi. | ||||||||||||||||||||||||||||||||||||||||||||
// TODO: Support FMin reductions as well. | ||||||||||||||||||||||||||||||||||||||||||||
auto *CurRedPhiR = dyn_cast<VPReductionPHIRecipe>(&R); | ||||||||||||||||||||||||||||||||||||||||||||
if (!CurRedPhiR) | ||||||||||||||||||||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||||||||||||||||||||
if (RedPhiR) | ||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||
if (CurRedPhiR->getRecurrenceKind() != RecurKind::FCmpOGTSelect || | ||||||||||||||||||||||||||||||||||||||||||||
CurRedPhiR->isInLoop() || CurRedPhiR->isOrdered()) | ||||||||||||||||||||||||||||||||||||||||||||
continue; | ||||||||||||||||||||||||||||||||||||||||||||
RedPhiR = CurRedPhiR; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// MaxOp feeding the reduction phi must be a select (either wide or a | ||||||||||||||||||||||||||||||||||||||||||||
// replicate recipe), where the phi is the last operand, and the compare | ||||||||||||||||||||||||||||||||||||||||||||
// predicate is strict. This ensures NaNs won't get propagated unless the | ||||||||||||||||||||||||||||||||||||||||||||
// initial value is NaN | ||||||||||||||||||||||||||||||||||||||||||||
VPRecipeBase *Inc = RedPhiR->getBackedgeValue()->getDefiningRecipe(); | ||||||||||||||||||||||||||||||||||||||||||||
auto *RepR = dyn_cast<VPReplicateRecipe>(Inc); | ||||||||||||||||||||||||||||||||||||||||||||
if (!isa<VPWidenSelectRecipe>(Inc) && | ||||||||||||||||||||||||||||||||||||||||||||
!(RepR && (isa<SelectInst>(RepR->getUnderlyingInstr())))) | ||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
MaxOp = cast<VPRecipeWithIRFlags>(Inc); | ||||||||||||||||||||||||||||||||||||||||||||
auto *Cmp = cast<VPRecipeWithIRFlags>(MaxOp->getOperand(0)); | ||||||||||||||||||||||||||||||||||||||||||||
if (MaxOp->getOperand(1) == RedPhiR || | ||||||||||||||||||||||||||||||||||||||||||||
!CmpInst::isStrictPredicate(Cmp->getPredicate())) | ||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// Nothing to do. | ||||||||||||||||||||||||||||||||||||||||||||
if (!RedPhiR) | ||||||||||||||||||||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// A wide canonical IV is currently required. | ||||||||||||||||||||||||||||||||||||||||||||
// TODO: Create an induction if no suitable existing one is available. | ||||||||||||||||||||||||||||||||||||||||||||
if (!WideIV) | ||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+690
to
+693
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. Note that a scalar canonical IV always exists, and is unique. But widen ones may exist (last one found is used?) or not. 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. Yep, at this stage, all inductions will still be widened, but may not be canonical. |
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// Create a reduction that tracks the first indices where the latest maximum | ||||||||||||||||||||||||||||||||||||||||||||
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
|
||||||||||||||||||||||||||||||||||||||||||||
// value has been selected. This is later used to select the max value from | ||||||||||||||||||||||||||||||||||||||||||||
// the partial reductions in a way that correctly handles signed zeros and | ||||||||||||||||||||||||||||||||||||||||||||
// NaNs in the input. | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+697
to
+698
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
(NaN's nor non-zero numbers do not require the tracked indices.) |
||||||||||||||||||||||||||||||||||||||||||||
// Note that we do not need to check if the induction may hit the sentinel | ||||||||||||||||||||||||||||||||||||||||||||
// value. If the sentinel value gets hit, the final reduction value is at the | ||||||||||||||||||||||||||||||||||||||||||||
// last index or the maximum was never set and all lanes contain the start | ||||||||||||||||||||||||||||||||||||||||||||
// value. In either case, the correct value is selected. | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+699
to
+702
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. Tried to elaborate, is this clear/correct/helpful?
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
unsigned IVWidth = | ||||||||||||||||||||||||||||||||||||||||||||
VPTypeAnalysis(Plan).inferScalarType(WideIV)->getScalarSizeInBits(); | ||||||||||||||||||||||||||||||||||||||||||||
LLVMContext &Ctx = Plan.getScalarHeader()->getIRBasicBlock()->getContext(); | ||||||||||||||||||||||||||||||||||||||||||||
VPValue *UMinSentinel = | ||||||||||||||||||||||||||||||||||||||||||||
Plan.getOrAddLiveIn(ConstantInt::get(Ctx, APInt::getMaxValue(IVWidth))); | ||||||||||||||||||||||||||||||||||||||||||||
auto *IdxPhi = new VPReductionPHIRecipe(nullptr, RecurKind::FindFirstIVUMin, | ||||||||||||||||||||||||||||||||||||||||||||
*UMinSentinel, false, false, 1); | ||||||||||||||||||||||||||||||||||||||||||||
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. Worth commenting constant parameters. |
||||||||||||||||||||||||||||||||||||||||||||
IdxPhi->insertBefore(RedPhiR); | ||||||||||||||||||||||||||||||||||||||||||||
auto *MinIdxSel = new VPInstruction(Instruction::Select, | ||||||||||||||||||||||||||||||||||||||||||||
{MaxOp->getOperand(0), WideIV, IdxPhi}); | ||||||||||||||||||||||||||||||||||||||||||||
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
|
||||||||||||||||||||||||||||||||||||||||||||
MinIdxSel->insertAfter(MaxOp); | ||||||||||||||||||||||||||||||||||||||||||||
IdxPhi->addOperand(MinIdxSel); | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// Find the first index of with the maximum value. This is used to extract the | ||||||||||||||||||||||||||||||||||||||||||||
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
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. Done thanks |
||||||||||||||||||||||||||||||||||||||||||||
// lane with the final max value and is needed to handle signed zeros and NaNs | ||||||||||||||||||||||||||||||||||||||||||||
// in the input. | ||||||||||||||||||||||||||||||||||||||||||||
auto *MaxResult = find_singleton<VPSingleDefRecipe>( | ||||||||||||||||||||||||||||||||||||||||||||
RedPhiR->users(), [](VPUser *U, bool) -> VPSingleDefRecipe * { | ||||||||||||||||||||||||||||||||||||||||||||
auto *VPI = dyn_cast<VPInstruction>(U); | ||||||||||||||||||||||||||||||||||||||||||||
if (VPI && VPI->getOpcode() == VPInstruction::ComputeReductionResult) | ||||||||||||||||||||||||||||||||||||||||||||
return VPI; | ||||||||||||||||||||||||||||||||||||||||||||
return nullptr; | ||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||
VPBuilder Builder(MaxResult->getParent(), | ||||||||||||||||||||||||||||||||||||||||||||
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
|
||||||||||||||||||||||||||||||||||||||||||||
std::next(MaxResult->getIterator())); | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// Create mask for lanes that have the max value and use it to mask out | ||||||||||||||||||||||||||||||||||||||||||||
// indices that don't contain maximum values. | ||||||||||||||||||||||||||||||||||||||||||||
auto *MaskFinalMaxValue = Builder.createNaryOp( | ||||||||||||||||||||||||||||||||||||||||||||
Instruction::FCmp, {MaxResult->getOperand(1), MaxResult}, | ||||||||||||||||||||||||||||||||||||||||||||
VPIRFlags(CmpInst::FCMP_OEQ)); | ||||||||||||||||||||||||||||||||||||||||||||
auto *IndicesWithMaxValue = Builder.createNaryOp( | ||||||||||||||||||||||||||||||||||||||||||||
Instruction::Select, {MaskFinalMaxValue, MinIdxSel, UMinSentinel}); | ||||||||||||||||||||||||||||||||||||||||||||
auto *FirstMaxIdx = Builder.createNaryOp( | ||||||||||||||||||||||||||||||||||||||||||||
VPInstruction::ComputeFindIVResult, | ||||||||||||||||||||||||||||||||||||||||||||
{IdxPhi, WideIV->getStartValue(), UMinSentinel, IndicesWithMaxValue}); | ||||||||||||||||||||||||||||||||||||||||||||
// Convert the index of the first max value to an index in the vector lanes of | ||||||||||||||||||||||||||||||||||||||||||||
// the partial reduction results. This ensures we select the first max value | ||||||||||||||||||||||||||||||||||||||||||||
// and acts as a tie-breaker if the partial reductions contain signed zeros. | ||||||||||||||||||||||||||||||||||||||||||||
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. The vertical computation of each partial reduction result takes care of NaNs and signed zeroes, it is only the horizontal reduction of these vector lanes that require tie-breaking, to handle potential signed zeroes? 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. Yes, the tie-breaking is only needed to handle signed zeroes when computing the final reduction results. Consider a final partial reduction vector with We then compare the partial reduction values to the result of the horizontal reduction (-0.0 == +0.0 will also be true, selecting all lanes with zeros of any signed-ness) Out of those, we select the one encountered first using FindFirstIV. Note that this only works for strict predicates. |
||||||||||||||||||||||||||||||||||||||||||||
auto *FirstMaxLane = | ||||||||||||||||||||||||||||||||||||||||||||
Builder.createNaryOp(Instruction::URem, {FirstMaxIdx, &Plan.getVFxUF()}); | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
// Extract the final max value and update the users. | ||||||||||||||||||||||||||||||||||||||||||||
auto *Res = Builder.createNaryOp(VPInstruction::ExtractLane, | ||||||||||||||||||||||||||||||||||||||||||||
{FirstMaxLane, MaxResult->getOperand(1)}); | ||||||||||||||||||||||||||||||||||||||||||||
MaxResult->replaceUsesWithIf(Res, [MaskFinalMaxValue](VPUser &U, unsigned) { | ||||||||||||||||||||||||||||||||||||||||||||
return &U != MaskFinalMaxValue; | ||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||
return true; | ||||||||||||||||||||||||||||||||||||||||||||
} |
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.
The naming issue was admittedly raised before. This is still documented and titled as dealing with "FP max", and also affects loops doing
fcmp ugt
andselect
as in a testcase at the bottom. If RecurKind tries to capture the explicit pattern in the input IR, it may need to accommodate a variety of compare predicates. Signed and unsigned max and min try, OTOH, to abstract cmp/select pairs having a variety of lt/le/gt/ge predicated and same or reversed operands. Is this aiming to handle Ordered and/or Unordered FMax reduction of sets that may include NaNs (in terms of select(cmp()))?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.
Yep, this for now should be restricted to selects with ordered compares for now, so the result is only NaN if the start value is NaN.
It is not restricted to OGT, OLT also works. It needs to be a strict predicate, to use FindFirstIV. For non-strict ones we would have to use FindLastIV.
Updated to OrderedFCmpSelect, wdyt?