Skip to content

Commit 20cb546

Browse files
authored
[FIRRTL][Dedup] Rework hashing for perf and bug fixes. (#7420)
Primary change is to only generate and populate mappings for sources of values, and not each value themselves. Values are identified using these base numberings plus an appropriate offset. The main benefit of this is to greatly reduce the number of entries in the `indices` map. When handling operations with many block arguments (module-like's with many ports) or with many results (instances of those module-like's) this greatly reduces the pressure on the `indices` map. For these designs, dedup now runs dramatically faster and uses significantly less memory. Also separates location of the value impl, such that if a Value's impl is storage inline into an Operation or Block such that there is aliasing, the two are given different numbers (and especially the numbering isn't changed). On a synthetic design containing a module with 2^20 ports and 256 instances of that module, this is the difference between completing in 20s and OOM'ing on my machine after running for 30 minutes. Functional changes: Fixes #7415. Fixes #7416. Also fixes deduping if block arg types are different (but unused). This is done by hashing block count, and each block's numbering between as well as the types of its arguments before that block's operations. Additionally fixes use of numberings (indices) before it was populated where attribute processing for inner symbol ports hashed using the block argument's numbering before it was populated.
1 parent 2dbab26 commit 20cb546

File tree

2 files changed

+140
-28
lines changed

2 files changed

+140
-28
lines changed

lib/Dialect/FIRRTL/Transforms/Dedup.cpp

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,17 @@ struct ModuleInfo {
7878
mlir::ArrayAttr referredModuleNames;
7979
};
8080

