@@ -1760,49 +1760,181 @@ template <typename CmpOp>
17601760struct MinMaxConverter : public OpRewritePattern <CmpOp> {
17611761 using OpRewritePattern<CmpOp>::OpRewritePattern;
17621762
1763+ template <typename T> static T getOnlyUserOfType (Value val) {
1764+ if (!val || !val.hasOneUse ()) {
1765+ return nullptr ;
1766+ }
1767+ return dyn_cast<T>(*val.getUsers ().begin ());
1768+ }
1769+
1770+ // Only handle the Cmp + OrIOp + Select pattern here.
1771+ static arith::SelectOp findSelectThroughOr (Value cond) {
1772+ if (auto ori = getOnlyUserOfType<arith::OrIOp>(cond)) {
1773+ return getOnlyUserOfType<arith::SelectOp>(ori.getResult ());
1774+ }
1775+ return nullptr ;
1776+ }
1777+
17631778 MinMaxConverter (MLIRContext *context)
17641779 : OpRewritePattern<CmpOp>(context, /* benefit=*/ 10 ) {}
17651780
1781+ // / Helper that maps a floating-point compare predicate to the
1782+ // / corresponding min/max operation. THis is parametrized by
1783+ // / whether we want NaN-aware operations (MaximumFOp/MinimumFOp) or
1784+ // / numeric operations (MaxNumFOp/MinNumFOp).
1785+ FailureOr<Value> foldCmpToMinMax (PatternRewriter &rewriter, Location loc,
1786+ Value lhs, Value rhs,
1787+ arith::CmpFPredicate pred,
1788+ bool useNaNOps) const {
1789+ switch (pred) {
1790+ case arith::CmpFPredicate::OGT :
1791+ case arith::CmpFPredicate::OGE :
1792+ if (useNaNOps) {
1793+ return arith::MaximumFOp::create (rewriter, loc, lhs, rhs).getResult ();
1794+ } else {
1795+ return arith::MaxNumFOp::create (rewriter, loc, lhs, rhs).getResult ();
1796+ }
1797+ return success ();
1798+ case arith::CmpFPredicate::OLT :
1799+ case arith::CmpFPredicate::OLE :
1800+ if (useNaNOps) {
1801+ return arith::MinimumFOp::create (rewriter, loc, lhs, rhs).getResult ();
1802+ } else {
1803+ return arith::MinNumFOp::create (rewriter, loc, lhs, rhs).getResult ();
1804+ }
1805+ default :
1806+ return failure ();
1807+ }
1808+ }
1809+
17661810 LogicalResult matchAndRewrite (CmpOp cmpOp,
17671811 PatternRewriter &rewriter) const final {
1768- if (!cmpOp.getResult ().hasOneUse ()) {
1812+ Value result = cmpOp.getResult ();
1813+ if (!result.hasOneUse ()) {
17691814 return failure ();
17701815 }
1771- auto selectOp =
1772- dyn_cast<arith::SelectOp>(*cmpOp.getResult ().getUsers ().begin ());
1816+
1817+ // 1. Simple pattern: cmpf + select.
1818+ if (auto selectOp = dyn_cast<arith::SelectOp>(*result.getUsers ().begin ())) {
1819+ if (!(result == selectOp.getCondition () &&
1820+ (cmpOp.getLhs () == selectOp.getTrueValue () &&
1821+ cmpOp.getRhs () == selectOp.getFalseValue ()))) {
1822+ return failure ();
1823+ }
1824+
1825+ rewriteOpWithMinMax (rewriter, cmpOp, selectOp, cmpOp.getPredicate ());
1826+ rewriter.eraseOp (cmpOp);
1827+ return success ();
1828+ }
1829+
1830+ // 2. NaN-aware pattern: cmpf + or + select.
1831+ auto selectOp = findSelectThroughOr (result);
17731832 if (!selectOp) {
17741833 return failure ();
17751834 }
17761835
1777- if (!(cmpOp.getResult () == selectOp.getCondition () &&
1778- cmpOp.getLhs () == selectOp.getTrueValue () &&
1779- cmpOp.getRhs () == selectOp.getFalseValue ())) {
1836+ if (failed (foldCmpSelectToMinMax (rewriter, selectOp))) {
17801837 return failure ();
17811838 }
1839+ return success ();
1840+ }
17821841
1783- rewriteOpWithMinMax (rewriter, cmpOp, selectOp, cmpOp.getPredicate ());
1784- rewriter.eraseOp (cmpOp);
1842+ // / foldCmpSelectToMinMax performs an optimization pattern that matches
1843+ // / 'arith.select' operations based on a floating-point comparison
1844+ // / and rewrites them into equivalent numeric min/max operations.
1845+ // /
1846+ // / This pattern handles the following case:
1847+ // /
1848+ // / ** NaN-Aware Min/Max Reduction **
1849+ // / - select (cmpf ogt a, b) || cmpf une a, a), a, b --> arith.maximumf(a,
1850+ // / b)
1851+ // / - select (cmpf olt a, b) || cmpf une a, a), a, b --> arith.minimumf(a,
1852+ // / b)
1853+ // /
1854+ // / These transformations not only improve IR canonicalization but also
1855+ // / allow the successful lowering of tt.reduce operations to linalg
1856+ // / operations, which is already supported in the triton-shared dialect
1857+ // / conversion pipeline.
1858+
1859+ LogicalResult foldCmpSelectToMinMax (PatternRewriter &rewriter,
1860+ arith::SelectOp sel) const {
1861+
1862+ if (!isa<FloatType>(sel.getType ())) {
1863+ return failure ();
1864+ }
17851865
1786- return success ();
1866+ Operation *condOp = sel.getCondition ().getDefiningOp ();
1867+ if (!condOp) {
1868+ return failure ();
1869+ }
1870+
1871+ Value trueVal = sel.getTrueValue ();
1872+ Value falseVal = sel.getFalseValue ();
1873+
1874+ // NaN-Aware Min/Max Reduction.
1875+ auto ori = dyn_cast<arith::OrIOp>(condOp);
1876+ if (!ori)
1877+ return failure ();
1878+ // Extract both sides of the OR condition.
1879+ auto cmp1 = ori.getLhs ().getDefiningOp <arith::CmpFOp>();
1880+ auto cmp2 = ori.getRhs ().getDefiningOp <arith::CmpFOp>();
1881+ if (!cmp1 || !cmp2)
1882+ return failure ();
1883+
1884+ // Helper lambdas to identify comparison patterns.
1885+ auto isOGT = [&](arith::CmpFOp cmp) {
1886+ return cmp.getPredicate () == arith::CmpFPredicate::OGT &&
1887+ trueVal == cmp.getLhs () && falseVal == cmp.getRhs ();
1888+ };
1889+ auto isOLT = [&](arith::CmpFOp cmp) {
1890+ return cmp.getPredicate () == arith::CmpFPredicate::OLT &&
1891+ trueVal == cmp.getLhs () && falseVal == cmp.getRhs ();
1892+ };
1893+ auto isNaN = [&](arith::CmpFOp cmp) {
1894+ return cmp.getPredicate () == arith::CmpFPredicate::UNE &&
1895+ trueVal == cmp.getLhs () && trueVal == cmp.getRhs ();
1896+ };
1897+
1898+ // Match: select ((ogt(a, b) || une(a, a)), a, b) -> arith.maximumf(a, b).
1899+ if ((isOGT (cmp1) && isNaN (cmp2)) || (isOGT (cmp2) && isNaN (cmp1))) {
1900+ PatternRewriter::InsertionGuard guard (rewriter);
1901+ rewriter.setInsertionPoint (sel);
1902+ FailureOr<Value> foldResult = foldCmpToMinMax (
1903+ rewriter, sel.getLoc (), trueVal, falseVal, arith::CmpFPredicate::OGT ,
1904+ /* useNaNOps=*/ true );
1905+ if (failed (foldResult)) {
1906+ return failure ();
1907+ }
1908+ rewriter.replaceOp (sel, *foldResult);
1909+ return success ();
1910+ }
1911+
1912+ // Match: select ((olt(a, b) || une(a, a)), a, b) -> arith.minimumf(a, b).
1913+ if ((isOLT (cmp1) && isNaN (cmp2)) || (isOLT (cmp2) && isNaN (cmp1))) {
1914+ PatternRewriter::InsertionGuard guard (rewriter);
1915+ rewriter.setInsertionPoint (sel);
1916+ FailureOr<Value> foldResult = foldCmpToMinMax (
1917+ rewriter, sel.getLoc (), trueVal, falseVal, arith::CmpFPredicate::OLT ,
1918+ /* useNaNOps=*/ true );
1919+ if (failed (foldResult)) {
1920+ return failure ();
1921+ }
1922+ rewriter.replaceOp (sel, *foldResult);
1923+ return success ();
1924+ }
1925+ return failure ();
17871926 }
17881927
17891928 void rewriteOpWithMinMax (PatternRewriter &rewriter, arith::CmpFOp cmpOp,
17901929 arith::SelectOp selectOp,
17911930 arith::CmpFPredicate pred) const {
1792- switch (pred) {
1793- case arith::CmpFPredicate::OGT :
1794- case arith::CmpFPredicate::OGE :
1795- rewriter.replaceOpWithNewOp <arith::MaximumFOp>(selectOp, cmpOp.getLhs (),
1796- cmpOp.getRhs ());
1797- break ;
1798- case arith::CmpFPredicate::OLT :
1799- case arith::CmpFPredicate::OLE :
1800- rewriter.replaceOpWithNewOp <arith::MinimumFOp>(selectOp, cmpOp.getLhs (),
1801- cmpOp.getRhs ());
1802- break ;
1803- default :
1931+ FailureOr<Value> foldedResult =
1932+ foldCmpToMinMax (rewriter, selectOp.getLoc (), cmpOp.getLhs (),
1933+ cmpOp.getRhs (), pred, /* useNaNOps=*/ true );
1934+ if (failed (foldedResult)) {
18041935 llvm_unreachable (" Unhandled predicate" );
18051936 }
1937+ rewriter.replaceOp (selectOp, *foldedResult);
18061938 }
18071939
18081940 void rewriteOpWithMinMax (PatternRewriter &rewriter, arith::CmpIOp cmpOp,
0 commit comments