CIRCT 23.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// Unlike MLIR's general CSE pass, this is domain-specific to AIG
11// 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/ADT/TypeSwitch.h"
31#include "llvm/Support/DebugLog.h"
32#include "llvm/Support/LogicalResult.h"
33
34#define DEBUG_TYPE "synth-structural-hash"
35
36namespace circt {
37namespace synth {
38#define GEN_PASS_DEF_STRUCTURALHASH
39#include "circt/Dialect/Synth/Transforms/SynthPasses.h.inc"
40} // namespace synth
41} // namespace circt
42
43using namespace circt;
44using namespace circt::synth;
45
46/// A struct that represents the key used for structural hashing. It contains
47/// the operation name and a sorted vector of pointer-integer pairs, which
48/// represent the inputs to the operation and their inversion status.
49/// This key is used to identify structurally equivalent operations for CSE.
51 OperationName opName;
52 llvm::SmallVector<llvm::PointerIntPair<Value, 1>, 3> operandPairs;
53
54 /// Constructor.
55 StructuralHashKey(OperationName name,
56 llvm::SmallVector<llvm::PointerIntPair<Value, 1>, 3> inps)
57 : opName(name), operandPairs(std::move(inps)) {}
58};
59
60// DenseMapInfo specialization for StructuralHashKey
61template <>
63 static unsigned getHashValue(const StructuralHashKey &key) {
64 auto hash = hash_value(key.opName);
65 for (const auto &operand : key.operandPairs)
66 hash = llvm::hash_combine(hash, operand.getOpaqueValue());
67 return static_cast<unsigned>(hash);
68 }
69
70 static bool isEqual(const StructuralHashKey &lhs,
71 const StructuralHashKey &rhs) {
73 lhs.operandPairs == rhs.operandPairs;
74 }
75};
76
77namespace {
78/// Pass definition.
79struct StructuralHashPass
80 : public impl::StructuralHashBase<StructuralHashPass> {
81 void runOnOperation() override;
82};
83} // namespace
84
85namespace {
86/// The main driver class that implements the structural hashing algorithm.
87/// This class manages the state for value numbering, inversion tracking,
88/// and the hash table for CSE. It processes operations in topological order
89/// and performs operand reordering and inversion propagation for
90/// canonicalization.
91class StructuralHashDriver {
92public:
93 StructuralHashDriver() = default;
94 void visitOp(BooleanLogicOpInterface op);
95 void visitUnaryOp(BooleanLogicOpInterface op);
96 void visitVariadicOp(BooleanLogicOpInterface op);
97 uint64_t getNumber(Value v);
98
99 /// Runs the structural hashing pass on the given operation.
100 /// Performs topological sorting, assigns value numbers to arguments,
101 /// processes target operations, and cleans up unused operations.
102 llvm::LogicalResult run(Operation *op);
103
104private:
105 /// Runs the structural hashing pass on the given block.
106 llvm::LogicalResult runOnBlock(Block &block);
107
108 /// Maps values to unique numbers for deterministic operand sorting.
109 DenseMap<Value, uint64_t> valueNumber;
110 uint64_t constantCounter = 0;
111
112 /// Hash table mapping structural keys to canonical operations for CSE.
113 DenseMap<StructuralHashKey, BooleanLogicOpInterface> hashTable;
114
115 /// Maps inverted values to their non-inverted equivalents for propagation.
116 /// For example, if we have:
117 /// ```
118 /// %b = synth.aig.and_inv not %a
119 /// %c = synth.aig.and_inv not %b
120 /// ```
121 /// Then `inversion[%b] = %a`, and when visiting `%c`, we can query
122 /// `inversion[%b]` to directly obtain `%a`.
123 DenseMap<Value, Value> inversion;
124};
125} // namespace
126
127void StructuralHashDriver::visitOp(BooleanLogicOpInterface op) {
128 /// Dispatches to the appropriate visitor based on the number of operands.
129 /// For unary operations, calls visitUnaryOp; for variadic operations,
130 /// calls visitVariadicOp.
131 if (op.getInputs().size() == 1) {
132 visitUnaryOp(op);
133 return;
134 }
135 visitVariadicOp(op);
136}
137
138/// Handles unary operations (single operand).
139/// If not inverted, replaces the operation with its operand.
140/// If inverted, attempts to propagate inversion through the inversion map
141/// or records the inversion for later propagation.
142void StructuralHashDriver::visitUnaryOp(BooleanLogicOpInterface logicOp) {
143 Operation *op = logicOp.getOperation();
144 auto [input, inverted] = logicOp.getInputPair(0);
145 if (!inverted) {
146 op->replaceAllUsesWith(ArrayRef<Value>{input});
147 op->erase();
148 return;
149 }
150 auto it = inversion.find(input);
151 if (it != inversion.end()) {
152 // Found, replace the operand with the mapped value
153 op->replaceAllUsesWith(ArrayRef<Value>{it->second});
154 op->erase();
155 } else {
156 // Not found, insert into the map
157 inversion[logicOp.getResult()] = input;
158 }
159}
160
161/// Computes a structural hash key, sorts operands for canonicalization,
162/// and performs CSE by checking the hash table for equivalent operations.
163void StructuralHashDriver::visitVariadicOp(BooleanLogicOpInterface logicOp) {
164 Operation *op = logicOp.getOperation();
165 auto inversions = logicOp.getInverted();
166
167 // Compute the structural hash key for the operation.
168 StructuralHashKey key(op->getName(), {});
169 for (auto [input, inverted] : llvm::zip(op->getOperands(), inversions)) {
170 bool isInverted = inverted;
171 // Check if we can propagate inversion through the inversion map
172 auto it = inversion.find(input);
173 if (it != inversion.end()) {
174 // Found, use the mapped value and flip the inversion status
175 input = it->second;
176 isInverted = !isInverted;
177 }
178
179 key.operandPairs.push_back(
180 llvm::PointerIntPair<Value, 1>(input, isInverted));
181 // Ensure the operand has a number assigned, otherwise sorting might be
182 // non-deterministic.
183 (void)getNumber(input);
184 }
185
186 // Canonicalize operand order only when the operation semantics permit
187 // reordering full (input, inverted) pairs.
188 if (logicOp.areInputsPermutationInvariant()) {
189 llvm::sort(key.operandPairs, [&](auto a, auto b) {
190 size_t aNum = getNumber(a.getPointer());
191 size_t bNum = getNumber(b.getPointer());
192 if (aNum != bNum)
193 return aNum < bNum;
194 return a.getInt() < b.getInt();
195 });
196 }
197
198 // Insert the key into the hash table.
199 auto [it, inserted] = hashTable.try_emplace(key, logicOp);
200 if (inserted) {
201 // New entry, keep the operation and sort its operands.
202 op->setOperands(llvm::to_vector<3>(llvm::map_range(
203 key.operandPairs, [](auto p) { return p.getPointer(); })));
204 SmallVector<bool, 3> newInversion(
205 llvm::map_range(key.operandPairs, [](auto p) { return p.getInt(); }));
206 logicOp.setInverted(newInversion);
207 // Assign a number to the result for future sorting.
208 (void)getNumber(logicOp.getResult());
209 } else {
210 LDBG() << "Structural Hash: Replacing " << *op << " with " << *(it->second)
211 << "\n";
212 // Existing entry, replace all uses and erase the operation.
213 // Propagate namehints.
214 auto name = circt::chooseName(op, it->second);
215 if (name && !it->second->hasAttr("sv.namehint"))
216 it->second->setAttr("sv.namehint", name);
217 op->replaceAllUsesWith(it->second);
218 op->erase();
219 }
220}
221
222/// Assigns or retrieves a unique number for a value. Used for deterministic
223/// operand sorting.
224uint64_t StructuralHashDriver::getNumber(Value v) {
225 auto it = valueNumber.find(v);
226 if (it != valueNumber.end())
227 return it->second;
228
229 // Assign a new number. Constants get high numbers to make constants are
230 // pushed to the back.
231 if (auto *op = v.getDefiningOp();
232 op && op->hasTrait<mlir::OpTrait::ConstantLike>()) {
233 auto [it, inserted] = valueNumber.try_emplace(
234 v, std::numeric_limits<uint64_t>::max() - constantCounter++);
235 return it->second;
236 }
237
238 return valueNumber.try_emplace(v, valueNumber.size() - constantCounter)
239 .first->second;
240}
241
242llvm::LogicalResult StructuralHashDriver::runOnBlock(Block &block) {
243 hashTable.clear();
244
245 auto isOperationReady = [&](Value value, Operation *op) -> bool {
246 // Other than target ops, all other ops are always ready.
247 return !isa<BooleanLogicOpInterface>(op);
248 };
249
250 if (!mlir::sortTopologically(&block, isOperationReady))
251 return failure();
252
253 for (auto arg : block.getArguments())
254 (void)getNumber(arg);
255
256 // Process target ops.
257 for (auto op :
258 llvm::make_early_inc_range(block.getOps<BooleanLogicOpInterface>())) {
259 visitOp(op);
260 }
261
262 return mlir::success();
263}
264
265llvm::LogicalResult StructuralHashDriver::run(Operation *moduleOp) {
266 auto result = moduleOp->walk([&](Block *block) {
267 return failed(runOnBlock(*block)) ? WalkResult::interrupt()
268 : WalkResult::advance();
269 });
270 if (result.wasInterrupted())
271 return failure();
272
273 // Run DCE to remove dangling ops.
274 mlir::PatternRewriter rewriter(moduleOp->getContext());
275 (void)mlir::runRegionDCE(rewriter, moduleOp->getRegions());
276 return mlir::success();
277}
278
279void StructuralHashPass::runOnOperation() {
280 auto *topOp = getOperation();
281 StructuralHashDriver driver;
282 if (failed(driver.run(topOp)))
283 return signalPassFailure();
284}
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:2405
llvm::hash_code hash_value(const DenseSet< T > &set)
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 bool isEqual(const StructuralHashKey &lhs, const StructuralHashKey &rhs)
static unsigned getHashValue(const StructuralHashKey &key)