81-
struct SymbolTarget {
81+
/// Unique identifier for a value. All value sources are numbered by apperance,
82+
/// and values are identified using this numbering (`index`) and an `offset`.
83+
/// For BlockArgument's, this is the argument number.
84+
/// For OpResult's, this is the result number.
85+
struct ValueId {
8286
uint64_t index;
87+
uint64_t offset;
88+
};
89+
90+
struct SymbolTarget {
91+
ValueId index;
8392
uint64_t fieldID;
8493
};
8594

@@ -161,14 +170,19 @@ struct StructuralHasher {
161170

162171
void record(void *address) {
163172
auto size = indices.size();
173+
assert(!indices.contains(address));
164174
indices[address] = size;
165175
}
166176

167-
void update(BlockArgument arg) { record(arg.getAsOpaquePointer()); }
177+
/// Get the unique id for the specified value.
178+
ValueId getId(Value val) {
179+
if (auto arg = dyn_cast<BlockArgument>(val))
180+
return {indices.at(arg.getOwner()), arg.getArgNumber()};
181+
auto result = cast<OpResult>(val);
182+
return {indices.at(result.getOwner()), result.getResultNumber()};
183+
}
168184

169185
void update(OpResult result) {
170-
record(result.getAsOpaquePointer());
171-
172186
// Like instance ops, don't use object ops' result types since they might be
173187
// replaced by dedup. Record the class names and lazily combine their hashes
174188
// using the same mechanism as instances and modules.
@@ -180,23 +194,23 @@ struct StructuralHasher {
180194
update(result.getType());
181195
}
182196

183-
void update(OpOperand &operand) {
184-
// We hash the value's index as it apears in the block.
185-
auto it = indices.find(operand.get().getAsOpaquePointer());
186-
assert(it != indices.end() && "op should have been previously hashed");
187-
update(it->second);
197+
void update(ValueId index) {
198+
update(index.index);
199+
update(index.offset);
188200
}
189201

202+
void update(OpOperand &operand) { update(getId(operand.get())); }
203+
190204
void update(Operation *op, hw::InnerSymAttr attr) {
191205
for (auto props : attr)
192206
innerSymTargets[props.getName()] =
193-
SymbolTarget{indices[op], props.getFieldID()};
207+
SymbolTarget{{indices.at(op), 0}, props.getFieldID()};
194208
}
195209

196210
void update(Value value, hw::InnerSymAttr attr) {
197211
for (auto props : attr)
198212
innerSymTargets[props.getName()] =
199-
SymbolTarget{indices[value.getAsOpaquePointer()], props.getFieldID()};
213+
SymbolTarget{getId(value), props.getFieldID()};
200214
}
201215

202216
void update(const SymbolTarget &target) {
@@ -281,15 +295,6 @@ struct StructuralHasher {
281295
}
282296
}
283297

284-
void update(Block &block) {
285-
// Hash the block arguments.
286-
for (auto arg : block.getArguments())
287-
update(arg);
288-
// Hash the operations in the block.
289-
for (auto &op : block)
290-
update(&op);
291-
}
292-
293298
void update(mlir::OperationName name) {
294299
// Operation names are interned.
295300
update(name.getAsOpaquePointer());
@@ -299,26 +304,44 @@ struct StructuralHasher {
299304
void update(Operation *op) {
300305
record(op);
301306
update(op->getName());
302-
update(op, op->getAttrDictionary());
307+
303308
// Hash the operands.
304309
for (auto &operand : op->getOpOperands())
305310
update(operand);
311+
312+
// Number the block pointers, for use numbering their arguments.
313+
for (auto &region : op->getRegions())
314+
for (auto &block : region.getBlocks())
315+
record(&block);
316+
317+
// This happens after the numbering above, as it uses blockarg numbering
318+
// for inner symbols.
319+
update(op, op->getAttrDictionary());
320+
306321
// Hash the regions. We need to make sure an empty region doesn't hash the
307322
// same as no region, so we include the number of regions.
308323
update(op->getNumRegions());
309-
for (auto &region : op->getRegions())
310-
for (auto &block : region.getBlocks())
311-
update(block);
312-
// Record any op results.
324+
for (auto &region : op->getRegions()) {
325+
update(region.getBlocks().size());
326+
for (auto &block : region.getBlocks()) {
327+
update(indices.at(&block));
328+
for (auto argType : block.getArgumentTypes())
329+
update(argType);
330+
for (auto &op : block)
331+
update(&op);
332+
}
333+
}
334+
335+
// Record any op results (types).
313336
for (auto result : op->getResults())
314337
update(result);
315338
}
316339

317-
// Every operation and value is assigned a unique id based on their order of
318-
// appearance
340+
// Every operation and block is assigned a unique id based on their order of
341+
// appearance. All values are uniquely identified using these.
319342
DenseMap<void *, unsigned> indices;
320343

321-
// Every value is assigned a unique id based on their order of appearance.
344+
// Track inner symbol name -> target's unique identification.
322345
DenseMap<StringAttr, SymbolTarget> innerSymTargets;
323346

324347
// This keeps track of module names in the order of the appearance.

test/Dialect/FIRRTL/dedup-errors.mlir

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,95 @@ firrtl.circuit "MustDedup" attributes {annotations = [{
290290

291291
// -----
292292

293+
// Check same number of blocks but instructions across are same.
294+
// https://github.com/llvm/circt/issues/7415
295+
// expected-error@below {{module "Test1" not deduplicated with "Test0"}}
296+
firrtl.circuit "SameInstDiffBlock" attributes {annotations = [{
297+
class = "firrtl.transforms.MustDeduplicateAnnotation",
298+
modules = ["~SameInstDiffBlock|Test0", "~SameInstDiffBlock|Test1"]
299+
}]} {
300+
firrtl.module private @Test0(in %a : !firrtl.uint<1>) {
301+
"test"()({
302+
^bb0:
303+
// expected-note@below {{first block has more operations}}
304+
"return"() : () -> ()
305+
}, {
306+
^bb0:
307+
}) : () -> ()
308+
}
309+
firrtl.module private @Test1(in %a : !firrtl.uint<1>) {
310+
// expected-note@below {{second block here}}
311+
"test"() ({
312+
^bb0:
313+
}, {
314+
^bb0:
315+
"return"() : () -> ()
316+
}): () -> ()
317+
}
318+
firrtl.module @SameInstDiffBlock() {
319+
firrtl.instance test0 @Test0(in a : !firrtl.uint<1>)
320+
firrtl.instance test1 @Test1(in a : !firrtl.uint<1>)
321+
}
322+
}
323+
324+
// -----
325+
326+
// Check differences in block arguments.
327+
// expected-error@below {{module "Test1" not deduplicated with "Test0"}}
328+
firrtl.circuit "BlockArgTypes" attributes {annotations = [{
329+
class = "firrtl.transforms.MustDeduplicateAnnotation",
330+
modules = ["~BlockArgTypes|Test0", "~BlockArgTypes|Test1"]
331+
}]} {
332+
firrtl.module private @Test0(in %a : !firrtl.uint<1>) {
333+
// expected-note@below {{types don't match, first type is 'i32'}}
334+
"test"()({
335+
^bb0(%arg0 : i32):
336+
"return"() : () -> ()
337+
}) : () -> ()
338+
}
339+
firrtl.module private @Test1(in %a : !firrtl.uint<1>) {
340+
// expected-note@below {{second type is 'i64'}}
341+
"test"() ({
342+
^bb0(%arg0 : i64):
343+
"return"() : () -> ()
344+
}): () -> ()
345+
}
346+
firrtl.module @BlockArgTypes() {
347+
firrtl.instance test0 @Test0(in a : !firrtl.uint<1>)
348+
firrtl.instance test1 @Test1(in a : !firrtl.uint<1>)
349+
}
350+
}
351+
352+
// -----
353+
354+
// Check empty block not same as no block.
355+
// https://github.com/llvm/circt/issues/7416
356+
// expected-error@below {{module "B" not deduplicated with "A"}}
357+
firrtl.circuit "NoBlockEmptyBlock" attributes {annotations = [{
358+
class = "firrtl.transforms.MustDeduplicateAnnotation",
359+
modules = ["~NoBlockEmptyBlock|A", "~NoBlockEmptyBlock|B"]
360+
}]} {
361+
firrtl.module private @A(in %x: !firrtl.uint<1>) {
362+
// expected-note @below {{operation regions have different number of blocks}}
363+
firrtl.when %x : !firrtl.uint<1> {
364+
}
365+
}
366+
firrtl.module private @B(in %x: !firrtl.uint<1>) {
367+
// expected-note @below {{second operation here}}
368+
firrtl.when %x : !firrtl.uint<1> {
369+
} else {
370+
}
371+
}
372+
firrtl.module @NoBlockEmptyBlock(in %x: !firrtl.uint<1>) {
373+
%a_x = firrtl.instance a @A(in x: !firrtl.uint<1>)
374+
firrtl.matchingconnect %a_x, %x : !firrtl.uint<1>
375+
%b_x = firrtl.instance b @B(in x: !firrtl.uint<1>)
376+
firrtl.matchingconnect %b_x, %x : !firrtl.uint<1>
377+
}
378+
}
379+
380+
// -----
381+
293382
// expected-error@below {{module "Test1" not deduplicated with "Test0"}}
294383
firrtl.circuit "MustDedup" attributes {annotations = [{
295384
class = "firrtl.transforms.MustDeduplicateAnnotation",

0 commit comments

Comments
 (0)