-
Notifications
You must be signed in to change notification settings - Fork 381
Add a few mux-of-const and reg-of-and/or canonicalizers. #8307
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 all commits
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 | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -1472,12 +1472,12 @@ class MuxSharedCond : public mlir::RewritePattern { | |||||||||
|
||||||||||
void MuxPrimOp::getCanonicalizationPatterns(RewritePatternSet &results, | ||||||||||
MLIRContext *context) { | ||||||||||
results | ||||||||||
.add<MuxPad, MuxSharedCond, patterns::MuxEQOperands, | ||||||||||
patterns::MuxEQOperandsSwapped, patterns::MuxNEQ, patterns::MuxNot, | ||||||||||
patterns::MuxSameTrue, patterns::MuxSameFalse, | ||||||||||
patterns::NarrowMuxLHS, patterns::NarrowMuxRHS, patterns::MuxPadSel>( | ||||||||||
context); | ||||||||||
results.add<MuxPad, MuxSharedCond, patterns::MuxEQOperands, | ||||||||||
patterns::MuxEQOperandsSwapped, patterns::MuxNEQ, | ||||||||||
patterns::MuxNot, patterns::MuxSameTrue, patterns::MuxSameFalse, | ||||||||||
patterns::NarrowMuxLHS, patterns::NarrowMuxRHS, | ||||||||||
patterns::MuxPadSel, patterns::MuxLhsZero, patterns::MuxLhsOne, | ||||||||||
patterns::MuxRhsZero, patterns::MuxRhsOne>(context); | ||||||||||
} | ||||||||||
|
||||||||||
void Mux2CellIntrinsicOp::getCanonicalizationPatterns( | ||||||||||
|
@@ -2256,6 +2256,77 @@ struct FoldResetMux : public mlir::RewritePattern { | |||||||||
}; | ||||||||||
} // namespace | ||||||||||
|
||||||||||
namespace { | ||||||||||
/// This canonicalizer provides the following patterns: | ||||||||||
/// reset(reg) = 0 ==> connect(reg, and(reg, x)) ==> reg -> 0 | ||||||||||
/// reset(reg) = 0 ==> connect(reg, and(x, reg)) ==> reg -> 0 | ||||||||||
/// reset(reg) = 1 ==> connect(reg, or(reg, x)) ==> reg -> 1 | ||||||||||
/// reset(reg) = 1 ==> connect(reg, or(x, reg)) ==> reg -> 1 | ||||||||||
/// | ||||||||||
/// Justification: The initial value of a register is indeterminant, which means | ||||||||||
/// we are free to choose any initial value when optimizing the circuit. For the | ||||||||||
/// AND patterns, if the reset is zero, and we assume the initial value is zero, | ||||||||||
/// then the register will always be zero. For the OR patterns, if the reset is | ||||||||||
/// one, and we assume the initial value is one, then the register will always | ||||||||||
/// be one. | ||||||||||
struct RegResetAndOrOfSelf : public mlir::OpRewritePattern<RegResetOp> { | ||||||||||
using OpRewritePattern::OpRewritePattern; | ||||||||||
LogicalResult matchAndRewrite(RegResetOp op, | ||||||||||
PatternRewriter &rewriter) const override { | ||||||||||
Comment on lines
+2274
to
+2275
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. Supreme nit:
Suggested change
When reading the later rewrite pattern, I found it clearer to use the specific "reg" instead of the generic "op". |
||||||||||
// Do not fold the register away if it is important. | ||||||||||
if (hasDontTouch(op.getOperation()) || !AnnotationSet(op).empty() || | ||||||||||
op.isForceable()) | ||||||||||
return failure(); | ||||||||||
|
||||||||||
// This canonicalization only applies when the register holds 1 bit. | ||||||||||
auto type = dyn_cast<UIntType>(op.getResult().getType()); | ||||||||||
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. I think |
||||||||||
if (!type || type.getWidthOrSentinel() != 1) | ||||||||||
return failure(); | ||||||||||
|
||||||||||
// This canonicalization only applies when the reset is a constant. | ||||||||||
auto reset = | ||||||||||
dyn_cast_or_null<ConstantOp>(op.getResetValue().getDefiningOp()); | ||||||||||
if (!reset) | ||||||||||
return failure(); | ||||||||||
Comment on lines
+2286
to
+2290
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. Nit: please be exact. This is the reset value and not the reset (or exactly, the reset signal). |
||||||||||
|
||||||||||
auto value = reset.getValue(); | ||||||||||
|
||||||||||
// Find the one true connect, or bail. | ||||||||||
auto connect = getSingleConnectUserOf(op.getResult()); | ||||||||||
if (!connect) | ||||||||||
return failure(); | ||||||||||
|
||||||||||
auto *src = connect.getSrc().getDefiningOp(); | ||||||||||
if (!src) | ||||||||||
return failure(); | ||||||||||
|
||||||||||
if (value == 0) { | ||||||||||
if (auto srcAnd = dyn_cast<AndPrimOp>(src)) { | ||||||||||
if (srcAnd.getLhs().getDefiningOp() == op || | ||||||||||
srcAnd.getRhs().getDefiningOp() == op) { | ||||||||||
rewriter.eraseOp(connect); | ||||||||||
replaceOpAndCopyName(rewriter, op, reset.getResult()); | ||||||||||
return success(); | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
if (value == 1) { | ||||||||||
if (auto srcOr = dyn_cast<OrPrimOp>(src)) { | ||||||||||
if (srcOr.getLhs().getDefiningOp() == op || | ||||||||||
srcOr.getRhs().getDefiningOp() == op) { | ||||||||||
rewriter.eraseOp(connect); | ||||||||||
replaceOpAndCopyName(rewriter, op, reset.getResult()); | ||||||||||
return success(); | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
return failure(); | ||||||||||
} | ||||||||||
}; | ||||||||||
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. Challenge 1: Can this be written in using ODS? 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. I don't know if that makes sense, since this isn't so much a pattern on the register op, but more, a pattern on the connect driving the register. Maybe I could do it using some new constraints. I took a quick look around and it doesn't look like we do this kind of thing using ODS. I'm interested in suggestions though. 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. I see, the problem isn't in generating the match, but the replacement. The match would be something like: |
||||||||||
} // namespace | ||||||||||
|
||||||||||
static bool isDefinedByOneConstantOp(Value v) { | ||||||||||
if (auto c = v.getDefiningOp<ConstantOp>()) | ||||||||||
return c.getValue().isOne(); | ||||||||||
|
@@ -2279,7 +2350,9 @@ canonicalizeRegResetWithOneReset(RegResetOp reg, PatternRewriter &rewriter) { | |||||||||
|
||||||||||
void RegResetOp::getCanonicalizationPatterns(RewritePatternSet &results, | ||||||||||
MLIRContext *context) { | ||||||||||
results.add<patterns::RegResetWithZeroReset, FoldResetMux>(context); | ||||||||||
results | ||||||||||
.add<patterns::RegResetWithZeroReset, FoldResetMux, RegResetAndOrOfSelf>( | ||||||||||
context); | ||||||||||
results.add(canonicalizeRegResetWithOneReset); | ||||||||||
results.add(demoteForceableIfUnused<RegResetOp>); | ||||||||||
} | ||||||||||
|
@@ -3156,10 +3229,66 @@ static LogicalResult foldHiddenReset(RegOp reg, PatternRewriter &rewriter) { | |||||||||
return success(); | ||||||||||
} | ||||||||||
|
||||||||||
/// This canonicalizer provides the following patterns: | ||||||||||
/// connect(reg, and(reg, x)) ==> reg -> 0 | ||||||||||
/// connect(reg, and(x, reg)) ==> reg -> 0 | ||||||||||
/// connect(reg, or(reg, x)) ==> reg -> 1 | ||||||||||
/// connect(reg, or(x, reg)) ==> reg -> 1 | ||||||||||
/// | ||||||||||
/// Justification: The initial value of a register is indeterminant, which means | ||||||||||
/// We are free to choose any initial value when optimizing the circuit. For the | ||||||||||
/// AND patterns, if we assume the initial value is zero, then the register will | ||||||||||
/// always be zero. For the OR patterns, if we assume the initial value is one, | ||||||||||
/// then the register will always be one. | ||||||||||
Comment on lines
+3238
to
+3242
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. Great for including this on both rewrite patterns! Providing this as a comment makes it clear not only what the pattern is doing, but why and with what justification in the spec we are using. 💯 |
||||||||||
static LogicalResult foldRegAndOrOfSelf(RegOp reg, PatternRewriter &rewriter) { | ||||||||||
// This canonicalization only applies when the register holds 1 bit. | ||||||||||
auto type = dyn_cast<UIntType>(reg.getResult().getType()); | ||||||||||
if (!type || type.getWidthOrSentinel() != 1) | ||||||||||
return failure(); | ||||||||||
|
||||||||||
// Find the one true connect, or bail. | ||||||||||
auto connect = getSingleConnectUserOf(reg.getResult()); | ||||||||||
if (!connect) | ||||||||||
return failure(); | ||||||||||
|
||||||||||
auto *src = connect.getSrc().getDefiningOp(); | ||||||||||
if (!src) | ||||||||||
return failure(); | ||||||||||
|
||||||||||
// connect(reg, and(reg, x)) ==> reg -> 0 | ||||||||||
// connect(reg, and(x, reg)) ==> reg -> 0 | ||||||||||
if (auto srcAnd = dyn_cast<AndPrimOp>(src)) { | ||||||||||
if (srcAnd.getLhs().getDefiningOp() == reg || | ||||||||||
srcAnd.getRhs().getDefiningOp() == reg) { | ||||||||||
auto attr = getIntAttr(type, APInt(1, 0)); | ||||||||||
replaceOpWithNewOpAndCopyName<ConstantOp>(rewriter, reg, type, attr); | ||||||||||
rewriter.eraseOp(connect); | ||||||||||
return success(); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
// connect(reg, or(reg, x)) ==> reg -> 1 | ||||||||||
// connect(reg, or(x, reg)) ==> reg -> 1 | ||||||||||
if (auto srcOr = dyn_cast<OrPrimOp>(src)) { | ||||||||||
if (srcOr.getLhs().getDefiningOp() == reg || | ||||||||||
srcOr.getRhs().getDefiningOp() == reg) { | ||||||||||
auto attr = getIntAttr(type, APInt(1, 1)); | ||||||||||
replaceOpWithNewOpAndCopyName<ConstantOp>(rewriter, reg, type, attr); | ||||||||||
rewriter.eraseOp(connect); | ||||||||||
return success(); | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
return failure(); | ||||||||||
} | ||||||||||
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. Challenge 2: Can this be written in terms of ODS? |
||||||||||
|
||||||||||
LogicalResult RegOp::canonicalize(RegOp op, PatternRewriter &rewriter) { | ||||||||||
if (!hasDontTouch(op.getOperation()) && !op.isForceable() && | ||||||||||
succeeded(foldHiddenReset(op, rewriter))) | ||||||||||
return success(); | ||||||||||
if (!hasDontTouch(op.getOperation()) && !op.isForceable()) { | ||||||||||
if (succeeded(foldHiddenReset(op, rewriter))) | ||||||||||
return success(); | ||||||||||
if (succeeded(foldRegAndOrOfSelf(op, rewriter))) | ||||||||||
return success(); | ||||||||||
} | ||||||||||
|
||||||||||
if (succeeded(demoteForceableIfUnused(op, rewriter))) | ||||||||||
return success(); | ||||||||||
|
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.
Mega-nit: This syntax is bothering me and took me a while to figure it out. The
==>
is an "AND" in the first use and is "is converted to" (or "IMPLIES"?) in the second.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.
Ah OK I can delete these comments, I don't think they're helpful. FWIW
==>
is implication,->
is canonicalization.