-
Notifications
You must be signed in to change notification settings - Fork 303
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
[FIRRTL] Make IMDCE work for ops w/ regions/blocks #7881
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 |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
#include "circt/Dialect/HW/InnerSymbolTable.h" | ||
#include "circt/Support/Debug.h" | ||
#include "mlir/IR/ImplicitLocOpBuilder.h" | ||
#include "mlir/IR/Iterators.h" | ||
#include "mlir/IR/Threading.h" | ||
#include "mlir/Interfaces/SideEffectInterfaces.h" | ||
#include "mlir/Pass/Pass.h" | ||
|
@@ -240,15 +241,16 @@ void IMDeadCodeElimPass::markBlockExecutable(Block *block) { | |
if (!executableBlocks.insert(block).second) | ||
return; // Already executable. | ||
|
||
auto fmodule = cast<FModuleOp>(block->getParentOp()); | ||
if (fmodule.isPublic()) | ||
auto fmodule = dyn_cast<FModuleOp>(block->getParentOp()); | ||
if (fmodule && fmodule.isPublic()) | ||
markAlive(fmodule); | ||
|
||
// Mark ports with don't touch as alive. | ||
for (auto blockArg : block->getArguments()) | ||
if (hasDontTouch(blockArg)) { | ||
markAlive(blockArg); | ||
markAlive(fmodule); | ||
if (fmodule) | ||
markAlive(fmodule); | ||
} | ||
|
||
for (auto &op : *block) { | ||
|
@@ -261,8 +263,14 @@ void IMDeadCodeElimPass::markBlockExecutable(Block *block) { | |
else if (isa<FConnectLike>(op)) | ||
// Skip connect op. | ||
continue; | ||
else if (hasUnknownSideEffect(&op)) | ||
else if (hasUnknownSideEffect(&op)) { | ||
markUnknownSideEffectOp(&op); | ||
// Recursively mark any blocks contained within these operations as | ||
// executable. | ||
for (auto ®ion : op.getRegions()) | ||
for (auto &block : region.getBlocks()) | ||
markBlockExecutable(&block); | ||
} | ||
|
||
// TODO: Handle attach etc. | ||
} | ||
|
@@ -561,33 +569,35 @@ void IMDeadCodeElimPass::rewriteModuleBody(FModuleOp module) { | |
std::bind(removeDeadNonLocalAnnotations, -1, std::placeholders::_1)); | ||
|
||
// Walk the IR bottom-up when deleting operations. | ||
for (auto &op : llvm::make_early_inc_range(llvm::reverse(*body))) { | ||
// Connects to values that we found to be dead can be dropped. | ||
if (auto connect = dyn_cast<FConnectLike>(op)) { | ||
if (isAssumedDead(connect.getDest())) { | ||
LLVM_DEBUG(llvm::dbgs() << "DEAD: " << connect << "\n";); | ||
connect.erase(); | ||
++numErasedOps; | ||
} | ||
continue; | ||
} | ||
|
||
// Delete dead wires, regs, nodes and alloc/read ops. | ||
if ((isDeclaration(&op) || !hasUnknownSideEffect(&op)) && | ||
isAssumedDead(&op)) { | ||
LLVM_DEBUG(llvm::dbgs() << "DEAD: " << op << "\n";); | ||
assert(op.use_empty() && "users should be already removed"); | ||
op.erase(); | ||
++numErasedOps; | ||
continue; | ||
} | ||
|
||
// Remove non-sideeffect op using `isOpTriviallyDead`. | ||
if (mlir::isOpTriviallyDead(&op)) { | ||
op.erase(); | ||
++numErasedOps; | ||
} | ||
} | ||
module.walk<mlir::WalkOrder::PostOrder, mlir::ReverseIterator>( | ||
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. Could this work as a preorder walk? If you have a dead op with a region, there is no reason to rewrite the region if the whole thing is going to be deleted anyways. We don't have any ops like this right now 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. It could be, however, this would require us to better define whether or not all the ops in the FIRRTL dialect have side effects. Consider the following example: firrtl.circuit "Foo" {
firrtl.layer @A bind {
}
firrtl.module private @Bar(in %clock: !firrtl.clock, in %a: !firrtl.uint<1>, out %b: !firrtl.probe<uint<1>, @A>, out %c: !firrtl.uint<1>) {
%0 = firrtl.not %a : (!firrtl.uint<1>) -> !firrtl.uint<1>
firrtl.matchingconnect %c, %0 : !firrtl.uint<1>
firrtl.layerblock @A {
%1 = firrtl.not %a : (!firrtl.uint<1>) -> !firrtl.uint<1>
%a_not = firrtl.node %1 : !firrtl.uint<1>
%2 = firrtl.ref.send %a_not : !firrtl.uint<1>
%3 = firrtl.ref.cast %2 : (!firrtl.probe<uint<1>>) -> !firrtl.probe<uint<1>, @A>
firrtl.ref.define %b, %3 : !firrtl.probe<uint<1>, @A>
}
}
firrtl.module @Foo(in %clock: !firrtl.clock, in %a: !firrtl.uint<1>, out %c: !firrtl.uint<1>) attributes {convention = #firrtl<convention scalarized>} {
%bar_clock, %bar_a, %bar_b, %bar_c = firrtl.instance bar @Bar(in clock: !firrtl.clock, in a: !firrtl.uint<1>, out b: !firrtl.probe<uint<1>, @A>, out c: !firrtl.uint<1>)
firrtl.matchingconnect %bar_clock, %clock : !firrtl.clock
firrtl.matchingconnect %bar_a, %a : !firrtl.uint<1>
firrtl.matchingconnect %c, %bar_c : !firrtl.uint<1>
}
} This has a path through a Given the way that we've defined FIRRTL operations, the post-order walk seems to be the only way to do this if we want to delete the h/t @rwy7 for working through this example. 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. Change to add the missing traits to 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 added a test here showing this example: 08c3c8a
seldridge marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[&](Operation *op) { | ||
// Connects to values that we found to be dead can be dropped. | ||
LLVM_DEBUG(llvm::dbgs() << "Visit: " << *op << "\n"); | ||
if (auto connect = dyn_cast<FConnectLike>(op)) { | ||
if (isAssumedDead(connect.getDest())) { | ||
LLVM_DEBUG(llvm::dbgs() << "DEAD: " << connect << "\n";); | ||
connect.erase(); | ||
++numErasedOps; | ||
} | ||
return; | ||
} | ||
|
||
// Delete dead wires, regs, nodes and alloc/read ops. | ||
if ((isDeclaration(op) || !hasUnknownSideEffect(op)) && | ||
isAssumedDead(op)) { | ||
LLVM_DEBUG(llvm::dbgs() << "DEAD: " << *op << "\n";); | ||
assert(op->use_empty() && "users should be already removed"); | ||
op->erase(); | ||
++numErasedOps; | ||
return; | ||
} | ||
|
||
// Remove non-sideeffect op using `isOpTriviallyDead`. | ||
if (mlir::isOpTriviallyDead(op)) { | ||
op->erase(); | ||
++numErasedOps; | ||
} | ||
}); | ||
} | ||
|
||
void IMDeadCodeElimPass::rewriteModuleSignature(FModuleOp module) { | ||
|
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.
Something seems sketchy to me about this. We don't have many ops other than FModuleOps that have block arguments, but if we did: if a block argument is DontTouch'd, shouldn't we always mark the owning operation as alive, and not
if(isa<FModuleOp>(op))
? That being said,hasDontTouch(value)
first checks to see if the owning op isDontTouch
d and then checks for port symbols and annotations iff it is an FModuleOp. It seems a bit weird to me that we say a port is dont touched if the owning module is.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.
What would you say are the defined semantics of
DontTouchAnnotation
? 😉From an annotation perspective, this can only be applied to certain named things (modules, wires, registers, memories, and ports). This annotation has the semantics of, when applied to a module, is equivalent to don't touching all the ports. Given that interpretation, then this is doing the right thing. Or: it's reasonable to expect that this is a module port and not a general block arg as this was never intended to be defined for block args, just for ports.
That said, this annotation has been begging for a better in-IR representation and we've been skirting around this for a while. The best thing to do is to fit this into the FIRRTL spec and then figure out how to represent it here as opposed to trying to generalize don't touch on ports to don't touch on block args (as nobody is asking for that).