Skip to content

Commit 95535c5

Browse files
rocallahanjpienaar
andauthored
[ImportVerilog] Capture analysis should skip reprocessing identical module instances (#10338)
Currently capture analysis unconditionally descends into all module instance bodies. This forces slang to instantiate complete ASTs for all module instances, transitively. For large designs this does not scale. For example with one 7M LOC Verilog example, ImportVerilog consumes more than 200GB of memory before crashing with OOM. Slang lets clients avoid this problem via "canonical instance bodies" that express deduplication. Setting `VisitCanonical` to true in the `ASTVisitor` template causes the `InstanceBodySymbol` visit to use the deduplicated canonical instance body (when available). For this analysis we don't need to re-analyze duplicate instances, so we can just keep track of which bodies we've visited and avoid descending into already-visited bodies. Co-authored-by: Jacques Pienaar <jacques+gh@japienaar.info>
1 parent 5ae6aef commit 95535c5

3 files changed

Lines changed: 46 additions & 59 deletions

File tree

lib/Conversion/ImportVerilog/CaptureAnalysis.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ namespace {
8080
/// visiting enabled so that we recurse into all function bodies.
8181
struct CaptureWalker
8282
: public ASTVisitor<CaptureWalker, /*VisitStatements=*/true,
83-
/*VisitExpressions=*/true> {
83+
/*VisitExpressions=*/true,
84+
/*VisitBad=*/false,
85+
/*VisitCanonical=*/true> {
8486

8587
/// The function whose body we are currently inside, or nullptr if we are at
8688
/// a scope outside any function.
@@ -89,6 +91,8 @@ struct CaptureWalker
8991
/// Captured variables per function.
9092
CaptureMap capturedVars;
9193

94+
mlir::DenseSet<const InstanceBodySymbol *> visitedInstanceBodies;
95+
9296
/// Inverse call graph: maps each callee to the set of callers that call it.
9397
/// Used to propagate captures from callees to their callers. Uses MapVector
9498
/// for deterministic iteration order during propagation.
@@ -150,6 +154,15 @@ struct CaptureWalker
150154
visitDefault(expr);
151155
}
152156

157+
/// `VisitCanonical` is set above, so this visits the canonical instance body
158+
/// if there is one. If there is and we've already visited it via some
159+
/// other instance, don't process it again.
160+
void handle(const InstanceBodySymbol &instance) {
161+
if (visitedInstanceBodies.insert(&instance).second) {
162+
visitDefault(instance);
163+
}
164+
}
165+
153166
/// Propagate captures transitively through the call graph. For each callee
154167
/// that has captures, push each captured variable upward through all
155168
/// transitive callers using a worklist. A captured variable is only

lib/Conversion/ImportVerilog/ImportVerilogInternals.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ namespace ImportVerilog {
3333

3434
using moore::Domain;
3535

36+
/// Get the slang canonical body for the given `instance`, if there is one.
37+
/// If there isn't one, fall back to the normal body.
38+
inline const slang::ast::InstanceBodySymbol *
39+
getCanonicalBody(const slang::ast::InstanceSymbol &inst) {
40+
const slang::ast::InstanceBodySymbol *body = inst.getCanonicalBody();
41+
return body == nullptr ? &inst.body : body;
42+
}
43+
3644
/// Port lowering information.
3745
struct PortLowering {
3846
const slang::ast::PortSymbol &ast;
@@ -216,8 +224,16 @@ struct Context {
216224

217225
/// Convert hierarchy and structure AST nodes to MLIR ops.
218226
LogicalResult convertCompilation();
227+
/// Convert a module and its ports to an empty module op in the IR. Also adds
228+
/// the op to the worklist of module bodies to be lowered. This acts like a
229+
/// module "declaration", allowing instances to already refer to a module even
230+
/// before its body has been lowered.
231+
/// `module` must be the canonical module body if there is one.
219232
ModuleLowering *
220233
convertModuleHeader(const slang::ast::InstanceBodySymbol *module);
234+
/// Convert a module's body to the corresponding IR ops. The module op must
235+
/// have already been created earlier through a `convertModuleHeader` call.
236+
/// `module` must be the canonical module body if there is one.
221237
LogicalResult convertModuleBody(const slang::ast::InstanceBodySymbol *module);
222238
LogicalResult convertPackage(const slang::ast::PackageSymbol &package);
223239
FunctionLowering *
@@ -419,6 +435,7 @@ struct Context {
419435
std::map<LocationKey, Operation *> orderedRootOps;
420436

421437
/// How we have lowered modules to MLIR.
438+
/// The keys must be the slang canonical module bodies where they exist.
422439
DenseMap<const slang::ast::InstanceBodySymbol *,
423440
std::unique_ptr<ModuleLowering>>
424441
modules;

lib/Conversion/ImportVerilog/Structure.cpp

Lines changed: 15 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -407,13 +407,19 @@ struct ModuleVisitor : public BaseVisitor {
407407
using slang::ast::MultiPortSymbol;
408408
using slang::ast::PortSymbol;
409409

410+
// Always operate on the canonical instance body if there is one.
411+
// This means any symbols we record will be the symbols from the
412+
// canonical body, which will match up with the symbols encountered
413+
// by analyses which visit the canonical bodies.
414+
const slang::ast::InstanceBodySymbol *body = getCanonicalBody(instNode);
415+
410416
// Interface instances are expanded inline into individual variable/net ops
411417
// rather than creating a moore.instance op.
412-
auto defKind = instNode.body.getDefinition().definitionKind;
418+
auto defKind = body->getDefinition().definitionKind;
413419
if (defKind == slang::ast::DefinitionKind::Interface)
414420
return expandInterfaceInstance(instNode);
415421

416-
auto *moduleLowering = context.convertModuleHeader(&instNode.body);
422+
auto *moduleLowering = context.convertModuleHeader(body);
417423
if (!moduleLowering)
418424
return failure();
419425
auto module = moduleLowering->op;
@@ -952,11 +958,13 @@ LogicalResult Context::convertCompilation() {
952958
// Interfaces are not lowered as modules; they are expanded inline at each
953959
// use site, so skip them here.
954960
SmallVector<const slang::ast::InstanceSymbol *> topInstances;
955-
for (auto *inst : root.topInstances)
956-
if (inst->body.getDefinition().definitionKind !=
961+
for (auto *inst : root.topInstances) {
962+
const slang::ast::InstanceBodySymbol *body = getCanonicalBody(*inst);
963+
if (body->getDefinition().definitionKind !=
957964
slang::ast::DefinitionKind::Interface)
958-
if (!convertModuleHeader(&inst->body))
965+
if (!convertModuleHeader(body))
959966
return failure();
967+
}
960968

961969
// Convert all the root module definitions.
962970
while (!moduleWorklist.empty()) {
@@ -1006,10 +1014,6 @@ LogicalResult Context::convertCompilation() {
10061014
return success();
10071015
}
10081016

1009-
/// Convert a module and its ports to an empty module op in the IR. Also adds
1010-
/// the op to the worklist of module bodies to be lowered. This acts like a
1011-
/// module "declaration", allowing instances to already refer to a module even
1012-
/// before its body has been lowered.
10131017
ModuleLowering *
10141018
Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
10151019
using slang::ast::ArgumentDirection;
@@ -1024,53 +1028,8 @@ Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
10241028
timeScale = module->getTimeScale().value_or(slang::TimeScale());
10251029
llvm::scope_exit timeScaleGuard([&] { timeScale = prevTimeScale; });
10261030

1027-
auto parameters = module->getParameters();
1028-
bool hasModuleSame = false;
1029-
// If there is already exist a module that has the same name with this
1030-
// module ,has the same parent scope and has the same parameters we can
1031-
// define this module is a duplicate module
1032-
for (auto const &existingModule : modules) {
1033-
if (module->getDeclaringDefinition() ==
1034-
existingModule.getFirst()->getDeclaringDefinition()) {
1035-
auto moduleParameters = existingModule.getFirst()->getParameters();
1036-
hasModuleSame = true;
1037-
for (auto it1 = parameters.begin(), it2 = moduleParameters.begin();
1038-
it1 != parameters.end() && it2 != moduleParameters.end();
1039-
it1++, it2++) {
1040-
// Parameters size different
1041-
if (it1 == parameters.end() || it2 == moduleParameters.end()) {
1042-
hasModuleSame = false;
1043-
break;
1044-
}
1045-
const auto *para1 = (*it1)->symbol.as_if<ParameterSymbol>();
1046-
const auto *para2 = (*it2)->symbol.as_if<ParameterSymbol>();
1047-
// Parameters kind different
1048-
if ((para1 == nullptr) ^ (para2 == nullptr)) {
1049-
hasModuleSame = false;
1050-
break;
1051-
}
1052-
// Compare ParameterSymbol
1053-
if (para1 != nullptr) {
1054-
hasModuleSame = para1->getValue() == para2->getValue();
1055-
}
1056-
// Compare TypeParameterSymbol
1057-
if (para1 == nullptr) {
1058-
auto para1Type = convertType(
1059-
(*it1)->symbol.as<TypeParameterSymbol>().getTypeAlias());
1060-
auto para2Type = convertType(
1061-
(*it2)->symbol.as<TypeParameterSymbol>().getTypeAlias());
1062-
hasModuleSame = para1Type == para2Type;
1063-
}
1064-
if (!hasModuleSame)
1065-
break;
1066-
}
1067-
if (hasModuleSame) {
1068-
module = existingModule.first;
1069-
break;
1070-
}
1071-
}
1072-
}
1073-
1031+
// `module` is the canonical module body if it exists (i.e. deduplicated by
1032+
// slang).
10741033
auto &slot = modules[module];
10751034
if (slot)
10761035
return slot.get();
@@ -1273,8 +1232,6 @@ Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) {
12731232
return &lowering;
12741233
}
12751234

1276-
/// Convert a module's body to the corresponding IR ops. The module op must have
1277-
/// already been created earlier through a `convertModuleHeader` call.
12781235
LogicalResult
12791236
Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) {
12801237
auto &lowering = *modules[module];

0 commit comments

Comments
 (0)