From 9f4a7ab99166e60a853132fe12bbf74f6f29782a Mon Sep 17 00:00:00 2001 From: Jeremy Kun Date: Tue, 19 Sep 2023 10:45:21 -0700 Subject: [PATCH 1/8] slight formatting changes --- lib/Dialect/Poly/PolyOps.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/Dialect/Poly/PolyOps.cpp b/lib/Dialect/Poly/PolyOps.cpp index b92c58a..d94bcdd 100644 --- a/lib/Dialect/Poly/PolyOps.cpp +++ b/lib/Dialect/Poly/PolyOps.cpp @@ -1,7 +1,6 @@ #include "lib/Dialect/Poly/PolyOps.h" #include "mlir/Dialect/CommonFolders.h" -#include "llvm/Support/Debug.h" namespace mlir { namespace tutorial { @@ -25,8 +24,7 @@ OpFoldResult MulOp::fold(MulOp::FoldAdaptor adaptor) { auto lhs = dyn_cast(adaptor.getOperands()[0]); auto rhs = dyn_cast(adaptor.getOperands()[1]); - if (!lhs || !rhs) - return nullptr; + if (!lhs || !rhs) return nullptr; auto degree = getResult().getType().cast().getDegreeBound(); auto maxIndex = lhs.size() + rhs.size() - 1; @@ -43,7 +41,8 @@ OpFoldResult MulOp::fold(MulOp::FoldAdaptor adaptor) { int j = 0; for (auto rhsIt = rhs.value_begin(); rhsIt != rhs.value_end(); ++rhsIt) { - // index is modulo degree because poly's semantics are defined modulo x^N = 1. + // index is modulo degree because poly's semantics are defined modulo x^N + // = 1. result[(i + j) % degree] += *rhsIt * (*lhsIt); ++j; } @@ -67,6 +66,6 @@ LogicalResult EvalOp::verify() { : emitOpError("argument point must be a 32-bit integer"); } -} // namespace poly -} // namespace tutorial -} // namespace mlir +} // namespace poly +} // namespace tutorial +} // namespace mlir From 8fb25f5815718e82bb3a61ff3bdff58301805cea Mon Sep 17 00:00:00 2001 From: Jeremy Kun Date: Tue, 19 Sep 2023 10:47:10 -0700 Subject: [PATCH 2/8] Add empty canonicalizers to poly binops --- lib/Dialect/Poly/PolyOps.cpp | 9 +++++++++ lib/Dialect/Poly/PolyOps.td | 1 + 2 files changed, 10 insertions(+) diff --git a/lib/Dialect/Poly/PolyOps.cpp b/lib/Dialect/Poly/PolyOps.cpp index d94bcdd..482c249 100644 --- a/lib/Dialect/Poly/PolyOps.cpp +++ b/lib/Dialect/Poly/PolyOps.cpp @@ -66,6 +66,15 @@ LogicalResult EvalOp::verify() { : emitOpError("argument point must be a 32-bit integer"); } +void AddOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, + ::mlir::MLIRContext *context) {} + +void SubOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, + ::mlir::MLIRContext *context) {} + +void MulOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, + ::mlir::MLIRContext *context) {} + } // namespace poly } // namespace tutorial } // namespace mlir diff --git a/lib/Dialect/Poly/PolyOps.td b/lib/Dialect/Poly/PolyOps.td index 520ab09..10d7331 100644 --- a/lib/Dialect/Poly/PolyOps.td +++ b/lib/Dialect/Poly/PolyOps.td @@ -22,6 +22,7 @@ class Poly_BinOp : Op { From f556f6e658e431ad38eb39c1882f96eb393cc950 Mon Sep 17 00:00:00 2001 From: Jeremy Kun Date: Tue, 19 Sep 2023 13:08:36 -0700 Subject: [PATCH 3/8] dyn_cast -> dyn_cast_or_null --- lib/Dialect/Poly/PolyOps.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Dialect/Poly/PolyOps.cpp b/lib/Dialect/Poly/PolyOps.cpp index 482c249..e7c0ff3 100644 --- a/lib/Dialect/Poly/PolyOps.cpp +++ b/lib/Dialect/Poly/PolyOps.cpp @@ -21,8 +21,8 @@ OpFoldResult SubOp::fold(SubOp::FoldAdaptor adaptor) { } OpFoldResult MulOp::fold(MulOp::FoldAdaptor adaptor) { - auto lhs = dyn_cast(adaptor.getOperands()[0]); - auto rhs = dyn_cast(adaptor.getOperands()[1]); + auto lhs = dyn_cast_or_null(adaptor.getOperands()[0]); + auto rhs = dyn_cast_or_null(adaptor.getOperands()[1]); if (!lhs || !rhs) return nullptr; @@ -57,7 +57,7 @@ OpFoldResult MulOp::fold(MulOp::FoldAdaptor adaptor) { OpFoldResult FromTensorOp::fold(FromTensorOp::FoldAdaptor adaptor) { // Returns null if the cast failed, which corresponds to a failed fold. - return dyn_cast(adaptor.getInput()); + return dyn_cast_or_null(adaptor.getInput()); } LogicalResult EvalOp::verify() { From 7b9f2829ac16084bd3a05ec4aef9e24622deee1d Mon Sep 17 00:00:00 2001 From: Jeremy Kun Date: Tue, 19 Sep 2023 13:09:25 -0700 Subject: [PATCH 4/8] Canonicalize a difference of squares --- lib/Dialect/Poly/PolyOps.cpp | 49 +++++++++++++++++++++++++++++++++++- tests/poly_canonicalize.mlir | 32 +++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/lib/Dialect/Poly/PolyOps.cpp b/lib/Dialect/Poly/PolyOps.cpp index e7c0ff3..a54a9e6 100644 --- a/lib/Dialect/Poly/PolyOps.cpp +++ b/lib/Dialect/Poly/PolyOps.cpp @@ -1,6 +1,7 @@ #include "lib/Dialect/Poly/PolyOps.h" #include "mlir/Dialect/CommonFolders.h" +#include "mlir/IR/PatternMatch.h" namespace mlir { namespace tutorial { @@ -66,11 +67,57 @@ LogicalResult EvalOp::verify() { : emitOpError("argument point must be a 32-bit integer"); } +// Rewrites (x^2 - y^2) as (x+y)(x-y) if x^2 and y^2 have no other uses. +struct DifferenceOfSquares : public OpRewritePattern { + DifferenceOfSquares(mlir::MLIRContext *context) + : OpRewritePattern(context, /*benefit=*/1) {} + + LogicalResult matchAndRewrite(SubOp op, + PatternRewriter &rewriter) const override { + Value lhs = op.getOperand(0); + Value rhs = op.getOperand(1); + + // If either arg has another use, then this rewrite is probably less + // efficient, because it cannot delete the mul ops. + if (!lhs.hasOneUse() || !rhs.hasOneUse()) { + return failure(); + } + + auto rhsMul = rhs.getDefiningOp(); + auto lhsMul = lhs.getDefiningOp(); + if (!rhsMul || !lhsMul) { + return failure(); + } + + bool rhsMulOpsAgree = rhsMul.getLhs() == rhsMul.getRhs(); + bool lhsMulOpsAgree = lhsMul.getLhs() == lhsMul.getRhs(); + + if (!rhsMulOpsAgree || !lhsMulOpsAgree) { + return failure(); + } + + auto x = lhsMul.getLhs(); + auto y = rhsMul.getLhs(); + + AddOp newAdd = rewriter.create(op.getLoc(), x, y); + SubOp newSub = rewriter.create(op.getLoc(), x, y); + MulOp newMul = rewriter.create(op.getLoc(), newAdd, newSub); + + rewriter.replaceOp(op, {newMul}); + // We don't need to remove the original ops because MLIR already has + // canonicalization patterns that remove unused ops. + + return success(); + } +}; + void AddOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, ::mlir::MLIRContext *context) {} void SubOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, - ::mlir::MLIRContext *context) {} + ::mlir::MLIRContext *context) { + results.add(context); +} void MulOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, ::mlir::MLIRContext *context) {} diff --git a/tests/poly_canonicalize.mlir b/tests/poly_canonicalize.mlir index 0b219ab..77885bc 100644 --- a/tests/poly_canonicalize.mlir +++ b/tests/poly_canonicalize.mlir @@ -11,3 +11,35 @@ func.func @test_simple() -> !poly.poly<10> { %4 = poly.add %2, %3 : !poly.poly<10> return %2 : !poly.poly<10> } + +// CHECK-LABEL: func.func @test_difference_of_squares +// CHECK-SAME: %[[x:.+]]: !poly.poly<3>, +// CHECK-SAME: %[[y:.+]]: !poly.poly<3> +func.func @test_difference_of_squares( + %0: !poly.poly<3>, %1: !poly.poly<3>) -> !poly.poly<3> { + // CHECK: %[[sum:.+]] = poly.add %[[x]], %[[y]] + // CHECK: %[[diff:.+]] = poly.sub %[[x]], %[[y]] + // CHECK: %[[mul:.+]] = poly.mul %[[sum]], %[[diff]] + %2 = poly.mul %0, %0 : !poly.poly<3> + %3 = poly.mul %1, %1 : !poly.poly<3> + %4 = poly.sub %2, %3 : !poly.poly<3> + %5 = poly.add %4, %4 : !poly.poly<3> + return %5 : !poly.poly<3> +} + +// CHECK-LABEL: func.func @test_difference_of_squares_other_uses +// CHECK-SAME: %[[x:.+]]: !poly.poly<3>, +// CHECK-SAME: %[[y:.+]]: !poly.poly<3> +func.func @test_difference_of_squares_other_uses( + %0: !poly.poly<3>, %1: !poly.poly<3>) -> !poly.poly<3> { + // The canonicalization does not occur because x_squared has a second use. + // CHECK: %[[x_squared:.+]] = poly.mul %[[x]], %[[x]] + // CHECK: %[[y_squared:.+]] = poly.mul %[[y]], %[[y]] + // CHECK: %[[diff:.+]] = poly.sub %[[x_squared]], %[[y_squared]] + // CHECK: %[[sum:.+]] = poly.add %[[diff]], %[[x_squared]] + %2 = poly.mul %0, %0 : !poly.poly<3> + %3 = poly.mul %1, %1 : !poly.poly<3> + %4 = poly.sub %2, %3 : !poly.poly<3> + %5 = poly.add %4, %2 : !poly.poly<3> + return %5 : !poly.poly<3> +} From 2235f84a7dbcbc78b152febb134f028f2b72f585 Mon Sep 17 00:00:00 2001 From: Jeremy Kun Date: Tue, 19 Sep 2023 13:30:02 -0700 Subject: [PATCH 5/8] upgrade eval to accept complex inputs --- lib/Dialect/Poly/PolyOps.cpp | 10 +++++++--- lib/Dialect/Poly/PolyOps.td | 6 ++++-- tests/poly_syntax.mlir | 4 ++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/Dialect/Poly/PolyOps.cpp b/lib/Dialect/Poly/PolyOps.cpp index a54a9e6..0681a06 100644 --- a/lib/Dialect/Poly/PolyOps.cpp +++ b/lib/Dialect/Poly/PolyOps.cpp @@ -62,9 +62,13 @@ OpFoldResult FromTensorOp::fold(FromTensorOp::FoldAdaptor adaptor) { } LogicalResult EvalOp::verify() { - return getPoint().getType().isSignlessInteger(32) - ? success() - : emitOpError("argument point must be a 32-bit integer"); + auto pointTy = getPoint().getType(); + bool isSignlessInteger = pointTy.isSignlessInteger(32); + auto complexPt = llvm::dyn_cast(pointTy); + return isSignlessInteger || complexPt ? success() + : emitOpError( + "argument point must be a 32-bit " + "integer, or a complex number"); } // Rewrites (x^2 - y^2) as (x+y)(x-y) if x^2 and y^2 have no other uses. diff --git a/lib/Dialect/Poly/PolyOps.td b/lib/Dialect/Poly/PolyOps.td index 10d7331..b392bb1 100644 --- a/lib/Dialect/Poly/PolyOps.td +++ b/lib/Dialect/Poly/PolyOps.td @@ -45,10 +45,12 @@ def Poly_FromTensorOp : Op { let hasFolder = 1; } +def IntOrComplex : AnyTypeOf<[AnyInteger, AnyComplex]>; + def Poly_EvalOp : Op, Has32BitArguments]> { let summary = "Evaluates a Polynomial at a given input value."; - let arguments = (ins Polynomial:$input, AnyInteger:$point); - let results = (outs AnyInteger:$output); + let arguments = (ins Polynomial:$input, IntOrComplex:$point); + let results = (outs IntOrComplex:$output); let assemblyFormat = "$input `,` $point attr-dict `:` `(` qualified(type($input)) `,` type($point) `)` `->` type($output)"; let hasVerifier = 1; } diff --git a/tests/poly_syntax.mlir b/tests/poly_syntax.mlir index 8ab6db8..f3c7f5e 100644 --- a/tests/poly_syntax.mlir +++ b/tests/poly_syntax.mlir @@ -25,6 +25,10 @@ module { // CHECK: poly.eval %6 = poly.eval %4, %5 : (!poly.poly<10>, i32) -> i32 + %z = complex.constant [1.0, 2.0] : complex + // CHECK: poly.eval + %complex_eval = poly.eval %4, %z : (!poly.poly<10>, complex) -> complex + %7 = tensor.from_elements %arg0, %arg1 : tensor<2x!poly.poly<10>> // CHECK: poly.add %8 = poly.add %7, %7 : tensor<2x!poly.poly<10>> From e55bab91becc9f6c7c93e525a0c81f432c11e4e5 Mon Sep 17 00:00:00 2001 From: Jeremy Kun Date: Tue, 19 Sep 2023 14:30:55 -0700 Subject: [PATCH 6/8] Add tablegen pattern to lift conj through eval --- lib/Dialect/Poly/BUILD | 19 ++++++++++++++++++- lib/Dialect/Poly/PolyPatterns.td | 13 +++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 lib/Dialect/Poly/PolyPatterns.td diff --git a/lib/Dialect/Poly/BUILD b/lib/Dialect/Poly/BUILD index 9dfb6d4..20a5ce5 100644 --- a/lib/Dialect/Poly/BUILD +++ b/lib/Dialect/Poly/BUILD @@ -9,9 +9,9 @@ td_library( srcs = [ "PolyDialect.td", "PolyOps.td", + "PolyPatterns.td", "PolyTypes.td", ], - includes = ["@heir//include"], deps = [ "@llvm-project//mlir:BuiltinDialectTdFiles", "@llvm-project//mlir:InferTypeOpInterfaceTdFiles", @@ -80,6 +80,23 @@ gentbl_cc_library( ], ) +gentbl_cc_library( + name = "canonicalize_inc_gen", + tbl_outs = [ + ( + ["-gen-rewriters"], + "PolyCanonicalize.cpp.inc", + ), + ], + tblgen = "@llvm-project//mlir:mlir-tblgen", + td_file = "PolyPatterns.td", + deps = [ + ":td_files", + ":types_inc_gen", + "@llvm-project//mlir:ComplexOpsTdFiles", + ], +) + cc_library( name = "Poly", srcs = [ diff --git a/lib/Dialect/Poly/PolyPatterns.td b/lib/Dialect/Poly/PolyPatterns.td new file mode 100644 index 0000000..37cf5d0 --- /dev/null +++ b/lib/Dialect/Poly/PolyPatterns.td @@ -0,0 +1,13 @@ +#ifndef LIB_DIALECT_POLY_POLYPATTERNS_TD_ +#define LIB_DIALECT_POLY_POLYPATTERNS_TD_ + +include "PolyOps.td" +include "mlir/Dialect/Complex/IR/ComplexOps.td" +include "mlir/IR/PatternBase.td" + +def LiftConjThroughEval : Pat< + (Poly_EvalOp $f, (ConjOp $z)), + (ConjOp (Poly_EvalOp $f, $z)) +>; + +#endif // LIB_DIALECT_POLY_POLYPATTERNS_TD_ From 69a18e9a1fa5f256d5edf3534f307115f3174a07 Mon Sep 17 00:00:00 2001 From: Jeremy Kun Date: Tue, 19 Sep 2023 14:56:30 -0700 Subject: [PATCH 7/8] add generated patterns to eval canonicalizer --- lib/Dialect/Poly/BUILD | 2 ++ lib/Dialect/Poly/PolyOps.cpp | 9 +++++++++ lib/Dialect/Poly/PolyOps.td | 1 + tests/poly_canonicalize.mlir | 13 +++++++++++++ 4 files changed, 25 insertions(+) diff --git a/lib/Dialect/Poly/BUILD b/lib/Dialect/Poly/BUILD index 20a5ce5..5a4d699 100644 --- a/lib/Dialect/Poly/BUILD +++ b/lib/Dialect/Poly/BUILD @@ -110,9 +110,11 @@ cc_library( "PolyTypes.h", ], deps = [ + ":canonicalize_inc_gen", ":dialect_inc_gen", ":ops_inc_gen", ":types_inc_gen", + "@llvm-project//mlir:ComplexDialect", "@llvm-project//mlir:Dialect", "@llvm-project//mlir:IR", "@llvm-project//mlir:InferTypeOpInterface", diff --git a/lib/Dialect/Poly/PolyOps.cpp b/lib/Dialect/Poly/PolyOps.cpp index 0681a06..bc813a6 100644 --- a/lib/Dialect/Poly/PolyOps.cpp +++ b/lib/Dialect/Poly/PolyOps.cpp @@ -1,8 +1,12 @@ #include "lib/Dialect/Poly/PolyOps.h" #include "mlir/Dialect/CommonFolders.h" +#include "mlir/Dialect/Complex/IR/Complex.h" #include "mlir/IR/PatternMatch.h" +// Required after PatternMatch.h +#include "lib/Dialect/Poly/PolyCanonicalize.cpp.inc" + namespace mlir { namespace tutorial { namespace poly { @@ -126,6 +130,11 @@ void SubOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, void MulOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, ::mlir::MLIRContext *context) {} +void EvalOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, + ::mlir::MLIRContext *context) { + populateWithGenerated(results); +} + } // namespace poly } // namespace tutorial } // namespace mlir diff --git a/lib/Dialect/Poly/PolyOps.td b/lib/Dialect/Poly/PolyOps.td index b392bb1..2106bb9 100644 --- a/lib/Dialect/Poly/PolyOps.td +++ b/lib/Dialect/Poly/PolyOps.td @@ -53,6 +53,7 @@ def Poly_EvalOp : Op, let results = (outs IntOrComplex:$output); let assemblyFormat = "$input `,` $point attr-dict `:` `(` qualified(type($input)) `,` type($point) `)` `->` type($output)"; let hasVerifier = 1; + let hasCanonicalizer = 1; } def Poly_ConstantOp : Op { diff --git a/tests/poly_canonicalize.mlir b/tests/poly_canonicalize.mlir index 77885bc..a1c4cdf 100644 --- a/tests/poly_canonicalize.mlir +++ b/tests/poly_canonicalize.mlir @@ -43,3 +43,16 @@ func.func @test_difference_of_squares_other_uses( %5 = poly.add %4, %2 : !poly.poly<3> return %5 : !poly.poly<3> } + +// CHECK-LABEL: func.func @test_normalize_conj_through_eval +// CHECK-SAME: %[[f:.+]]: !poly.poly<3>, +// CHECK-SAME: %[[z:.+]]: complex +func.func @test_normalize_conj_through_eval( + %f: !poly.poly<3>, %z: complex) -> complex { + // CHECK: %[[evaled:.+]] = poly.eval %[[f]], %[[z]] + // CHECK-NEXT: %[[eval_bar:.+]] = complex.conj %[[evaled]] + // CHECK-NEXT: return %[[eval_bar]] + %z_bar = complex.conj %z : complex + %evaled = poly.eval %f, %z_bar : (!poly.poly<3>, complex) -> complex + return %evaled : complex +} From 6dea94a522d6471ded1645bb3e438b85272c6565 Mon Sep 17 00:00:00 2001 From: Jeremy Kun Date: Tue, 19 Sep 2023 16:06:30 -0700 Subject: [PATCH 8/8] rewrite DifferenceOfSquares in tablegen --- lib/Dialect/Poly/PolyOps.cpp | 46 +------------------------------- lib/Dialect/Poly/PolyPatterns.td | 13 +++++++++ 2 files changed, 14 insertions(+), 45 deletions(-) diff --git a/lib/Dialect/Poly/PolyOps.cpp b/lib/Dialect/Poly/PolyOps.cpp index bc813a6..473f2d4 100644 --- a/lib/Dialect/Poly/PolyOps.cpp +++ b/lib/Dialect/Poly/PolyOps.cpp @@ -75,50 +75,6 @@ LogicalResult EvalOp::verify() { "integer, or a complex number"); } -// Rewrites (x^2 - y^2) as (x+y)(x-y) if x^2 and y^2 have no other uses. -struct DifferenceOfSquares : public OpRewritePattern { - DifferenceOfSquares(mlir::MLIRContext *context) - : OpRewritePattern(context, /*benefit=*/1) {} - - LogicalResult matchAndRewrite(SubOp op, - PatternRewriter &rewriter) const override { - Value lhs = op.getOperand(0); - Value rhs = op.getOperand(1); - - // If either arg has another use, then this rewrite is probably less - // efficient, because it cannot delete the mul ops. - if (!lhs.hasOneUse() || !rhs.hasOneUse()) { - return failure(); - } - - auto rhsMul = rhs.getDefiningOp(); - auto lhsMul = lhs.getDefiningOp(); - if (!rhsMul || !lhsMul) { - return failure(); - } - - bool rhsMulOpsAgree = rhsMul.getLhs() == rhsMul.getRhs(); - bool lhsMulOpsAgree = lhsMul.getLhs() == lhsMul.getRhs(); - - if (!rhsMulOpsAgree || !lhsMulOpsAgree) { - return failure(); - } - - auto x = lhsMul.getLhs(); - auto y = rhsMul.getLhs(); - - AddOp newAdd = rewriter.create(op.getLoc(), x, y); - SubOp newSub = rewriter.create(op.getLoc(), x, y); - MulOp newMul = rewriter.create(op.getLoc(), newAdd, newSub); - - rewriter.replaceOp(op, {newMul}); - // We don't need to remove the original ops because MLIR already has - // canonicalization patterns that remove unused ops. - - return success(); - } -}; - void AddOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, ::mlir::MLIRContext *context) {} @@ -132,7 +88,7 @@ void MulOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, void EvalOp::getCanonicalizationPatterns(::mlir::RewritePatternSet &results, ::mlir::MLIRContext *context) { - populateWithGenerated(results); + results.add(context); } } // namespace poly diff --git a/lib/Dialect/Poly/PolyPatterns.td b/lib/Dialect/Poly/PolyPatterns.td index 37cf5d0..442a1ba 100644 --- a/lib/Dialect/Poly/PolyPatterns.td +++ b/lib/Dialect/Poly/PolyPatterns.td @@ -10,4 +10,17 @@ def LiftConjThroughEval : Pat< (ConjOp (Poly_EvalOp $f, $z)) >; +def HasOneUse: Constraint, "has one use">; + +// Rewrites (x^2 - y^2) as (x+y)(x-y) if x^2 and y^2 have no other uses. +def DifferenceOfSquares : Pattern< + (Poly_SubOp (Poly_MulOp:$lhs $x, $x), (Poly_MulOp:$rhs $y, $y)), + [ + (Poly_AddOp:$sum $x, $y), + (Poly_SubOp:$diff $x, $y), + (Poly_MulOp:$res $sum, $diff), + ], + [(HasOneUse:$lhs), (HasOneUse:$rhs)] +>; + #endif // LIB_DIALECT_POLY_POLYPATTERNS_TD_