From 8c567a16a4f28ed11f9db5f87642259fe9af4b62 Mon Sep 17 00:00:00 2001 From: Daan de Graaf Date: Thu, 27 Aug 2026 14:37:46 +0000 Subject: [PATCH 1/3] LT and LE ops. --- compiler/include/graphalg/GraphAlgOps.td | 21 +++- compiler/include/graphalg/GraphAlgTypes.td | 2 + .../src/graphalg/GraphAlgCanonicalize.cpp | 48 ++++++++ .../src/graphalg/GraphAlgScalarizeApply.cpp | 6 +- compiler/src/graphalg/evaluate/Evaluator.cpp | 38 ++++++- compiler/test/canonicalize/compare.mlir | 103 ++++++++++++++++++ compiler/test/exec/le.mlir | 23 ++++ compiler/test/exec/lt.mlir | 22 ++++ compiler/test/graphalg-to-core/compare.mlir | 49 +++++++++ compiler/test/scalarize-apply/compare.mlir | 63 +++++++++++ 10 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 compiler/test/canonicalize/compare.mlir create mode 100644 compiler/test/exec/le.mlir create mode 100644 compiler/test/exec/lt.mlir create mode 100644 compiler/test/graphalg-to-core/compare.mlir create mode 100644 compiler/test/scalarize-apply/compare.mlir diff --git a/compiler/include/graphalg/GraphAlgOps.td b/compiler/include/graphalg/GraphAlgOps.td index 8e99851..104d834 100644 --- a/compiler/include/graphalg/GraphAlgOps.td +++ b/compiler/include/graphalg/GraphAlgOps.td @@ -723,10 +723,8 @@ def MulOp : Core_Op<"mul", [Pure, SameOperandsAndResultType]> { let hasFolder = 1; } -def EqOp : Core_Op<"eq", [Pure, AllTypesMatch<["lhs", "rhs"]>]> { - let summary = "Scalar equality comparison"; - - let arguments = (ins AnySemiring:$lhs, AnySemiring:$rhs); +class CompareOp : Core_Op]> { + let arguments = (ins PartialOrd:$lhs, PartialOrd:$rhs); let results = (outs I1:$result); @@ -737,6 +735,21 @@ def EqOp : Core_Op<"eq", [Pure, AllTypesMatch<["lhs", "rhs"]>]> { let hasFolder = 1; } +def EqOp : CompareOp<"eq"> { + let summary = "Scalar equality comparison"; + + // Arguments do not need to be partially ordered + let arguments = (ins AnySemiring:$lhs, AnySemiring:$rhs); +} + +def LtOp : CompareOp<"lt"> { + let summary = "Scalar less-than comparison"; +} + +def LeOp : CompareOp<"le"> { + let summary = "Scalar less-than-or-equal comparison"; +} + // Note: In spec but not used in implementation: OneVector. // Its functionality is subsumed by BroadcastOp. diff --git a/compiler/include/graphalg/GraphAlgTypes.td b/compiler/include/graphalg/GraphAlgTypes.td index 10feba7..e27969e 100644 --- a/compiler/include/graphalg/GraphAlgTypes.td +++ b/compiler/include/graphalg/GraphAlgTypes.td @@ -125,6 +125,8 @@ def AnySemiring : Type< CPred<"::llvm::isa($_self)">, "semiring">; +def PartialOrd : AnyTypeOf<[I64, F64]>; + class AllSemiringsMatch names> : AllMatchSameOperatorTrait< names, "llvm::cast($_self.getType()).getSemiring()", diff --git a/compiler/src/graphalg/GraphAlgCanonicalize.cpp b/compiler/src/graphalg/GraphAlgCanonicalize.cpp index f430b3c..1ec08fb 100644 --- a/compiler/src/graphalg/GraphAlgCanonicalize.cpp +++ b/compiler/src/graphalg/GraphAlgCanonicalize.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -522,6 +523,53 @@ mlir::OpFoldResult EqOp::fold(FoldAdaptor adaptor) { return nullptr; } +static bool isLessThan(mlir::IntegerAttr lhs, mlir::IntegerAttr rhs) { + return (lhs.getValue() - rhs.getValue()).isNegative(); +} + +static bool isLessThan(mlir::FloatAttr lhs, mlir::FloatAttr rhs) { + return lhs.getValueAsDouble() < rhs.getValueAsDouble(); +} + +static bool isLessThan(mlir::Attribute lhs, mlir::Attribute rhs) { + if (auto lhsInt = llvm::dyn_cast(lhs)) { + return isLessThan(lhsInt, llvm::cast(rhs)); + } else if (auto lhsFloat = llvm::dyn_cast(lhs)) { + return isLessThan(lhsFloat, llvm::cast(rhs)); + } else { + llvm_unreachable("unsupported type for less-than comparison"); + } +} + +mlir::OpFoldResult LtOp::fold(FoldAdaptor adaptor) { + if (getLhs() == getRhs()) { + return mlir::BoolAttr::get(getContext(), false); + } + + auto lhs = adaptor.getLhs(); + auto rhs = adaptor.getRhs(); + if (lhs && rhs) { + return mlir::BoolAttr::get(getContext(), isLessThan(lhs, rhs)); + } + + return nullptr; +} + +mlir::OpFoldResult LeOp::fold(FoldAdaptor adaptor) { + if (getLhs() == getRhs()) { + return mlir::BoolAttr::get(getContext(), true); + } + + auto lhs = adaptor.getLhs(); + auto rhs = adaptor.getRhs(); + if (lhs && rhs) { + return mlir::BoolAttr::get(getContext(), + isLessThan(lhs, rhs) || (lhs == rhs)); + } + + return nullptr; +} + // Test if the value is equal to the additive identity. static bool isAdditiveIdentity(mlir::Value v) { mlir::Attribute constantValue; diff --git a/compiler/src/graphalg/GraphAlgScalarizeApply.cpp b/compiler/src/graphalg/GraphAlgScalarizeApply.cpp index 3821395..4b6ad03 100644 --- a/compiler/src/graphalg/GraphAlgScalarizeApply.cpp +++ b/compiler/src/graphalg/GraphAlgScalarizeApply.cpp @@ -248,11 +248,13 @@ mlir::FailureOr createScalarOpFor(mlir::Location loc, BinaryOp op, return mlir::Value(builder.create(loc, falseOp, eqOp)); } case BinaryOp::LT: + return mlir::Value(builder.create(loc, lhs, rhs)); case BinaryOp::GT: + return mlir::Value(builder.create(loc, rhs, lhs)); case BinaryOp::LE: + return mlir::Value(builder.create(loc, lhs, rhs)); case BinaryOp::GE: - return mlir::emitError(loc) - << "operator " << stringifyBinaryOp(op) << " is not yet supported"; + return mlir::Value(builder.create(loc, rhs, lhs)); } } diff --git a/compiler/src/graphalg/evaluate/Evaluator.cpp b/compiler/src/graphalg/evaluate/Evaluator.cpp index a5f7072..e5a4b6d 100644 --- a/compiler/src/graphalg/evaluate/Evaluator.cpp +++ b/compiler/src/graphalg/evaluate/Evaluator.cpp @@ -56,6 +56,8 @@ class ScalarEvaluator { mlir::LogicalResult evaluate(MulOp op); mlir::LogicalResult evaluate(CastScalarOp op); mlir::LogicalResult evaluate(EqOp op); + mlir::LogicalResult evaluate(LtOp op); + mlir::LogicalResult evaluate(LeOp op); mlir::LogicalResult evaluate(mlir::arith::DivFOp op); mlir::LogicalResult evaluate(mlir::arith::SubIOp op); mlir::LogicalResult evaluate(mlir::arith::SubFOp op); @@ -421,12 +423,44 @@ mlir::LogicalResult ScalarEvaluator::evaluate(CastScalarOp op) { } mlir::LogicalResult ScalarEvaluator::evaluate(EqOp op) { - auto ring = llvm::cast(op.getType()); bool eq = _values[op.getLhs()] == _values[op.getRhs()]; _values[op] = mlir::BoolAttr::get(op.getContext(), eq); return mlir::success(); } +static bool isLessThan(mlir::IntegerAttr lhs, mlir::IntegerAttr rhs) { + return (lhs.getValue() - rhs.getValue()).isNegative(); +} + +static bool isLessThan(mlir::FloatAttr lhs, mlir::FloatAttr rhs) { + return lhs.getValueAsDouble() < rhs.getValueAsDouble(); +} + +static bool isLessThan(mlir::Attribute lhs, mlir::Attribute rhs) { + if (auto lhsInt = llvm::dyn_cast(lhs)) { + return isLessThan(lhsInt, llvm::cast(rhs)); + } else if (auto lhsFloat = llvm::dyn_cast(lhs)) { + return isLessThan(lhsFloat, llvm::cast(rhs)); + } else { + llvm_unreachable("unsupported type for less-than comparison"); + } +} + +mlir::LogicalResult ScalarEvaluator::evaluate(LtOp op) { + auto lhs = _values[op.getLhs()]; + auto rhs = _values[op.getRhs()]; + _values[op] = mlir::BoolAttr::get(op.getContext(), isLessThan(lhs, rhs)); + return mlir::success(); +} + +mlir::LogicalResult ScalarEvaluator::evaluate(LeOp op) { + auto lhs = _values[op.getLhs()]; + auto rhs = _values[op.getRhs()]; + _values[op] = + mlir::BoolAttr::get(op.getContext(), isLessThan(lhs, rhs) || lhs == rhs); + return mlir::success(); +} + mlir::LogicalResult ScalarEvaluator::evaluate(mlir::arith::DivFOp op) { auto lhs = llvm::cast(_values[op.getLhs()]).getValueAsDouble(); @@ -461,7 +495,7 @@ mlir::LogicalResult ScalarEvaluator::evaluate(mlir::Operation *op) { GA_CASE(ConstantOp) GA_CASE(mlir::arith::ConstantOp) GA_CASE(AddOp) GA_CASE(MulOp) GA_CASE(CastScalarOp) GA_CASE(EqOp) GA_CASE(mlir::arith::DivFOp) GA_CASE(mlir::arith::SubIOp) - GA_CASE(mlir::arith::SubFOp) + GA_CASE(mlir::arith::SubFOp) GA_CASE(LtOp) GA_CASE(LeOp) #undef GA_CASE .Default([](mlir::Operation *op) { return op->emitOpError("unsupported op"); diff --git a/compiler/test/canonicalize/compare.mlir b/compiler/test/canonicalize/compare.mlir new file mode 100644 index 0000000..b1695f9 --- /dev/null +++ b/compiler/test/canonicalize/compare.mlir @@ -0,0 +1,103 @@ +// RUN: graphalg-opt --canonicalize < %s | FileCheck %s + +// CHECK-LABEL: @LtSelf +func.func @LtSelf(%arg0: i64) -> i1 { + // CHECK: %[[#FALSE:]] = graphalg.const false + %0 = graphalg.lt %arg0, %arg0 : i64 + + // CHECK: return %[[#FALSE]] + return %0 : i1 +} + +// CHECK-LABEL: @LeSelf +func.func @LeSelf(%arg0: i64) -> i1 { + // CHECK: %[[#TRUE:]] = graphalg.const true + %0 = graphalg.le %arg0, %arg0 : i64 + + // CHECK: return %[[#TRUE]] + return %0 : i1 +} + +// CHECK-LABEL: @LtConstTrue +func.func @LtConstTrue() -> i1 { + // CHECK: %[[#TRUE:]] = graphalg.const true + %0 = graphalg.const 1 : i64 + %1 = graphalg.const 2 : i64 + %2 = graphalg.lt %0, %1 : i64 + + // CHECK: return %[[#TRUE]] + return %2 : i1 +} + +// CHECK-LABEL: @LtConstFalse +func.func @LtConstFalse() -> i1 { + // CHECK: %[[#FALSE:]] = graphalg.const false + %0 = graphalg.const 2 : i64 + %1 = graphalg.const 1 : i64 + %2 = graphalg.lt %0, %1 : i64 + + // CHECK: return %[[#FALSE]] + return %2 : i1 +} + +// CHECK-LABEL: @LeConstTrue +func.func @LeConstTrue() -> i1 { + // CHECK: %[[#TRUE:]] = graphalg.const true + %0 = graphalg.const 2 : i64 + %1 = graphalg.const 2 : i64 + %2 = graphalg.le %0, %1 : i64 + + // CHECK: return %[[#TRUE]] + return %2 : i1 +} + +// CHECK-LABEL: @LeConstFalse +func.func @LeConstFalse() -> i1 { + // CHECK: %[[#FALSE:]] = graphalg.const false + %0 = graphalg.const 3 : i64 + %1 = graphalg.const 2 : i64 + %2 = graphalg.le %0, %1 : i64 + + // CHECK: return %[[#FALSE]] + return %2 : i1 +} + +// CHECK-LABEL: @LtFloat +func.func @LtFloat() -> i1 { + // CHECK: %[[#FALSE:]] = graphalg.const false + %0 = graphalg.const 1.5 : f64 + %1 = graphalg.const 1.0 : f64 + %2 = graphalg.lt %0, %1 : f64 + + // CHECK: return %[[#FALSE]] + return %2 : i1 +} + +// CHECK-LABEL: @LeFloat +func.func @LeFloat() -> i1 { + // CHECK: %[[#TRUE:]] = graphalg.const true + %0 = graphalg.const 1.0 : f64 + %1 = graphalg.const 1.0 : f64 + %2 = graphalg.le %0, %1 : f64 + + // CHECK: return %[[#TRUE]] + return %2 : i1 +} + +// CHECK-LABEL: @LtNotFolded +func.func @LtNotFolded(%arg0: i64, %arg1: i64) -> i1 { + // CHECK: %[[#LT:]] = graphalg.lt %arg0, %arg1 : i64 + %0 = graphalg.lt %arg0, %arg1 : i64 + + // CHECK: return %[[#LT]] + return %0 : i1 +} + +// CHECK-LABEL: @LeNotFolded +func.func @LeNotFolded(%arg0: i64, %arg1: i64) -> i1 { + // CHECK: %[[#LE:]] = graphalg.le %arg0, %arg1 : i64 + %0 = graphalg.le %arg0, %arg1 : i64 + + // CHECK: return %[[#LE]] + return %0 : i1 +} diff --git a/compiler/test/exec/le.mlir b/compiler/test/exec/le.mlir new file mode 100644 index 0000000..0418bce --- /dev/null +++ b/compiler/test/exec/le.mlir @@ -0,0 +1,23 @@ +// RUN: split-file %s %t +// RUN: graphalg-exec %t/input.mlir Le %t/input.m | diff - %t/output-le.m + +//--- input.m +0 0 41 +1 0 42 +1 1 43 + +//--- input.mlir +func.func @Le(%arg0: !graphalg.mat<2 x 2 x i64>) -> !graphalg.mat<2 x 2 x i1> { + %0 = graphalg.apply %arg0 : !graphalg.mat<2 x 2 x i64> -> <2 x 2 x i1> { + ^bb0(%arg1: i64): + %1 = graphalg.const 42 : i64 + %2 = graphalg.le %arg1, %1 : i64 + graphalg.apply.return %2 : i1 + } + return %0 : !graphalg.mat<2 x 2 x i1> +} + +//--- output-le.m +0 0 true +0 1 true +1 0 true diff --git a/compiler/test/exec/lt.mlir b/compiler/test/exec/lt.mlir new file mode 100644 index 0000000..f3e90f0 --- /dev/null +++ b/compiler/test/exec/lt.mlir @@ -0,0 +1,22 @@ +// RUN: split-file %s %t +// RUN: graphalg-exec %t/input.mlir Lt %t/input.m | diff - %t/output-lt.m + +//--- input.m +0 0 41 +1 0 42 +1 1 43 + +//--- input.mlir +func.func @Lt(%arg0: !graphalg.mat<2 x 2 x i64>) -> !graphalg.mat<2 x 2 x i1> { + %0 = graphalg.apply %arg0 : !graphalg.mat<2 x 2 x i64> -> <2 x 2 x i1> { + ^bb0(%arg1: i64): + %1 = graphalg.const 42 : i64 + %2 = graphalg.lt %arg1, %1 : i64 + graphalg.apply.return %2 : i1 + } + return %0 : !graphalg.mat<2 x 2 x i1> +} + +//--- output-lt.m +0 0 true +0 1 true diff --git a/compiler/test/graphalg-to-core/compare.mlir b/compiler/test/graphalg-to-core/compare.mlir new file mode 100644 index 0000000..4ca16e9 --- /dev/null +++ b/compiler/test/graphalg-to-core/compare.mlir @@ -0,0 +1,49 @@ +// RUN: graphalg-opt --graphalg-to-core < %s | FileCheck %s +#dim = #graphalg.dim> + +!int = !graphalg.mat<#dim x #dim x i64> +!bool = !graphalg.mat<#dim x #dim x i1> + +// CHECK-LABEL: @lt +func.func @lt(%arg0: !int, %arg1: !int) -> !bool { + // CHECK: %[[#APPLY:]] = graphalg.apply %arg0, %arg1 + // CHECK: %[[#LT:]] = graphalg.lt %arg2, %arg3 : i64 + // CHECK: graphalg.apply.return %[[#LT]] + %0 = graphalg.ewise %arg0 LT %arg1 : !int + + // CHECK: return %[[#APPLY]] + return %0 : !bool +} + +// CHECK-LABEL: @gt +func.func @gt(%arg0: !int, %arg1: !int) -> !bool { + // CHECK: %[[#APPLY:]] = graphalg.apply %arg0, %arg1 + // CHECK: %[[#LT:]] = graphalg.lt %arg3, %arg2 : i64 + // CHECK: graphalg.apply.return %[[#LT]] + %0 = graphalg.ewise %arg0 GT %arg1 : !int + + // CHECK: return %[[#APPLY]] + return %0 : !bool +} + +// CHECK-LABEL: @le +func.func @le(%arg0: !int, %arg1: !int) -> !bool { + // CHECK: %[[#APPLY:]] = graphalg.apply %arg0, %arg1 + // CHECK: %[[#LE:]] = graphalg.le %arg2, %arg3 : i64 + // CHECK: graphalg.apply.return %[[#LE]] + %0 = graphalg.ewise %arg0 LE %arg1 : !int + + // CHECK: return %[[#APPLY]] + return %0 : !bool +} + +// CHECK-LABEL: @ge +func.func @ge(%arg0: !int, %arg1: !int) -> !bool { + // CHECK: %[[#APPLY:]] = graphalg.apply %arg0, %arg1 + // CHECK: %[[#LE:]] = graphalg.le %arg3, %arg2 : i64 + // CHECK: graphalg.apply.return %[[#LE]] + %0 = graphalg.ewise %arg0 GE %arg1 : !int + + // CHECK: return %[[#APPLY]] + return %0 : !bool +} diff --git a/compiler/test/scalarize-apply/compare.mlir b/compiler/test/scalarize-apply/compare.mlir new file mode 100644 index 0000000..0c4650e --- /dev/null +++ b/compiler/test/scalarize-apply/compare.mlir @@ -0,0 +1,63 @@ +// RUN: graphalg-opt --graphalg-scalarize-apply < %s | FileCheck %s + +#dim = #graphalg.dim> + +// CHECK-LABEL: func.func @Lt +func.func @Lt(%arg0: !graphalg.mat<#dim x #dim x i64>) -> !graphalg.mat<#dim x #dim x i1> { + // CHECK: %[[#APPLY:]] = graphalg.apply %arg0 + // CHECK: %[[#RES:]] = graphalg.lt %arg1, %arg1 : i64 + // CHECK: graphalg.apply.return %[[#RES]] + %0 = graphalg.apply_inline %arg0 : !graphalg.mat<#dim x #dim x i64> -> <#dim x #dim x i1> { + ^bb0(%arg1: !graphalg.mat<1 x 1 x i64>): + %1 = graphalg.ewise %arg1 LT %arg1 : <1 x 1 x i64> + graphalg.apply_inline.return %1 : <1 x 1 x i1> + } + + // CHECK: return %[[#APPLY]] + return %0 : !graphalg.mat<#dim x #dim x i1> +} + +// CHECK-LABEL: func.func @Gt +func.func @Gt(%arg0: !graphalg.mat<#dim x #dim x i64>) -> !graphalg.mat<#dim x #dim x i1> { + // CHECK: %[[#APPLY:]] = graphalg.apply %arg0 + // CHECK: %[[#RES:]] = graphalg.lt %arg1, %arg1 : i64 + // CHECK: graphalg.apply.return %[[#RES]] + %0 = graphalg.apply_inline %arg0 : !graphalg.mat<#dim x #dim x i64> -> <#dim x #dim x i1> { + ^bb0(%arg1: !graphalg.mat<1 x 1 x i64>): + %1 = graphalg.ewise %arg1 GT %arg1 : <1 x 1 x i64> + graphalg.apply_inline.return %1 : <1 x 1 x i1> + } + + // CHECK: return %[[#APPLY]] + return %0 : !graphalg.mat<#dim x #dim x i1> +} + +// CHECK-LABEL: func.func @Le +func.func @Le(%arg0: !graphalg.mat<#dim x #dim x i64>) -> !graphalg.mat<#dim x #dim x i1> { + // CHECK: %[[#APPLY:]] = graphalg.apply %arg0 + // CHECK: %[[#RES:]] = graphalg.le %arg1, %arg1 : i64 + // CHECK: graphalg.apply.return %[[#RES]] + %0 = graphalg.apply_inline %arg0 : !graphalg.mat<#dim x #dim x i64> -> <#dim x #dim x i1> { + ^bb0(%arg1: !graphalg.mat<1 x 1 x i64>): + %1 = graphalg.ewise %arg1 LE %arg1 : <1 x 1 x i64> + graphalg.apply_inline.return %1 : <1 x 1 x i1> + } + + // CHECK: return %[[#APPLY]] + return %0 : !graphalg.mat<#dim x #dim x i1> +} + +// CHECK-LABEL: func.func @Ge +func.func @Ge(%arg0: !graphalg.mat<#dim x #dim x i64>) -> !graphalg.mat<#dim x #dim x i1> { + // CHECK: %[[#APPLY:]] = graphalg.apply %arg0 + // CHECK: %[[#RES:]] = graphalg.le %arg1, %arg1 : i64 + // CHECK: graphalg.apply.return %[[#RES]] + %0 = graphalg.apply_inline %arg0 : !graphalg.mat<#dim x #dim x i64> -> <#dim x #dim x i1> { + ^bb0(%arg1: !graphalg.mat<1 x 1 x i64>): + %1 = graphalg.ewise %arg1 GE %arg1 : <1 x 1 x i64> + graphalg.apply_inline.return %1 : <1 x 1 x i1> + } + + // CHECK: return %[[#APPLY]] + return %0 : !graphalg.mat<#dim x #dim x i1> +} From a2b32a515486fc4f1bf52c1ffee501354dd56559 Mon Sep 17 00:00:00 2001 From: Daan de Graaf Date: Thu, 27 Aug 2026 21:14:10 +0000 Subject: [PATCH 2/3] Fixup less-than and add to rel conversion. --- compiler/src/garel/GraphAlgToRel.cpp | 41 ++++++++++++ .../src/graphalg/GraphAlgCanonicalize.cpp | 2 +- compiler/src/graphalg/evaluate/Evaluator.cpp | 2 +- compiler/test/graphalg-to-rel/lt-le.mlir | 65 +++++++++++++++++++ 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 compiler/test/graphalg-to-rel/lt-le.mlir diff --git a/compiler/src/garel/GraphAlgToRel.cpp b/compiler/src/garel/GraphAlgToRel.cpp index cc94417..f2fcc14 100644 --- a/compiler/src/garel/GraphAlgToRel.cpp +++ b/compiler/src/garel/GraphAlgToRel.cpp @@ -1339,6 +1339,46 @@ mlir::LogicalResult OpConversion::matchAndRewrite( return mlir::success(); } +template <> +mlir::LogicalResult OpConversion::matchAndRewrite( + graphalg::LtOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + auto lhs = adaptor.getLhs(); + auto rhs = adaptor.getRhs(); + if (lhs.getType().isF64()) { + assert(rhs.getType().isF64()); + rewriter.replaceOpWithNewOp( + op, mlir::arith::CmpFPredicate::OLT, lhs, rhs); + } else { + assert(lhs.getType().isSignlessInteger()); + assert(rhs.getType().isSignlessInteger()); + rewriter.replaceOpWithNewOp( + op, mlir::arith::CmpIPredicate::slt, lhs, rhs); + } + + return mlir::success(); +} + +template <> +mlir::LogicalResult OpConversion::matchAndRewrite( + graphalg::LeOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + auto lhs = adaptor.getLhs(); + auto rhs = adaptor.getRhs(); + if (lhs.getType().isF64()) { + assert(rhs.getType().isF64()); + rewriter.replaceOpWithNewOp( + op, mlir::arith::CmpFPredicate::OLE, lhs, rhs); + } else { + assert(lhs.getType().isSignlessInteger()); + assert(rhs.getType().isSignlessInteger()); + rewriter.replaceOpWithNewOp( + op, mlir::arith::CmpIPredicate::sle, lhs, rhs); + } + + return mlir::success(); +} + template <> mlir::LogicalResult OpConversion::matchAndRewrite( graphalg::MulOp op, OpAdaptor adaptor, @@ -1453,6 +1493,7 @@ void GraphAlgToRel::runOnOperation() { .add, OpConversion, OpConversion, OpConversion, OpConversion, + OpConversion, OpConversion, OpConversion>(semiringTypeConverter, &getContext()); if (mlir::failed(mlir::applyFullConversion(getOperation(), target, diff --git a/compiler/src/graphalg/GraphAlgCanonicalize.cpp b/compiler/src/graphalg/GraphAlgCanonicalize.cpp index 1ec08fb..129af8b 100644 --- a/compiler/src/graphalg/GraphAlgCanonicalize.cpp +++ b/compiler/src/graphalg/GraphAlgCanonicalize.cpp @@ -524,7 +524,7 @@ mlir::OpFoldResult EqOp::fold(FoldAdaptor adaptor) { } static bool isLessThan(mlir::IntegerAttr lhs, mlir::IntegerAttr rhs) { - return (lhs.getValue() - rhs.getValue()).isNegative(); + return lhs.getValue().slt(rhs.getValue()); } static bool isLessThan(mlir::FloatAttr lhs, mlir::FloatAttr rhs) { diff --git a/compiler/src/graphalg/evaluate/Evaluator.cpp b/compiler/src/graphalg/evaluate/Evaluator.cpp index e5a4b6d..4f9b993 100644 --- a/compiler/src/graphalg/evaluate/Evaluator.cpp +++ b/compiler/src/graphalg/evaluate/Evaluator.cpp @@ -429,7 +429,7 @@ mlir::LogicalResult ScalarEvaluator::evaluate(EqOp op) { } static bool isLessThan(mlir::IntegerAttr lhs, mlir::IntegerAttr rhs) { - return (lhs.getValue() - rhs.getValue()).isNegative(); + return lhs.getValue().slt(rhs.getValue()); } static bool isLessThan(mlir::FloatAttr lhs, mlir::FloatAttr rhs) { diff --git a/compiler/test/graphalg-to-rel/lt-le.mlir b/compiler/test/graphalg-to-rel/lt-le.mlir new file mode 100644 index 0000000..b0cf9a4 --- /dev/null +++ b/compiler/test/graphalg-to-rel/lt-le.mlir @@ -0,0 +1,65 @@ +// RUN: graphalg-opt --graphalg-to-rel < %s | FileCheck %s + +// CHECK-LABEL: @LtInt +func.func @LtInt(%arg0: !graphalg.mat<1 x 1 x i64>) -> !graphalg.mat<1 x 1 x i1> { + %0 = graphalg.apply %arg0 : !graphalg.mat<1 x 1 x i64> -> <1 x 1 x i1> { + ^bb0(%arg1 : i64): + // CHECK: %[[LHS:.+]] = garel.extract 0 + // CHECK: %[[RHS:.+]] = arith.constant 0 + %1 = graphalg.const 0 : i64 + // CHECK: %[[#CMP:]] = arith.cmpi slt, %[[LHS]], %[[RHS]] : i64 + %2 = graphalg.lt %arg1, %1 : i64 + // CHECK: garel.project.return %[[#CMP]] + graphalg.apply.return %2 : i1 + } + + return %0 : !graphalg.mat<1 x 1 x i1> +} + +// CHECK-LABEL: @LtReal +func.func @LtReal(%arg0: !graphalg.mat<1 x 1 x f64>) -> !graphalg.mat<1 x 1 x i1> { + %0 = graphalg.apply %arg0 : !graphalg.mat<1 x 1 x f64> -> <1 x 1 x i1> { + ^bb0(%arg1 : f64): + // CHECK: %[[LHS:.+]] = garel.extract 0 + // CHECK: %[[RHS:.+]] = arith.constant 0.000000e+00 + %1 = graphalg.const 0.000000e+00 : f64 + // CHECK: %[[#CMP:]] = arith.cmpf olt, %[[LHS]], %[[RHS]] : f64 + %2 = graphalg.lt %arg1, %1 : f64 + // CHECK: garel.project.return %[[#CMP]] + graphalg.apply.return %2 : i1 + } + + return %0 : !graphalg.mat<1 x 1 x i1> +} + +// CHECK-LABEL: @LeInt +func.func @LeInt(%arg0: !graphalg.mat<1 x 1 x i64>) -> !graphalg.mat<1 x 1 x i1> { + %0 = graphalg.apply %arg0 : !graphalg.mat<1 x 1 x i64> -> <1 x 1 x i1> { + ^bb0(%arg1 : i64): + // CHECK: %[[LHS:.+]] = garel.extract 0 + // CHECK: %[[RHS:.+]] = arith.constant 0 + %1 = graphalg.const 0 : i64 + // CHECK: %[[#CMP:]] = arith.cmpi sle, %[[LHS]], %[[RHS]] : i64 + %2 = graphalg.le %arg1, %1 : i64 + // CHECK: garel.project.return %[[#CMP]] + graphalg.apply.return %2 : i1 + } + + return %0 : !graphalg.mat<1 x 1 x i1> +} + +// CHECK-LABEL: @LeReal +func.func @LeReal(%arg0: !graphalg.mat<1 x 1 x f64>) -> !graphalg.mat<1 x 1 x i1> { + %0 = graphalg.apply %arg0 : !graphalg.mat<1 x 1 x f64> -> <1 x 1 x i1> { + ^bb0(%arg1 : f64): + // CHECK: %[[LHS:.+]] = garel.extract 0 + // CHECK: %[[RHS:.+]] = arith.constant 0.000000e+00 + %1 = graphalg.const 0.000000e+00 : f64 + // CHECK: %[[#CMP:]] = arith.cmpf ole, %[[LHS]], %[[RHS]] : f64 + %2 = graphalg.le %arg1, %1 : f64 + // CHECK: garel.project.return %[[#CMP]] + graphalg.apply.return %2 : i1 + } + + return %0 : !graphalg.mat<1 x 1 x i1> +} From c3be0fb619e41dd5e44c1e013d5c111c2623b5e0 Mon Sep 17 00:00:00 2001 From: Daan de Graaf Date: Thu, 27 Aug 2026 21:18:54 +0000 Subject: [PATCH 3/3] Fold lt/le. --- compiler/include/graphalg/GraphAlgOps.td | 2 + .../src/graphalg/GraphAlgCanonicalize.cpp | 39 +++++++++++++++++++ compiler/test/canonicalize/compare.mlir | 22 +++++++++++ 3 files changed, 63 insertions(+) diff --git a/compiler/include/graphalg/GraphAlgOps.td b/compiler/include/graphalg/GraphAlgOps.td index 104d834..ec9b954 100644 --- a/compiler/include/graphalg/GraphAlgOps.td +++ b/compiler/include/graphalg/GraphAlgOps.td @@ -740,6 +740,8 @@ def EqOp : CompareOp<"eq"> { // Arguments do not need to be partially ordered let arguments = (ins AnySemiring:$lhs, AnySemiring:$rhs); + + let hasCanonicalizer = 1; } def LtOp : CompareOp<"lt"> { diff --git a/compiler/src/graphalg/GraphAlgCanonicalize.cpp b/compiler/src/graphalg/GraphAlgCanonicalize.cpp index 129af8b..41637fd 100644 --- a/compiler/src/graphalg/GraphAlgCanonicalize.cpp +++ b/compiler/src/graphalg/GraphAlgCanonicalize.cpp @@ -570,6 +570,45 @@ mlir::OpFoldResult LeOp::fold(FoldAdaptor adaptor) { return nullptr; } +// Fold the negation of a compare op. +// +// !(a < b) => b <= a +// !(a <= b) => b < a +// +// The negation of the comparison is expressed as `eq false, (cmp a b)`. +static mlir::LogicalResult negateCompare(EqOp op, + mlir::PatternRewriter &rewriter) { + // One operand must be the boolean constant `false`. + mlir::Value cmp; + if (isFalse(op.getLhs())) { + cmp = op.getRhs(); + } else if (isFalse(op.getRhs())) { + cmp = op.getLhs(); + } else { + return mlir::failure(); + } + + // The other operand must be a less-than / less-than-or-equal comparison. + if (auto ltOp = cmp.getDefiningOp()) { + // !(a < b) => b <= a + rewriter.replaceOpWithNewOp(op, ltOp.getRhs(), ltOp.getLhs()); + return mlir::success(); + } + + if (auto leOp = cmp.getDefiningOp()) { + // !(a <= b) => b < a + rewriter.replaceOpWithNewOp(op, leOp.getRhs(), leOp.getLhs()); + return mlir::success(); + } + + return mlir::failure(); +} + +void EqOp::getCanonicalizationPatterns(mlir::RewritePatternSet &patterns, + mlir::MLIRContext *context) { + patterns.add(negateCompare); +} + // Test if the value is equal to the additive identity. static bool isAdditiveIdentity(mlir::Value v) { mlir::Attribute constantValue; diff --git a/compiler/test/canonicalize/compare.mlir b/compiler/test/canonicalize/compare.mlir index b1695f9..2e3258e 100644 --- a/compiler/test/canonicalize/compare.mlir +++ b/compiler/test/canonicalize/compare.mlir @@ -101,3 +101,25 @@ func.func @LeNotFolded(%arg0: i64, %arg1: i64) -> i1 { // CHECK: return %[[#LE]] return %0 : i1 } + +// CHECK-LABEL: @NotLt +func.func @NotLt(%arg0: i64, %arg1: i64) -> i1 { + // CHECK: %[[#LE:]] = graphalg.le %arg1, %arg0 : i64 + %0 = graphalg.lt %arg0, %arg1 : i64 + %1 = graphalg.const false + %2 = graphalg.eq %1, %0 : i1 + + // CHECK: return %[[#LE]] + return %2 : i1 +} + +// CHECK-LABEL: @NotLe +func.func @NotLe(%arg0: i64, %arg1: i64) -> i1 { + // CHECK: %[[#LT:]] = graphalg.lt %arg1, %arg0 : i64 + %0 = graphalg.le %arg0, %arg1 : i64 + %1 = graphalg.const false + %2 = graphalg.eq %0, %1 : i1 + + // CHECK: return %[[#LT]] + return %2 : i1 +}