CIRCT 22.0.0git
Loading...
Searching...
No Matches
StructuralHash.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This pass performs structural hashing for Synth dialect operations
10// (AIG/MIG). Unlike MLIR's general CSE pass, this is domain-specific to
11// AIG/MIG operations, allowing it to reorder operands based on their
12// structural properties and take inversion flags into account for
13// canonicalization.
14//
15//===----------------------------------------------------------------------===//
16
21#include "mlir/Analysis/TopologicalSortUtils.h"
22#include "mlir/IR/BuiltinAttributes.h"
23#include "mlir/IR/Operation.h"
24#include "mlir/IR/Visitors.h"
25#include "mlir/Support/LLVM.h"
26#include "mlir/Transforms/RegionUtils.h"
27#include "llvm/ADT/DenseMap.h"
28#include "llvm/ADT/DenseMapInfo.h"
29#include "llvm/ADT/PointerIntPair.h"
30#include "llvm/Support/DebugLog.h"
31#include "llvm/Support/LogicalResult.h"
32
33#define DEBUG_TYPE "synth-structural-hash"
34
35namespace circt {
36namespace synth {
37#define GEN_PASS_DEF_STRUCTURALHASH
38#include "circt/Dialect/Synth/Transforms/SynthPasses.h.inc"
39} // namespace synth
40} // namespace circt
41
42using namespace circt;
43using namespace circt::synth;
44
45/// A struct that represents the key used for structural hashing. It contains
46/// the operation name and a sorted vector of pointer-integer pairs, which
47/// represent the inputs to the operation and their inversion status.
48/// This key is used to identify structurally equivalent operations for CSE.
50 OperationName opName;
51 llvm::SmallVector<llvm::PointerIntPair<Value, 1>, 3> operandPairs;
52
53 /// Constructor.
54 StructuralHashKey(OperationName name,
55 llvm::SmallVector<llvm::PointerIntPair<Value, 1>, 3> inps)
56 : opName(name), operandPairs(std::move(inps)) {}
57};
58
59// DenseMapInfo specialization for StructuralHashKey
60template <>
66
71
72 static unsigned getHashValue(const StructuralHashKey &key) {
73 auto hash = hash_value(key.opName);
74 for (const auto &operand : key.operandPairs)
75 hash = llvm::hash_combine(hash, operand.getOpaqueValue());
76 return static_cast<unsigned>(hash);
77 }
78
79 static bool isEqual(const StructuralHashKey &lhs,
80 const StructuralHashKey &rhs) {
82 lhs.operandPairs == rhs.operandPairs;
83 }
84};
85
86namespace {
87/// Pass definition.
88struct StructuralHashPass
89 : public impl::StructuralHashBase<StructuralHashPass> {
90 void runOnOperation() override;
91};
92} // namespace
93
94namespace {
95/// The main driver class that implements the structural hashing algorithm.
96/// This class manages the state for value numbering, inversion tracking,
97/// and the hash table for CSE. It processes operations in topological order
98/// and performs operand reordering and inversion propagation for
99/// canonicalization.
100class StructuralHashDriver {
101public:
102 StructuralHashDriver() = default;
103 void visitOp(Operation *op, ArrayRef<bool> inverted);
104 void visitUnaryOp(Operation *op, bool inverted);
105 void visitVariadicOp(Operation *op, ArrayRef<bool> inverted);
106 uint64_t getNumber(Value v);
107
108 /// Runs the structural hashing pass on the given module.
109 /// Performs topological sorting, assigns value numbers to arguments,
110 /// processes target operations, and cleans up unused operations.
111 llvm::LogicalResult run(hw::HWModuleOp op);
112
113private:
114 /// Maps values to unique numbers for deterministic operand sorting.
115 DenseMap<Value, uint64_t> valueNumber;
116 uint64_t constantCounter = 0;
117
118 /// Hash table mapping structural keys to canonical operations for CSE.
119 DenseMap<StructuralHashKey, Operation *> hashTable;
120
121 /// Maps inverted values to their non-inverted equivalents for propagation.
122 /// For example, if we have:
123 /// ```
124 /// %b = synth.aig.and_inv not %a
125 /// %c = synth.aig.and_inv not %b
126 /// ```
127 /// Then `inversion[%b] = %a`, and when visiting `%c`, we can query
128 /// `inversion[%b]` to directly obtain `%a`.
129 DenseMap<Value, Value> inversion;
130};
131} // namespace
132
133void StructuralHashDriver::visitOp(Operation *op, ArrayRef<bool> inverted) {
134 /// Dispatches to the appropriate visitor based on the number of operands.
135 /// For unary operations, calls visitUnaryOp; for variadic operations,
136 /// calls visitVariadicOp.
137 if (op->getNumOperands() == 1) {
138 visitUnaryOp(op, inverted[0]);
139 return;
140 }
141 visitVariadicOp(op, inverted);
142}
143
144/// Handles unary operations (single operand).
145/// If not inverted, replaces the operation with its operand.
146/// If inverted, attempts to propagate inversion through the inversion map
147/// or records the inversion for later propagation.
148void StructuralHashDriver::visitUnaryOp(Operation *op, bool inverted) {
149 if (!inverted) {
150 op->replaceAllUsesWith(ArrayRef<Value>{op->getOperand(0)});
151 op->erase();
152 return;
153 }
154 // Check if we can propagate inversion through the inversion map.
155 auto operand = op->getOperand(0);
156 auto it = inversion.find(operand);
157 if (it != inversion.end()) {
158 // Found, replace the operand with the mapped value
159 op->replaceAllUsesWith(ArrayRef<Value>{it->second});
160 op->erase();
161 } else {
162 // Not found, insert into the map
163 inversion[op->getResult(0)] = operand;
164 }
165}
166
167/// Computes a structural hash key, sorts operands for canonicalization,
168/// and performs CSE by checking the hash table for equivalent operations.
169void StructuralHashDriver::visitVariadicOp(Operation *op,
170 ArrayRef<bool> inverted) {
171
172 // Compute the structural hash key for the operation.
173 StructuralHashKey key(op->getName(), {});
174 for (auto [input, inverted] : llvm::zip(op->getOperands(), inverted)) {
175 bool isInverted = inverted;
176 // Check if we can propagate inversion through the inversion map
177 auto it = inversion.find(input);
178 if (it != inversion.end()) {
179 // Found, use the mapped value and flip the inversion status
180 input = it->second;
181 isInverted = !isInverted;
182 }
183
184 key.operandPairs.push_back(
185 llvm::PointerIntPair<Value, 1>(input, isInverted));
186 // Ensure the operand has a number assigned, otherwise sorting might be
187 // non-deterministic.
188 (void)getNumber(input);
189 }
190
191 // Sort operands based on their assigned numbers.
192 llvm::sort(key.operandPairs, [&](auto a, auto b) {
193 size_t aNum = getNumber(a.getPointer());
194 size_t bNum = getNumber(b.getPointer());
195 if (aNum != bNum)
196 return aNum < bNum;
197 return a.getInt() < b.getInt();
198 });
199
200 // Insert the key into the hash table.
201 auto [it, inserted] = hashTable.try_emplace(key, op);
202 if (inserted) {
203 // New entry, keep the operation and sort its operands.
204 op->setOperands(llvm::to_vector<3>(llvm::map_range(
205 key.operandPairs, [](auto p) { return p.getPointer(); })));
206 SmallVector<bool, 3> newInversion(
207 llvm::map_range(key.operandPairs, [](auto p) { return p.getInt(); }));
208 op->setAttr("inverted",
209 mlir::DenseBoolArrayAttr::get(op->getContext(), newInversion));
210 // Assign a number to the result for future sorting.
211 (void)getNumber(op->getResult(0));
212 } else {
213 LDBG() << "Structural Hash: Replacing " << *op << " with " << *(it->second)
214 << "\n";
215 // Existing entry, replace all uses and erase the operation.
216 // Propagate namehints.
217 auto name = circt::chooseName(op, it->second);
218 if (name && !it->second->hasAttr("sv.namehint"))
219 it->second->setAttr("sv.namehint", name);
220 op->replaceAllUsesWith(it->second);
221 op->erase();
222 }
223}
224
225/// Assigns or retrieves a unique number for a value. Used for deterministic
226/// operand sorting.
227uint64_t StructuralHashDriver::getNumber(Value v) {
228 auto it = valueNumber.find(v);
229 if (it != valueNumber.end())
230 return it->second;
231
232 // Assign a new number. Constants get high numbers to make constants are
233 // pushed to the back.
234 if (auto *op = v.getDefiningOp();
235 op && op->hasTrait<mlir::OpTrait::ConstantLike>()) {
236 auto [it, inserted] = valueNumber.try_emplace(
237 v, std::numeric_limits<uint64_t>::max() - constantCounter++);
238 return it->second;
239 }
240
241 return valueNumber.try_emplace(v, valueNumber.size() - constantCounter)
242 .first->second;
243}
244
245llvm::LogicalResult StructuralHashDriver::run(hw::HWModuleOp moduleOp) {
246 auto isOperationReady = [&](Value value, Operation *op) -> bool {
247 // Otherthan target ops, all other ops are always ready.
248 return !isa<circt::synth::aig::AndInverterOp,
249 circt::synth::mig::MajorityInverterOp>(op);
250 };
251
252 if (!mlir::sortTopologically(moduleOp.getBodyBlock(), isOperationReady))
253 return failure();
254
255 for (auto arg : moduleOp.getBodyBlock()->getArguments())
256 (void)getNumber(arg);
257
258 // Process target ops.
259 // NOTE: Don't use walk here since the pass currently doesn't handle nested
260 // regions.
261 for (auto &op :
262 llvm::make_early_inc_range(moduleOp.getBodyBlock()->getOperations())) {
263 mlir::TypeSwitch<Operation *>(&op)
264 .Case<circt::synth::aig::AndInverterOp,
265 circt::synth::mig::MajorityInverterOp>([&](auto invertibleOp) {
266 visitOp(invertibleOp, invertibleOp.getInverted());
267 })
268 .Default([&](Operation *op) {});
269 }
270
271 // Run DCE to remove dangling ops.
272 mlir::PatternRewriter rewriter(moduleOp.getContext());
273 (void)mlir::runRegionDCE(rewriter, moduleOp->getRegions());
274 return mlir::success();
275}
276
277void StructuralHashPass::runOnOperation() {
278 auto topOp = getOperation();
279 StructuralHashDriver driver;
280 if (failed(driver.run(topOp)))
281 return signalPassFailure();
282}
static Block * getBodyBlock(FModuleLike mod)
static llvm::hash_code hash_value(const ModulePort &port)
Definition HWTypes.h:38
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
StringRef chooseName(StringRef a, StringRef b)
Choose a good name for an item from two options.
Definition Naming.cpp:47
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition codegen.py:121
Definition synth.py:1
A struct that represents the key used for structural hashing.
StructuralHashKey(OperationName name, llvm::SmallVector< llvm::PointerIntPair< Value, 1 >, 3 > inps)
Constructor.
llvm::SmallVector< llvm::PointerIntPair< Value, 1 >, 3 > operandPairs
OperationName opName
static StructuralHashKey getTombstoneKey()
static bool isEqual(const StructuralHashKey &lhs, const StructuralHashKey &rhs)
static StructuralHashKey getEmptyKey()
static unsigned getHashValue(const StructuralHashKey &key)