From 06daae8fe086db4441f265478c71511b11edbc64 Mon Sep 17 00:00:00 2001 From: stomfaig Date: Sun, 22 Mar 2026 13:17:32 +0000 Subject: [PATCH 1/4] add module rewrites --- include/circt/Support/Utils.h | 41 +++ lib/Dialect/FIRRTL/FIRRTLOps.cpp | 41 --- .../HW/Transforms/HWIMDeadCodeElim.cpp | 315 ++++++++++++++++++ test/Dialect/HW/imdce.mlir | 178 ++++++++++ 4 files changed, 534 insertions(+), 41 deletions(-) diff --git a/include/circt/Support/Utils.h b/include/circt/Support/Utils.h index 92d9d959fd21..9d5ba87231e8 100644 --- a/include/circt/Support/Utils.h +++ b/include/circt/Support/Utils.h @@ -28,6 +28,47 @@ inline bool isAncestorOfValueOwner(Operation *op, Value value) { return op->isAncestor(value.getParentBlock()->getParentOp()); } +/// Remove elements from the input array corresponding to set bits in +/// `indicesToDrop`, returning the elements not mentioned. +template +static SmallVector +removeElementsAtIndices(ArrayRef input, + const llvm::BitVector &indicesToDrop) { +#ifndef NDEBUG + if (!input.empty()) { + int lastIndex = indicesToDrop.find_last(); + if (lastIndex >= 0) + assert((size_t)lastIndex < input.size() && "index out of range"); + } +#endif + + // If the input is empty (which is an optimization we do for certain array + // attributes), simply return an empty vector. + if (input.empty()) + return {}; + + // Copy over the live chunks. + size_t lastCopied = 0; + SmallVector result; + result.reserve(input.size() - indicesToDrop.count()); + + for (unsigned indexToDrop : indicesToDrop.set_bits()) { + // If we skipped over some valid elements, copy them over. + if (indexToDrop > lastCopied) { + result.append(input.begin() + lastCopied, input.begin() + indexToDrop); + lastCopied = indexToDrop; + } + // Ignore this value so we don't copy it in the next iteration. + ++lastCopied; + } + + // If there are live elements at the end, copy them over. + if (lastCopied < input.size()) + result.append(input.begin() + lastCopied, input.end()); + + return result; +} + //===----------------------------------------------------------------------===// // Parallel utilities //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/FIRRTL/FIRRTLOps.cpp b/lib/Dialect/FIRRTL/FIRRTLOps.cpp index bbef5888ce08..01b3e5606bb0 100644 --- a/lib/Dialect/FIRRTL/FIRRTLOps.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLOps.cpp @@ -52,47 +52,6 @@ using namespace chirrtl; // Utilities //===----------------------------------------------------------------------===// -/// Remove elements from the input array corresponding to set bits in -/// `indicesToDrop`, returning the elements not mentioned. -template -static SmallVector -removeElementsAtIndices(ArrayRef input, - const llvm::BitVector &indicesToDrop) { -#ifndef NDEBUG - if (!input.empty()) { - int lastIndex = indicesToDrop.find_last(); - if (lastIndex >= 0) - assert((size_t)lastIndex < input.size() && "index out of range"); - } -#endif - - // If the input is empty (which is an optimization we do for certain array - // attributes), simply return an empty vector. - if (input.empty()) - return {}; - - // Copy over the live chunks. - size_t lastCopied = 0; - SmallVector result; - result.reserve(input.size() - indicesToDrop.count()); - - for (unsigned indexToDrop : indicesToDrop.set_bits()) { - // If we skipped over some valid elements, copy them over. - if (indexToDrop > lastCopied) { - result.append(input.begin() + lastCopied, input.begin() + indexToDrop); - lastCopied = indexToDrop; - } - // Ignore this value so we don't copy it in the next iteration. - ++lastCopied; - } - - // If there are live elements at the end, copy them over. - if (lastCopied < input.size()) - result.append(input.begin() + lastCopied, input.end()); - - return result; -} - /// Emit an error if optional location is non-null, return null of return type. template static RetTy emitInferRetTypeError(std::optional loc, diff --git a/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp b/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp index 63b68a75e5cc..b9dae9f0c641 100644 --- a/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp +++ b/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp @@ -10,9 +10,12 @@ #include "circt/Dialect/HW/HWOpInterfaces.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWPasses.h" +#include "circt/Support/Utils.h" #include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Iterators.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "llvm/ADT/DenseMapInfoVariant.h" +#include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/SmallVector.h" #include @@ -69,6 +72,10 @@ struct HWIMDeadCodeElim void visitInstanceLike(HWInstanceLike instanceLike); void visitValue(Value value); + void rewriteModuleSignature(HWModuleOp module); + void eraseEmptyModule(HWModuleOp module); + void rewriteModuleBody(HWModuleOp module); + void markUnknownSideEffectOp(Operation *op); void markInstanceLike(HWInstanceLike instanceLike); @@ -93,6 +100,76 @@ static bool hasUnknownSideEffect(Operation *op) { return false; } +/// Clone \p instance, but with ports deleted according to +/// the \p inErasures and \p outErasures BitVectors. +static InstanceOp cloneWithErasedPorts(InstanceOp &instance, + const llvm::BitVector &inErasures, + const llvm::BitVector &outErasures) { + assert(outErasures.size() >= instance->getNumResults() && + "out_erasures is not at least as large as getNumResults()"); + assert(inErasures.size() >= instance->getNumOperands() && + "in_erasures is not at least as large as getNumOperands()"); + + // Restrict outputs + SmallVector newResultTypes = removeElementsAtIndices( + SmallVector(instance->result_type_begin(), + instance->result_type_end()), + outErasures); + auto newResultNames = removeElementsAtIndices( + SmallVector(instance.getResultNames().begin(), + instance.getResultNames().end()), + outErasures); + + // Restrict inputs + auto newOperands = removeElementsAtIndices( + SmallVector(instance->getOperands().begin(), + instance->getOperands().end()), + inErasures); + auto newOperandNames = removeElementsAtIndices( + SmallVector(instance.getArgNames().begin(), + instance.getArgNames().end()), + inErasures); + + ImplicitLocOpBuilder builder(instance->getLoc(), instance); + + auto newOpNamesArrayAttr = builder.getArrayAttr(newOperandNames); + auto newResultNamesArrayAttr = builder.getArrayAttr(newResultNames); + + auto newInstance = InstanceOp::create( + builder, instance->getLoc(), newResultTypes, + instance.getInstanceNameAttr(), instance.getModuleName(), newOperands, + newOpNamesArrayAttr, newResultNamesArrayAttr, instance.getParameters(), + instance.getInnerSymAttr()); + + return newInstance; +} + +/// Static method for cloning \p instance of type InstanceOp +/// with in- and output ports erased based on \p inErasures +/// and \p outErasures respectively. The users of the results +/// of the instance are also updated. +static InstanceOp +cloneWithErasePortsAndReplaceUses(InstanceOp &instance, + const llvm::BitVector &inErasures, + const llvm::BitVector &outErasures) { + + auto newInstance = cloneWithErasedPorts(instance, inErasures, outErasures); + + // Replace all input operands + size_t erased = 0; + for (size_t index = 0, e = instance->getNumResults(); index < e; ++index) { + auto r1 = instance->getResult(index); + if (outErasures[index]) { + ++erased; + continue; + } + auto r2 = newInstance->getResult(index - erased); + r1.replaceAllUsesWith(r2); + } + + return newInstance; +} + void HWIMDeadCodeElim::markUnknownSideEffectOp(Operation *op) { // For operations with side effects, pessimistically mark results and // operands as alive. @@ -351,9 +428,247 @@ void HWIMDeadCodeElim::runOnOperation() { op->setAttr("val-liveness", getValLiveness(op->getResults())); } }); + + return; + } + + // Rewrite module signatures or delete unreachable modules. + for (auto module : + llvm::make_early_inc_range(getOperation().getOps())) { + if (isBlockExecutable(module.getBodyBlock())) { + rewriteModuleSignature(module); + } else { + // If the module is unreachable from the toplevel, just delete it. + // Note that post-order traversal on the instance graph never visit + // unreachable modules so it's safe to erase the module even though + // `modules` seems to be capturing module pointers. + module.erase(); + } + } + + for (auto module : + llvm::make_early_inc_range(getOperation().getOps())) + rewriteModuleBody(module); + + for (auto module : + llvm::make_early_inc_range(getOperation().getOps())) { + eraseEmptyModule(module); } // Clean up data structures. executableBlocks.clear(); liveElements.clear(); } + +void HWIMDeadCodeElim::rewriteModuleSignature(HWModuleOp module) { + assert(isBlockExecutable(module.getBodyBlock()) && + "unreachable modules must be already deleted"); + + igraph::InstanceGraphNode *instanceGraphNode = instanceGraph->lookup(module); + LLVM_DEBUG(llvm::dbgs() << "Prune ports of module: " << module.getName() + << "\n"); + + auto replaceInstanceResultWithConst = + [&](ImplicitLocOpBuilder &builder, unsigned index, InstanceOp instance) { + auto result = instance.getResult(index); + assert(isAssumedDead(result)); + + auto dummy = + mlir::UnrealizedConversionCastOp::create( + builder, ArrayRef{result.getType()}, ArrayRef{}) + ->getResult(0); + result.replaceAllUsesWith(dummy); + return; + }; + + // First, delete dead instances. + for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) { + auto maybeInst = use->getInstance(); + if (!maybeInst) + continue; + + auto instance = cast(maybeInst); + + if (!isKnownAlive(instance)) { + // Replace old instance results with dummy wires. + ImplicitLocOpBuilder builder(instance.getLoc(), instance); + for (auto index : llvm::seq(0u, instance.getNumResults())) + replaceInstanceResultWithConst(builder, index, instance); + // Make sure that we update the instance graph. + use->erase(); + instance.erase(); + } + } + + // Ports of public modules cannot be modified. + if (module.isPublic()) + return; + + // Otherwise prepare data structures for tracking dead ports. + auto *outputOp = module.getBodyBlock()->getTerminator(); + + auto numInPorts = module.getBody().getNumArguments(); + auto numOutPorts = outputOp->getNumOperands(); + + llvm::BitVector deadInPortBitVec(numInPorts); + llvm::BitVector deadOutPortBitVec(numOutPorts); + + ImplicitLocOpBuilder builder(module.getLoc(), module.getContext()); + builder.setInsertionPointToStart(module.getBodyBlock()); + + for (auto index : llvm::seq(0u, numInPorts)) { + auto inPort = module.getBodyBlock()->getArgument(index); + if (isKnownAlive(inPort)) + continue; + + auto placeholder = + mlir::UnrealizedConversionCastOp::create( + builder, ArrayRef{inPort.getType()}, ArrayRef{}) + ->getResult(0); + inPort.replaceAllUsesWith(placeholder); + deadInPortBitVec.set(index); + } + + // Find all unused results + unsigned erasures = 0; + for (auto index : llvm::seq(0u, numOutPorts)) { + auto argument = outputOp->getOperand(index - erasures); + + if (isKnownAlive(argument)) + continue; + + outputOp->eraseOperand(index - erasures); + ++erasures; + + deadOutPortBitVec.set(index); + } + + // If there is nothing to remove, abort. + if (deadInPortBitVec.none() && deadOutPortBitVec.none()) + return; + + // Erase arguments of the old module from liveSet to prevent from creating + // dangling pointers. + for (auto arg : module.getBodyBlock()->getArguments()) + liveElements.erase(arg); + + for (auto op : outputOp->getOperands()) + liveElements.erase(op); + + // Delete ports from the module. + module.erasePorts(SmallVector(deadInPortBitVec.set_bits()), + SmallVector(deadOutPortBitVec.set_bits())); + module.getBodyBlock()->eraseArguments(deadInPortBitVec); + + // Add arguments of the new module to liveSet. + for (auto arg : module.getBodyBlock()->getArguments()) + liveElements.insert(arg); + + for (auto op : outputOp->getOperands()) + liveElements.insert(op); + + // Rewrite all instantiation of the module. + for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) { + auto instance = cast(*use->getInstance()); + + ImplicitLocOpBuilder builder(instance.getLoc(), instance); + // Replace old instance results with dummy constants. + for (auto index : deadOutPortBitVec.set_bits()) + replaceInstanceResultWithConst(builder, index, instance); + + // Since we will rewrite instance op, it is necessary to remove old + // instance results from liveSet. + for (auto oldResult : instance.getResults()) + liveElements.erase(oldResult); + + auto newInstance = cloneWithErasePortsAndReplaceUses( + instance, deadInPortBitVec, deadOutPortBitVec); + + for (auto newResult : newInstance.getResults()) + liveElements.insert(newResult); + + use->erase(); + instance->erase(); + } + + numRemovedPorts += deadInPortBitVec.count() + deadOutPortBitVec.count(); +} + +void HWIMDeadCodeElim::rewriteModuleBody(HWModuleOp module) { + assert(isBlockExecutable(module.getBodyBlock()) && + "unreachable modules must be already deleted"); + + // Walk the IR bottom-up when deleting operations. + module.walk( + [&](Operation *op) { + // Connects to values that we found to be dead can be dropped. + LLVM_DEBUG(llvm::dbgs() << "Visit: " << *op << "\n"); + + // Remove non-sideeffect op using `isOpTriviallyDead`. + // Skip instances - they're handled by + // rewriteModuleSignature/eraseEmptyModule and also need erasure from + // instanceGraph + if (!isa(op) && mlir::isOpTriviallyDead(op) && + isAssumedDead(op)) { + op->erase(); + ++numErasedOps; + } + }); +} + +void HWIMDeadCodeElim::eraseEmptyModule(HWModuleOp module) { + // If the module is not empty, just skip. + if (!module.getBodyBlock()->without_terminator().empty()) + return; + + // It can also be the case that the only `hw.output` is nontrivial, also skip. + if (module.getBodyBlock()->getTerminator()->getNumOperands() != 0) + return; + + // We cannot delete public modules so generate a warning. + if (module.isPublic()) { + mlir::emitWarning(module.getLoc()) + << "module `" << module.getName() + << "` is empty but cannot be removed because the module is public"; + return; + } + + // Ok, the module is empty. Delete instances unless they have symbols. + LLVM_DEBUG(llvm::dbgs() << "Erase " << module.getName() << "\n"); + igraph::InstanceGraphNode *instanceGraphNode = + instanceGraph->lookup(module.getModuleNameAttr()); + + SmallVector instancesWithSymbols; + for (auto *use : llvm::make_early_inc_range(instanceGraphNode->uses())) { + auto maybeInst = use->getInstance(); + if (!maybeInst) + continue; + + auto instance = cast(maybeInst); + if (instance.getInnerSym()) { + instancesWithSymbols.push_back(instance.getLoc()); + continue; + } + use->erase(); + instance.erase(); + } + + // If there is an instance with a symbol, we don't delete the module itself. + if (!instancesWithSymbols.empty()) { + auto diag = module.emitWarning() + << "module `" << module.getName() + << "` is empty but cannot be removed because an instance is " + "referenced by name"; + diag.attachNote(FusedLoc::get(&getContext(), instancesWithSymbols)) + << "these are instances with symbols"; + return; + } + + // We cannot delete alive modules. + if (liveElements.contains(module)) + return; + + instanceGraph->erase(instanceGraphNode); + module.erase(); + ++numErasedModules; +} \ No newline at end of file diff --git a/test/Dialect/HW/imdce.mlir b/test/Dialect/HW/imdce.mlir index 5e95f96dd354..bf29882e4f87 100644 --- a/test/Dialect/HW/imdce.mlir +++ b/test/Dialect/HW/imdce.mlir @@ -1,4 +1,5 @@ // RUN: circt-opt -hw-imdce=print-liveness --split-input-file --allow-unregistered-dialect %s | FileCheck %s --check-prefixes=LIVENESS +// RUN: circt-opt -hw-imdce --split-input-file --allow-unregistered-dialect %s | FileCheck %s --check-prefixes=ELIMINATE module { @@ -34,6 +35,10 @@ module { // LIVENESS-LABEL: hw.module public @dead_module_dead_user // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = [] + // ELIMINATE-LABEL: hw.module public @dead_module_dead_user(out const : i1) { + // ELIMINATE: %[[CONSTANT_0:.*]] = hw.constant false + // ELIMINATE: hw.output %[[CONSTANT_0]] : i1 + // ELIMINATE: } hw.module public @dead_module_dead_user(out const : i1) { // LIVENESS: hw.constant false // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -60,6 +65,10 @@ module { // LIVENESS-LABEL: hw.module private @dead_port_alive_module // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["DEAD", "LIVE"] + // ELIMINATE-LABEL: hw.module private @dead_port_alive_module(in + // ELIMINATE-SAME: %[[SOURCE2:.*]] : i1, out dest : i1) { + // ELIMINATE: hw.output %[[SOURCE2]] : i1 + // ELIMINATE: } hw.module private @dead_port_alive_module(in %source1 : i1, in %source2 : i1, out dest : i1) { // LIVENESS: hw.output // LIVENESS-SAME: "val-liveness" = ["LIVE"] @@ -69,6 +78,11 @@ module { // LIVENESS-LABEL: hw.module public @public_with_dead_port // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["DEAD", "LIVE"] + // ELIMINATE-LABEL: hw.module public @public_with_dead_port(in + // ELIMINATE-SAME: %[[SOURCE1:.*]] : i1, in + // ELIMINATE-SAME: %[[SOURCE2:.*]] : i1, out dest : i1) { + // ELIMINATE: hw.output %[[SOURCE2]] : i1 + // ELIMINATE: } hw.module public @public_with_dead_port(in %source1 : i1, in %source2 : i1, out dest : i1) { // LIVENESS: hw.output // LIVENESS-SAME: "val-liveness" = ["LIVE"] @@ -78,6 +92,13 @@ module { // LIVENESS-LABEL: hw.module public @dead_port_alive_module_user // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = [] + // ELIMINATE-LABEL: hw.module public @dead_port_alive_module_user(out dest1 : i1, out dest2 : i1) { + // ELIMINATE: %[[CONSTANT_0:.*]] = hw.constant false + // ELIMINATE: %[[CONSTANT_1:.*]] = hw.constant true + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "dead_port_alive_module_instance" @dead_port_alive_module(source2: %[[CONSTANT_1]]: i1) -> (dest: i1) + // ELIMINATE: %[[INSTANCE_1:.*]] = hw.instance "public_with_dead_port_instance" @public_with_dead_port(source1: %[[CONSTANT_0]]: i1, source2: %[[CONSTANT_1]]: i1) -> (dest: i1) + // ELIMINATE: hw.output %[[INSTANCE_0]], %[[INSTANCE_1]] : i1, i1 + // ELIMINATE: } hw.module public @dead_port_alive_module_user(out dest1 : i1, out dest2 : i1) { // LIVENESS: hw.constant false // LIVENESS-SAME: "op-liveness" = "DEAD" @@ -117,6 +138,12 @@ module { // LIVENESS-LABEL: hw.module private @Child2 // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE", "LIVE"] + // ELIMINATE-LABEL: hw.module private @Child2(in + // ELIMINATE-SAME: %[[INPUT:.*]] : i1, in + // ELIMINATE-SAME: %[[CLOCK:.*]] : !seq.clock, out output : i1) { + // ELIMINATE: %[[FIRREG_0:.*]] = seq.firreg %[[INPUT]] clock %[[CLOCK]] {firrtl.random_init_start = 0 : ui64} : i1 + // ELIMINATE: hw.output %[[FIRREG_0]] : i1 + // ELIMINATE: } hw.module private @Child2(in %input : i1, in %clock : !seq.clock, out output: i1) { // LIVENESS: seq.firreg // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -130,6 +157,11 @@ module { // LIVENESS-LABEL: hw.module public @Top // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["DEAD", "DEAD"] + // ELIMINATE-LABEL: hw.module public @Top(in + // ELIMINATE-SAME: %[[CLOCK:.*]] : !seq.clock, in + // ELIMINATE-SAME: %[[INPUT:.*]] : i1) { + // ELIMINATE: hw.output + // ELIMINATE: } hw.module public @Top(in %clock: !seq.clock, in %input : i1) { // LIVENESS: hw.instance "child1_instance" // LIVENESS-SAME: "op-liveness" = "DEAD" @@ -149,6 +181,12 @@ module { // LIVENESS-LABEL: hw.module private @SingleDriver // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module private @SingleDriver(in + // ELIMINATE-SAME: %[[A:.*]] : i1, out b : i1) { + // ELIMINATE: %[[CONSTANT_0:.*]] = hw.constant false + // ELIMINATE: %[[AND_0:.*]] = comb.and %[[A]], %[[CONSTANT_0]] : i1 + // ELIMINATE: hw.output %[[AND_0]] : i1 + // ELIMINATE: } hw.module private @SingleDriver(in %a : i1, out b : i1, out c : i1) { // LIVENESS: hw.constant false // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -170,6 +208,11 @@ module { // LIVENESS-LABEL: hw.module public @UnusedOutput // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @UnusedOutput(in + // ELIMINATE-SAME: %[[A:.*]] : i1, out b : i1) { + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "SingleDriverInstance" @SingleDriver(a: %[[A]]: i1) -> (b: i1) + // ELIMINATE: hw.output %[[INSTANCE_0]] : i1 + // ELIMINATE: } hw.module public @UnusedOutput(in %a : i1, out b : i1) { // LIVENESS: hw.instance "SingleDriverInstance" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -195,6 +238,9 @@ module { // LIVENESS-LABEL: hw.module public @DeleteEmptyModule // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = [] + // ELIMINATE-LABEL: hw.module public @DeleteEmptyModule() { + // ELIMINATE: hw.output + // ELIMINATE: } hw.module public @DeleteEmptyModule() { // LIVENESS: hw.constant true // LIVENESS-SAME: "op-liveness" = "DEAD" @@ -212,6 +258,10 @@ module { // LIVENESS-LABEL: hw.module private @multiple_remove_ports // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["DEAD", "DEAD", "LIVE"] + // ELIMINATE-LABEL: hw.module private @multiple_remove_ports(in + // ELIMINATE-SAME: %[[SOURCE3:.*]] : i1, out result : i1) { + // ELIMINATE: hw.output %[[SOURCE3]] : i1 + // ELIMINATE: } hw.module private @multiple_remove_ports(in %source1 : i1, in %source2 : i1, in %source3 : i1, out result : i1) { // LIVENESS: hw.output // LIVENESS-SAME: "val-liveness" = ["LIVE"] @@ -221,6 +271,11 @@ module { // LIVENESS-LABEL: hw.module public @mrp_user // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @mrp_user(in + // ELIMINATE-SAME: %[[IN:.*]] : i1, out result : i1) { + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "mrp_instance" @multiple_remove_ports(source3: %[[IN]]: i1) -> (result: i1) + // ELIMINATE: hw.output %[[INSTANCE_0]] : i1 + // ELIMINATE: } hw.module public @mrp_user(in %in : i1, out result : i1) { // Result used -> instance alive // LIVENESS: hw.instance "mrp_instance" @@ -239,6 +294,12 @@ module { // LIVENESS-LABEL: hw.module public @DeadOpsInBody // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE", "LIVE"] + // ELIMINATE-LABEL: hw.module public @DeadOpsInBody(in + // ELIMINATE-SAME: %[[A:.*]] : i1, in + // ELIMINATE-SAME: %[[CLK:.*]] : !seq.clock, out result : i1) { + // ELIMINATE: %[[FIRREG_0:.*]] = seq.firreg %[[A]] clock %[[CLK]] : i1 + // ELIMINATE: hw.output %[[A]] : i1 + // ELIMINATE: } hw.module public @DeadOpsInBody(in %a: i1, in %clk: !seq.clock, out result: i1) { // LIVENESS: seq.firreg // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -260,6 +321,10 @@ module { // LIVENESS-LABEL: hw.module private @NestedBottom // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module private @NestedBottom(in + // ELIMINATE-SAME: %[[X:.*]] : i1, out y : i1) { + // ELIMINATE: hw.output %[[X]] : i1 + // ELIMINATE: } hw.module private @NestedBottom(in %x: i1, out y: i1, out dead: i1) { // LIVENESS: hw.constant false // LIVENESS-SAME: "op-liveness" = "DEAD" @@ -277,6 +342,11 @@ module { // LIVENESS-LABEL: hw.module private @NestedMiddle // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module private @NestedMiddle(in + // ELIMINATE-SAME: %[[X:.*]] : i1, out y : i1) { + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "bot" @NestedBottom(x: %[[X]]: i1) -> (y: i1) + // ELIMINATE: hw.output %[[INSTANCE_0]] : i1 + // ELIMINATE: } hw.module private @NestedMiddle(in %x: i1, out y: i1) { // LIVENESS: hw.instance "bot" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -290,6 +360,11 @@ module { // LIVENESS-LABEL: hw.module public @NestedTop // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @NestedTop(in + // ELIMINATE-SAME: %[[X:.*]] : i1, out y : i1) { + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "mid" @NestedMiddle(x: %[[X]]: i1) -> (y: i1) + // ELIMINATE: hw.output %[[INSTANCE_0]] : i1 + // ELIMINATE: } hw.module public @NestedTop(in %x: i1, out y: i1) { // LIVENESS: hw.instance "mid" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -307,6 +382,13 @@ module { // LIVENESS-LABEL: hw.module public @SideEffectPreserved // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE", "LIVE"] + // ELIMINATE-LABEL: hw.module public @SideEffectPreserved(in + // ELIMINATE-SAME: %[[A:.*]] : i1, in + // ELIMINATE-SAME: %[[CLK:.*]] : !seq.clock, out result : i1) { + // ELIMINATE: %[[CONSTANT_0:.*]] = hw.constant true + // ELIMINATE: %[[FIRREG_0:.*]] = seq.firreg %[[A]] clock %[[CLK]] reset sync %[[CONSTANT_0]], %[[A]] : i1 + // ELIMINATE: hw.output %[[A]] : i1 + // ELIMINATE: } hw.module public @SideEffectPreserved(in %a: i1, in %clk: !seq.clock, out result: i1) { // LIVENESS: hw.constant true // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -329,6 +411,11 @@ module { // LIVENESS-LABEL: hw.module private @SharedModule // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE", "LIVE"] + // ELIMINATE-LABEL: hw.module private @SharedModule(in + // ELIMINATE-SAME: %[[A:.*]] : i1, in + // ELIMINATE-SAME: %[[B:.*]] : i1, out x : i1, out y : i1) { + // ELIMINATE: hw.output %[[A]], %[[B]] : i1, i1 + // ELIMINATE: } hw.module private @SharedModule(in %a: i1, in %b: i1, out x: i1, out y: i1) { // LIVENESS: hw.output // LIVENESS-SAME: "val-liveness" = ["LIVE", "LIVE"] @@ -338,6 +425,11 @@ module { // LIVENESS-LABEL: hw.module public @SharedUser1 // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @SharedUser1(in + // ELIMINATE-SAME: %[[IN:.*]] : i1, out out : i1) { + // ELIMINATE: %[[VAL_0:.*]], %[[INSTANCE_0:.*]] = hw.instance "s1" @SharedModule(a: %[[IN]]: i1, b: %[[IN]]: i1) -> (x: i1, y: i1) + // ELIMINATE: hw.output %[[VAL_0]] : i1 + // ELIMINATE: } hw.module public @SharedUser1(in %in: i1, out out: i1) { // LIVENESS: hw.instance "s1" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -351,6 +443,11 @@ module { // LIVENESS-LABEL: hw.module public @SharedUser2 // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @SharedUser2(in + // ELIMINATE-SAME: %[[IN:.*]] : i1, out out : i1) { + // ELIMINATE: %[[VAL_0:.*]], %[[INSTANCE_0:.*]] = hw.instance "s2" @SharedModule(a: %[[IN]]: i1, b: %[[IN]]: i1) -> (x: i1, y: i1) + // ELIMINATE: hw.output %[[INSTANCE_0]] : i1 + // ELIMINATE: } hw.module public @SharedUser2(in %in: i1, out out: i1) { // LIVENESS: hw.instance "s2" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -367,6 +464,14 @@ module { // LIVENESS-LABEL: hw.module public @DeadOpsVariety // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE", "LIVE", "DEAD"] + // ELIMINATE-LABEL: hw.module public @DeadOpsVariety( + // ELIMINATE-SAME: in %a : i1, + // ELIMINATE-SAME: in %b : i1, + // ELIMINATE-SAME: in %[[CLK:.*]] : !seq.clock, + // ELIMINATE-SAME: out result : i1) { + // ELIMINATE: %[[AND_0:.*]] = comb.and %a, %b : i1 + // ELIMINATE: hw.output %[[AND_0]] : i1 + // ELIMINATE: } hw.module public @DeadOpsVariety(in %a: i1, in %b: i1, in %clk: !seq.clock, out result: i1) { // LIVENESS: comb.or // LIVENESS-SAME: "op-liveness" = "DEAD" @@ -395,6 +500,10 @@ module { // LIVENESS-LABEL: hw.module public @DeadConstantChain // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @DeadConstantChain(in + // ELIMINATE-SAME: %[[A:.*]] : i1, out result : i1) { + // ELIMINATE: hw.output %[[A]] : i1 + // ELIMINATE: } hw.module public @DeadConstantChain(in %a: i1, out result: i1) { // LIVENESS: hw.constant false // LIVENESS-SAME: "op-liveness" = "DEAD" @@ -415,6 +524,11 @@ module { // LIVENESS-LABEL: hw.module private @AlternatingPorts // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE", "DEAD", "LIVE", "DEAD"] + // ELIMINATE-LABEL: hw.module private @AlternatingPorts(in + // ELIMINATE-SAME: %[[LIVE1:.*]] : i1, in + // ELIMINATE-SAME: %[[LIVE2:.*]] : i1, out out1 : i1, out out2 : i1) { + // ELIMINATE: hw.output %[[LIVE1]], %[[LIVE2]] : i1, i1 + // ELIMINATE: } hw.module private @AlternatingPorts( in %live1: i1, in %dead1: i1, in %live2: i1, in %dead2: i1, out out1: i1, out dead_out1: i1, out out2: i1, out dead_out2: i1 @@ -427,6 +541,11 @@ module { // LIVENESS-LABEL: hw.module public @AlternatingUser // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @AlternatingUser(in + // ELIMINATE-SAME: %[[X:.*]] : i1, out r1 : i1, out r2 : i1) { + // ELIMINATE: %[[VAL_0:.*]], %[[INSTANCE_0:.*]] = hw.instance "alt" @AlternatingPorts(live1: %[[X]]: i1, live2: %[[X]]: i1) -> (out1: i1, out2: i1) + // ELIMINATE: hw.output %[[VAL_0]], %[[INSTANCE_0]] : i1, i1 + // ELIMINATE: } hw.module public @AlternatingUser(in %x: i1, out r1: i1, out r2: i1) { // LIVENESS: hw.instance "alt" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -443,11 +562,17 @@ module { // - External modules have unknown implementations // - Instances targeting externals are conservatively kept alive + // ELIMINATE: hw.module.extern private @ExternalMod(in %[[VAL_1:.*]] : i1, out b : i1) hw.module.extern private @ExternalMod(in %a: i1, out b: i1) // LIVENESS-LABEL: hw.module public @UseExternal // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @UseExternal(in + // ELIMINATE-SAME: %[[X:.*]] : i1, out result : i1) { + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "ext" @ExternalMod(a: %[[X]]: i1) -> (b: i1) + // ELIMINATE: hw.output %[[INSTANCE_0]] : i1 + // ELIMINATE: } hw.module public @UseExternal(in %x: i1, out result: i1) { // LIVENESS: hw.instance "ext" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -458,11 +583,17 @@ module { hw.output %b : i1 } + // ELIMINATE: hw.module.extern private @UnusedExternal(in %[[VAL_1]] : i1, out b : i1) hw.module.extern private @UnusedExternal(in %a: i1, out b: i1) // LIVENESS-LABEL: hw.module public @DeadExternal // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @DeadExternal(in + // ELIMINATE-SAME: %[[X:.*]] : i1) { + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "dead_ext" @UnusedExternal(a: %[[X]]: i1) -> (b: i1) + // ELIMINATE: hw.output + // ELIMINATE: } hw.module public @DeadExternal(in %x: i1) { // LIVENESS: hw.instance "dead_ext" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -479,6 +610,10 @@ module { // LIVENESS-LABEL: hw.module private @NativeMod // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module private @NativeMod(in + // ELIMINATE-SAME: %[[X:.*]] : i1, out y : i1) { + // ELIMINATE: hw.output %[[X]] : i1 + // ELIMINATE: } hw.module private @NativeMod(in %x: i1, out y: i1) { // LIVENESS: hw.output // LIVENESS-SAME: "val-liveness" = ["LIVE"] @@ -488,6 +623,11 @@ module { // LIVENESS-LABEL: hw.module public @NativeModUser // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @NativeModUser(in + // ELIMINATE-SAME: %[[VAL_1]] : i1, out result : i1) { + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "native_inst" @NativeMod(x: %[[VAL_1]]: i1) -> (y: i1) + // ELIMINATE: hw.output %[[INSTANCE_0]] : i1 + // ELIMINATE: } hw.module public @NativeModUser(in %a: i1, out result: i1) { // LIVENESS: hw.instance "native_inst" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -503,11 +643,17 @@ module { // markInstanceLike takes the conservative path: the instance and all its // operands are marked alive regardless of output use. + // ELIMINATE: hw.module.extern private @ConservativeExtern(in %[[VAL_0:.*]] : i1, out y : i1) hw.module.extern private @ConservativeExtern(in %x: i1, out y: i1) // LIVENESS-LABEL: hw.module public @ConservativeExternUser // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @ConservativeExternUser(in + // ELIMINATE-SAME: %[[VAL_1]] : i1) { + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "extern_inst" @ConservativeExtern(x: %[[VAL_1]]: i1) -> (y: i1) + // ELIMINATE: hw.output + // ELIMINATE: } hw.module public @ConservativeExternUser(in %a: i1) { // LIVENESS: hw.instance "extern_inst" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -532,6 +678,12 @@ module { // LIVENESS-LABEL: hw.module private @SideEffectMod // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE", "LIVE"] + // ELIMINATE-LABEL: hw.module private @SideEffectMod(in + // ELIMINATE-SAME: %[[VAL_0]] : i1, in + // ELIMINATE-SAME: %[[CLK:.*]] : !seq.clock, out y : i1) { + // ELIMINATE: %[[FIRREG_0:.*]] = seq.firreg %[[VAL_0]] clock %[[CLK]] {firrtl.random_init_start = 0 : ui64} : i1 + // ELIMINATE: hw.output %[[FIRREG_0]] : i1 + // ELIMINATE: } hw.module private @SideEffectMod(in %x: i1, in %clk: !seq.clock, out y: i1) { // LIVENESS: seq.firreg // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -545,6 +697,11 @@ module { // LIVENESS-LABEL: hw.module public @SideEffectModUser // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["DEAD", "DEAD"] + // ELIMINATE-LABEL: hw.module public @SideEffectModUser(in + // ELIMINATE-SAME: %[[VAL_1]] : i1, in + // ELIMINATE-SAME: %[[C:.*]] : !seq.clock) { + // ELIMINATE: hw.output + // ELIMINATE: } hw.module public @SideEffectModUser(in %a: i1, in %c: !seq.clock) { // LIVENESS: hw.instance "side_effect_inst" // LIVENESS-SAME: "op-liveness" = "DEAD" @@ -567,6 +724,10 @@ module { // LIVENESS-LABEL: hw.module private @Gated // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module private @Gated(in + // ELIMINATE-SAME: %[[VAL_0]] : i1, out y : i1) { + // ELIMINATE: hw.output %[[VAL_0]] : i1 + // ELIMINATE: } hw.module private @Gated(in %x: i1, out y: i1) { // LIVENESS: hw.output // LIVENESS-SAME: "val-liveness" = ["LIVE"] @@ -576,6 +737,11 @@ module { // LIVENESS-LABEL: hw.module public @GatedUser // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @GatedUser(in + // ELIMINATE-SAME: %[[VAL_1]] : i1, out result : i1) { + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "g_live" @Gated(x: %[[VAL_1]]: i1) -> (y: i1) + // ELIMINATE: hw.output %[[INSTANCE_0]] : i1 + // ELIMINATE: } hw.module public @GatedUser(in %a: i1, out result: i1) { // LIVENESS: hw.instance "g_live" // LIVENESS-SAME: "op-liveness" = "LIVE" @@ -589,6 +755,10 @@ module { // LIVENESS-LABEL: hw.module public @GatedDead // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["DEAD"] + // ELIMINATE-LABEL: hw.module public @GatedDead(in + // ELIMINATE-SAME: %[[B:.*]] : i1) { + // ELIMINATE: hw.output + // ELIMINATE: } hw.module public @GatedDead(in %b: i1) { // LIVENESS: hw.instance "g_dead" // LIVENESS-SAME: "op-liveness" = "DEAD" @@ -605,11 +775,19 @@ module { // target alive and marks all its operands alive, regardless of output use. // - "ext_live" has its result consumed; "ext_dead" does not — both are LIVE. + // ELIMINATE: hw.module.extern private @MultiExt(in %[[VAL_1]] : i1, out b : i1) hw.module.extern private @MultiExt(in %a: i1, out b: i1) // LIVENESS-LABEL: hw.module public @MultiExtUser // LIVENESS-SAME: "op-liveness" = "LIVE" // LIVENESS-SAME: "val-liveness" = ["LIVE", "LIVE"] + // ELIMINATE-LABEL: hw.module public @MultiExtUser(in + // ELIMINATE-SAME: %[[VAL_0]] : i1, in + // ELIMINATE-SAME: %[[Y:.*]] : i1, out result : i1) { + // ELIMINATE: %[[INSTANCE_0:.*]] = hw.instance "ext_live" @MultiExt(a: %[[VAL_0]]: i1) -> (b: i1) + // ELIMINATE: %[[INSTANCE_1:.*]] = hw.instance "ext_dead" @MultiExt(a: %[[Y]]: i1) -> (b: i1) + // ELIMINATE: hw.output %[[INSTANCE_0]] : i1 + // ELIMINATE: } hw.module public @MultiExtUser(in %x: i1, in %y: i1, out result: i1) { // LIVENESS: hw.instance "ext_live" // LIVENESS-SAME: "op-liveness" = "LIVE" From 094793caf1ecd5020114537f17775cd5b41216f6 Mon Sep 17 00:00:00 2001 From: stomfaig Date: Thu, 26 Mar 2026 17:54:23 +0000 Subject: [PATCH 2/4] resolve comments --- lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp b/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp index b9dae9f0c641..ccf01c7f76b1 100644 --- a/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp +++ b/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp @@ -438,11 +438,11 @@ void HWIMDeadCodeElim::runOnOperation() { if (isBlockExecutable(module.getBodyBlock())) { rewriteModuleSignature(module); } else { - // If the module is unreachable from the toplevel, just delete it. - // Note that post-order traversal on the instance graph never visit - // unreachable modules so it's safe to erase the module even though - // `modules` seems to be capturing module pointers. - module.erase(); + // In some cases erasue here is not yet correct. E.g. + // sv.verbatim "{{0}}" { symbols = [@baz] } + // // Not reachable from the top level. + // hw.module private @baz() {} + // module.erase(); } } @@ -587,7 +587,7 @@ void HWIMDeadCodeElim::rewriteModuleSignature(HWModuleOp module) { for (auto newResult : newInstance.getResults()) liveElements.insert(newResult); - use->erase(); + instanceGraph->replaceInstance(instance, newInstance); instance->erase(); } From d22b5abae7cb659b47bcbcc01c7b1b755e62be09 Mon Sep 17 00:00:00 2001 From: stomfaig Date: Thu, 2 Apr 2026 22:55:43 +0100 Subject: [PATCH 3/4] dont remove empty modules --- .../HW/Transforms/HWIMDeadCodeElim.cpp | 33 ++++++++------- test/Dialect/HW/imdce.mlir | 40 +++++++++++++++++++ 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp b/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp index ccf01c7f76b1..d2056c2a427d 100644 --- a/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp +++ b/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp @@ -432,19 +432,13 @@ void HWIMDeadCodeElim::runOnOperation() { return; } - // Rewrite module signatures or delete unreachable modules. + // Rewrite module signatures. Non-executable modules are still rewritten + // (dead ports stripped, dead instances removed) but are never erased, to + // avoid invalidating symbol references (e.g. sv.verbatim "{{0}}" {symbols = + // [@mod]}). for (auto module : - llvm::make_early_inc_range(getOperation().getOps())) { - if (isBlockExecutable(module.getBodyBlock())) { - rewriteModuleSignature(module); - } else { - // In some cases erasue here is not yet correct. E.g. - // sv.verbatim "{{0}}" { symbols = [@baz] } - // // Not reachable from the top level. - // hw.module private @baz() {} - // module.erase(); - } - } + llvm::make_early_inc_range(getOperation().getOps())) + rewriteModuleSignature(module); for (auto module : llvm::make_early_inc_range(getOperation().getOps())) @@ -461,8 +455,8 @@ void HWIMDeadCodeElim::runOnOperation() { } void HWIMDeadCodeElim::rewriteModuleSignature(HWModuleOp module) { - assert(isBlockExecutable(module.getBodyBlock()) && - "unreachable modules must be already deleted"); + // assert(isBlockExecutable(module.getBodyBlock()) && + // "unreachable modules must be already deleted"); igraph::InstanceGraphNode *instanceGraphNode = instanceGraph->lookup(module); LLVM_DEBUG(llvm::dbgs() << "Prune ports of module: " << module.getName() @@ -595,8 +589,8 @@ void HWIMDeadCodeElim::rewriteModuleSignature(HWModuleOp module) { } void HWIMDeadCodeElim::rewriteModuleBody(HWModuleOp module) { - assert(isBlockExecutable(module.getBodyBlock()) && - "unreachable modules must be already deleted"); + // assert(isBlockExecutable(module.getBodyBlock()) && + // "unreachable modules must be already deleted"); // Walk the IR bottom-up when deleting operations. module.walk( @@ -617,6 +611,11 @@ void HWIMDeadCodeElim::rewriteModuleBody(HWModuleOp module) { } void HWIMDeadCodeElim::eraseEmptyModule(HWModuleOp module) { + // Non-executable modules are preserved as empty shells to avoid invalidating + // symbol references that may point to them by name. + if (!isBlockExecutable(module.getBodyBlock())) + return; + // If the module is not empty, just skip. if (!module.getBodyBlock()->without_terminator().empty()) return; @@ -669,6 +668,6 @@ void HWIMDeadCodeElim::eraseEmptyModule(HWModuleOp module) { return; instanceGraph->erase(instanceGraphNode); - module.erase(); + // module.erase(); ++numErasedModules; } \ No newline at end of file diff --git a/test/Dialect/HW/imdce.mlir b/test/Dialect/HW/imdce.mlir index bf29882e4f87..fe874b331a80 100644 --- a/test/Dialect/HW/imdce.mlir +++ b/test/Dialect/HW/imdce.mlir @@ -802,3 +802,43 @@ module { hw.output %b1 : i1 } } + +// ----- + +// ===== Non-executable module with non-empty body ===== +// - @DeadWithBody is non-executable +// - rewriteModuleSignature runs for all modules, clearing +// dead ports and body +// - Module is kept as an empty shell + +module { + + // LIVENESS-LABEL: hw.module private @DeadWithBody + // LIVENESS-SAME: "op-liveness" = "DEAD" + // LIVENESS-SAME: "val-liveness" = ["DEAD"] + // ELIMINATE-LABEL: hw.module private @DeadWithBody() { + // ELIMINATE: hw.output + // ELIMINATE: } + hw.module private @DeadWithBody(in %x : i1, out y : i1) { + // LIVENESS: hw.constant false + // LIVENESS-SAME: "op-liveness" = "DEAD" + // LIVENESS-SAME: "val-liveness" = ["DEAD"] + %c = hw.constant false + // LIVENESS: hw.output + // LIVENESS-SAME: "val-liveness" = ["DEAD"] + hw.output %c : i1 + } + + // LIVENESS-LABEL: hw.module public @Root + // LIVENESS-SAME: "op-liveness" = "LIVE" + // LIVENESS-SAME: "val-liveness" = ["LIVE"] + // ELIMINATE-LABEL: hw.module public @Root(in + // ELIMINATE-SAME: %[[X:.*]] : i1, out y : i1) { + // ELIMINATE: hw.output %[[X]] : i1 + // ELIMINATE: } + hw.module public @Root(in %x : i1, out y : i1) { + // LIVENESS: hw.output + // LIVENESS-SAME: "val-liveness" = ["LIVE"] + hw.output %x : i1 + } +} From 38ad68f360729d45998d15e3e18343ca15710c51 Mon Sep 17 00:00:00 2001 From: stomfaig Date: Fri, 3 Apr 2026 22:51:27 +0100 Subject: [PATCH 4/4] remove commented code --- lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp b/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp index d2056c2a427d..99696d41d288 100644 --- a/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp +++ b/lib/Dialect/HW/Transforms/HWIMDeadCodeElim.cpp @@ -455,8 +455,6 @@ void HWIMDeadCodeElim::runOnOperation() { } void HWIMDeadCodeElim::rewriteModuleSignature(HWModuleOp module) { - // assert(isBlockExecutable(module.getBodyBlock()) && - // "unreachable modules must be already deleted"); igraph::InstanceGraphNode *instanceGraphNode = instanceGraph->lookup(module); LLVM_DEBUG(llvm::dbgs() << "Prune ports of module: " << module.getName() @@ -589,8 +587,6 @@ void HWIMDeadCodeElim::rewriteModuleSignature(HWModuleOp module) { } void HWIMDeadCodeElim::rewriteModuleBody(HWModuleOp module) { - // assert(isBlockExecutable(module.getBodyBlock()) && - // "unreachable modules must be already deleted"); // Walk the IR bottom-up when deleting operations. module.walk( @@ -668,6 +664,5 @@ void HWIMDeadCodeElim::eraseEmptyModule(HWModuleOp module) { return; instanceGraph->erase(instanceGraphNode); - // module.erase(); ++numErasedModules; } \ No newline at end of file