From 35c27d77db873d7aa8ce157223caaed3ee792679 Mon Sep 17 00:00:00 2001 From: Mike Urbach Date: Thu, 30 May 2024 18:55:33 -0700 Subject: [PATCH] [FIRRTL] Simplify path handling in ResolvePaths and LowerClasses. This turns the multiple-instantiation error into a warning in ResolvePaths and LowerClasses. In real designs coming from Chisel today, we are not yet able to enforce single instantiation. This was never a requirement of the original way that we handled hierarchical paths in EmitOMIR, so this removes the requirement for now. Adding it back is tracked in https://github.com/llvm/circt/issues/7128. With this change, the ResolvePaths logic was simplified to stop trying to disambiguate paths in some cases, and instead allow the annotations to simply convey the user's requested local or hierarchical path. In LowerClasses, if there are multiple instances, this means we have ambiguity. In practice, this logic will produce the same outputs as EmitOMIR, once we fix https://github.com/llvm/circt/issues/7125. --- .../FIRRTL/Transforms/LowerClasses.cpp | 8 +- .../FIRRTL/Transforms/ResolvePaths.cpp | 150 ++++-------------- test/Dialect/FIRRTL/resolve-paths-errors.mlir | 2 +- test/Dialect/FIRRTL/resolve-paths.mlir | 63 ++++---- 4 files changed, 67 insertions(+), 156 deletions(-) diff --git a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp index b78a84f48faf..140308cdbd55 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp @@ -377,14 +377,16 @@ PathTracker::getOrComputeNeedsAltBasePath(Location loc, StringAttr moduleName, break; } // If there is more than one instance of this module, then the path - // operation is ambiguous, which is an error. + // operation is ambiguous, which is a warning. This should become an error + // once user code is properly enforcing single instantiation, but in + // practice this generates the same outputs as the original flow for now. + // See https://github.com/llvm/circt/issues/7128. if (!node->hasOneUse()) { - auto diag = mlir::emitError(loc) + auto diag = mlir::emitWarning(loc) << "unable to uniquely resolve target due " "to multiple instantiation"; for (auto *use : node->uses()) diag.attachNote(use->getInstance().getLoc()) << "instance here"; - return diag; } node = (*node->usesBegin())->getParent(); } diff --git a/lib/Dialect/FIRRTL/Transforms/ResolvePaths.cpp b/lib/Dialect/FIRRTL/Transforms/ResolvePaths.cpp index b51829cf0ce1..b1b5c871669e 100644 --- a/lib/Dialect/FIRRTL/Transforms/ResolvePaths.cpp +++ b/lib/Dialect/FIRRTL/Transforms/ResolvePaths.cpp @@ -30,8 +30,7 @@ struct PathResolver { /// be a reference to the HierPathOp, or null if no HierPathOp was needed. LogicalResult resolveHierPath(Location loc, FModuleOp owningModule, const AnnoPathValue &target, - bool canDisambiguate, - SmallVectorImpl &results) { + FlatSymbolRefAttr &result) { // We want to root this path at the top level module, or in the case of an // unreachable module, we settle for as high as we can get. @@ -39,7 +38,6 @@ struct PathResolver { if (!target.instances.empty()) module = target.instances.front()->getParentOfType(); auto *node = instanceGraph[module]; - bool needsDisambiguation = false; while (true) { // If the path is rooted at the owning module, we're done. if (node->getModule() == owningModule) @@ -51,44 +49,30 @@ struct PathResolver { << "unable to resolve path relative to owning module " << owningModule.getModuleNameAttr(); // If there is more than one instance of this module, then the path - // operation is ambiguous. + // operation is ambiguous, which is a warning. This should become an error + // once user code is properly enforcing single instantiation, but in + // practice this generates the same outputs as the original flow for now. + // See https://github.com/llvm/circt/issues/7128. if (!node->hasOneUse()) { - // If we can disambiguate by duplicating paths, stop here, otherwise - // this is an error. - if (canDisambiguate) { - needsDisambiguation = true; - break; - } - - auto diag = emitError(loc) << "unable to uniquely resolve target due " - "to multiple instantiation"; + auto diag = emitWarning(loc) << "unable to uniquely resolve target due " + "to multiple instantiation"; for (auto *use : node->uses()) diag.attachNote(use->getInstance().getLoc()) << "instance here"; - return diag; } node = (*node->usesBegin())->getParent(); } - // Find the minimal uniquely-identifying path to the operation. We scan - // through the list of instances looking for the first module which is - // multiply instantiated. We will start our HierPathOp at this instance. - auto *it = llvm::find_if(target.instances, [&](InstanceOp instance) { - auto *node = instanceGraph.lookup(instance.getReferencedModuleNameAttr()); - return !node->hasOneUse(); - }); - - // If the path is empty and we don't need to disambiguate, then this is a - // local reference and we should not construct a HierPathOp. - auto pathLength = std::distance(it, target.instances.end()); - if (pathLength == 0 && !needsDisambiguation) { + // If the path is empty then this is a local reference and we should not + // construct a HierPathOp. + if (target.instances.empty()) { return success(); } // Transform the instances into a list of FlatSymbolRefs. SmallVector insts; - insts.reserve(pathLength); - std::transform(it, target.instances.end(), std::back_inserter(insts), - [&](InstanceOp instance) { + insts.reserve(target.instances.size()); + std::transform(target.instances.begin(), target.instances.end(), + std::back_inserter(insts), [&](InstanceOp instance) { return OpAnnoTarget(instance).getNLAReference( namespaces[instance->getParentOfType()]); }); @@ -97,45 +81,10 @@ struct PathResolver { insts.push_back( FlatSymbolRefAttr::get(target.ref.getModule().getModuleNameAttr())); - // If we need to disambiguate this path, create new paths with every unique - // prefix from the owning module to the point where the path suffix starts. - if (needsDisambiguation) { - ArrayRef instancePaths = - instancePathCache.getAbsolutePaths(node->getModule(), - instanceGraph[owningModule]); - for (auto instancePath : instancePaths) { - // For each unique prefix from the owning module, build up a new path. - SmallVector fullInsts; - fullInsts.reserve(instancePath.size() + target.instances.size() + 1); - std::transform( - instancePath.begin(), instancePath.end(), - std::back_inserter(fullInsts), - [&](igraph::InstanceOpInterface inst) { - return OpAnnoTarget(cast(inst)) - .getNLAReference( - namespaces[inst->getParentOfType()]); - }); - - // If present, include the start of the target's instance path to tie - // the prefix to the suffix. - if (!target.instances.empty()) - for (InstanceOp inst : target.instances) - fullInsts.push_back(OpAnnoTarget(inst).getNLAReference( - namespaces[inst->getParentOfType()])); - - // Include the suffix, starting from the module which was multiply - // instantiated. - fullInsts.append(insts); - - // Return the disambiguated hierchical path. - auto instAttr = ArrayAttr::get(circuit.getContext(), fullInsts); - results.push_back(hierPathCache.getRefFor(instAttr)); - } - } else { - // Return the one hierchical path. - auto instAttr = ArrayAttr::get(circuit.getContext(), insts); - results.push_back(hierPathCache.getRefFor(instAttr)); - } + // Return the hierchical path. + auto instAttr = ArrayAttr::get(circuit.getContext(), insts); + + result = hierPathCache.getRefFor(instAttr); return success(); } @@ -220,22 +169,12 @@ struct PathResolver { if (!owningModule) return unresolved->emitError("path does not have a single owning module"); - // If the UnresolvedPathOp is ambiguous due to multiple instantiation, we - // can disambiguate by expanding it into multiple unambiguous paths, but - // only if the UnresolvedPathOp is already being used in a list. - bool canDisambiguate = - !unresolved->use_empty() && - llvm::all_of(unresolved->getUsers(), - [](Operation *user) { return isa(user); }); - - // Resolve a unique path to the operation in question, or multiple paths if - // necessary and it is legal to disambiguate. - SmallVector hierPathNames; - if (failed(resolveHierPath(loc, owningModule, *path, canDisambiguate, - hierPathNames))) + // Resolve a path to the operation in question. + FlatSymbolRefAttr hierPathName; + if (failed(resolveHierPath(loc, owningModule, *path, hierPathName))) return failure(); - auto createAnnotation = [&](std::optional hierPathName) { + auto createAnnotation = [&](FlatSymbolRefAttr hierPathName) { // Create a unique ID. auto id = DistinctAttr::create(UnitAttr::get(context)); @@ -244,7 +183,7 @@ struct PathResolver { fields.append("id", id); fields.append("class", StringAttr::get(context, "circt.tracker")); if (hierPathName) - fields.append("circt.nonlocal", hierPathName.value()); + fields.append("circt.nonlocal", hierPathName); if (path->fieldIdx != 0) fields.append("circt.fieldID", b.getI64IntegerAttr(path->fieldIdx)); @@ -252,48 +191,25 @@ struct PathResolver { }; // Create the annotation(s). - SmallVector annotations; - if (hierPathNames.empty()) { - annotations.push_back(createAnnotation(std::nullopt)); - } else { - for (auto hierPathName : hierPathNames) - annotations.push_back(createAnnotation(hierPathName)); - } + Attribute annotation = createAnnotation(hierPathName); // Attach the annotation(s) to the target. auto annoTarget = path->ref; auto targetAnnotations = annoTarget.getAnnotations(); - targetAnnotations.addAnnotations(annotations); + targetAnnotations.addAnnotations({annotation}); if (targetKindAttr.getValue() == TargetKind::DontTouch) targetAnnotations.addDontTouch(); annoTarget.setAnnotations(targetAnnotations); - // Create the path operation(s). - size_t lastAnnotationIdx = annotations.size() - 1; - for (auto [i, annotation] : llvm::enumerate(annotations)) { - // Create a PathOp using the id in the annotation we added to the target. - auto dictAttr = cast(annotation); - auto id = cast(dictAttr.get("id")); - auto resolved = b.create(targetKindAttr, id); - - if (!canDisambiguate || i == lastAnnotationIdx) { - // If we didn't need to disambiguate, always just replace the unresolved - // path with the resolved path. If we did disambiguate, and this is the - // last path, replace the unresolved path. In the case of multiple - // paths, we save the final replacement for last so the other paths can - // find the uses to update. - unresolved->replaceAllUsesWith(resolved); - unresolved.erase(); - } else { - // If we are handling one of several paths after disambiguation, insert - // a use of the new path next to where the unresolved path was used. - for (auto &use : unresolved->getUses()) { - assert(isa(use.getOwner())); - use.getOwner()->insertOperands(use.getOperandNumber(), - resolved.getResult()); - } - } - } + // Create a PathOp using the id in the annotation we added to the target. + auto dictAttr = cast(annotation); + auto id = cast(dictAttr.get("id")); + auto resolved = b.create(targetKindAttr, id); + + // Replace the unresolved path with the PathOp. + unresolved->replaceAllUsesWith(resolved); + unresolved.erase(); + return success(); } diff --git a/test/Dialect/FIRRTL/resolve-paths-errors.mlir b/test/Dialect/FIRRTL/resolve-paths-errors.mlir index 48a88f6f9747..9ce738cd890d 100644 --- a/test/Dialect/FIRRTL/resolve-paths-errors.mlir +++ b/test/Dialect/FIRRTL/resolve-paths-errors.mlir @@ -65,7 +65,7 @@ firrtl.module @VectorTarget(in %a : !firrtl.vector, 1>) { firrtl.circuit "AmbiguousPath" { firrtl.module @AmbiguousPath() { - // expected-error @below {{unable to uniquely resolve target due to multiple instantiation}} + // expected-warning @below {{unable to uniquely resolve target due to multiple instantiation}} %0 = firrtl.unresolved_path "OMReferenceTarget:~AmbiguousPath|Child" // expected-note @below {{instance here}} firrtl.instance child0 @Child() diff --git a/test/Dialect/FIRRTL/resolve-paths.mlir b/test/Dialect/FIRRTL/resolve-paths.mlir index 3a9cb243bb95..6664d917a649 100644 --- a/test/Dialect/FIRRTL/resolve-paths.mlir +++ b/test/Dialect/FIRRTL/resolve-paths.mlir @@ -96,13 +96,13 @@ firrtl.circuit "PathMinimization" { // CHECK: firrtl.module @PathMinimization() firrtl.module @PathMinimization() { // CHECK: %0 = firrtl.path reference distinct[0]<> - // CHECK: firrtl.instance child @Child() + // CHECK: firrtl.instance child {{.+}} @Child() %0 = firrtl.unresolved_path "OMReferenceTarget:~PathMinimization|PathMinimization/child:Child>wire" firrtl.instance child @Child() } // CHECK: firrtl.module @Child() firrtl.module @Child() { - // CHECK: %wire = firrtl.wire {annotations = [{class = "circt.tracker", id = distinct[0]<>}]} : !firrtl.uint<8> + // CHECK: %wire = firrtl.wire {annotations = [{{{.+}} class = "circt.tracker", id = distinct[0]<>}]} : !firrtl.uint<8> %wire = firrtl.wire : !firrtl.uint<8> } } @@ -116,16 +116,16 @@ firrtl.module @TargetInstance() { // CHECK: %0 = firrtl.path reference distinct[0]<> // CHECK: %1 = firrtl.path instance distinct[1]<> // CHECK: %2 = firrtl.path member_instance distinct[2]<> - // CHECK: firrtl.instance child @Child() + // CHECK: firrtl.instance child {{.+}} @Child() %0 = firrtl.unresolved_path "OMReferenceTarget:~TargetInstance|TargetInstance/child:Child" %1 = firrtl.unresolved_path "OMInstanceTarget:~TargetInstance|TargetInstance/child:Child" %2 = firrtl.unresolved_path "OMMemberInstanceTarget:~TargetInstance|TargetInstance/child:Child" firrtl.instance child @Child() } // CHECK: firrtl.module @Child() attributes {annotations = [ -// CHECK-SAME: {class = "circt.tracker", id = distinct[0]<>}, -// CHECK-SAME: {class = "circt.tracker", id = distinct[1]<>}, -// CHECK-SAME: {class = "circt.tracker", id = distinct[2]<>} +// CHECK-SAME: {{{.+}} class = "circt.tracker", id = distinct[0]<>}, +// CHECK-SAME: {{{.+}} class = "circt.tracker", id = distinct[1]<>}, +// CHECK-SAME: {{{.+}} class = "circt.tracker", id = distinct[2]<>} // CHECK-SAME: ]} firrtl.module @Child() { } } @@ -137,11 +137,11 @@ firrtl.circuit "TargetInstancePort" { // CHECK: firrtl.module @TargetInstancePort() { firrtl.module @TargetInstancePort() { // CHECK: %0 = firrtl.path reference distinct[0]<> - // CHECK: firrtl.instance child @Child(in in: !firrtl.uint<8>) + // CHECK: firrtl.instance child {{.+}} @Child(in in: !firrtl.uint<8>) %0 = firrtl.unresolved_path "OMReferenceTarget:~TargetInstancePort|TargetInstancePort/child:Child>in" firrtl.instance child @Child(in in : !firrtl.uint<8>) } -// CHECK: firrtl.module @Child(in %in: !firrtl.uint<8> [{class = "circt.tracker", id = distinct[0]<>}]) +// CHECK: firrtl.module @Child(in %in: !firrtl.uint<8> [{{{.+}} class = "circt.tracker", id = distinct[0]<>}]) firrtl.module @Child(in %in : !firrtl.uint<8>) { } } @@ -228,62 +228,55 @@ firrtl.class @OM() { // ----- -// CHECK-LABEL: firrtl.circuit "DisambiguateLocalPath" -firrtl.circuit "DisambiguateLocalPath" { -// CHECK: hw.hierpath private [[NLA0:@.+]] [@DisambiguateLocalPath::[[SYM0:@.+]], @Child] -// CHECK: hw.hierpath private [[NLA1:@.+]] [@DisambiguateLocalPath::[[SYM1:@.+]], @Child] -firrtl.module @DisambiguateLocalPath() { +// CHECK-LABEL: firrtl.circuit "AmbiguousLocalPath" +firrtl.circuit "AmbiguousLocalPath" { +firrtl.module @AmbiguousLocalPath() { // CHECK: [[PATH0:%.+]] = firrtl.path reference distinct[[[DISTINCT0:.+]]]<> - // CHECK: [[PATH1:%.+]] = firrtl.path reference distinct[[[DISTINCT1:.+]]]<> - %0 = firrtl.unresolved_path "OMReferenceTarget:~DisambiguateLocalPath|Child" + %0 = firrtl.unresolved_path "OMReferenceTarget:~AmbiguousLocalPath|Child" - // CHECK: firrtl.list.create [[PATH0]], [[PATH1]] + // CHECK: firrtl.list.create [[PATH0]] %1 = firrtl.list.create %0 : !firrtl.list - // CHECK: firrtl.instance child0 sym [[SYM1]] + // CHECK: firrtl.instance child0 firrtl.instance child0 @Child() - // CHECK: firrtl.instance child1 sym [[SYM0]] + // CHECK: firrtl.instance child1 firrtl.instance child1 @Child() } // CHECK: firrtl.module @Child -// CHECK-SAME: {circt.nonlocal = [[NLA0]], class = "circt.tracker", id = distinct[[[DISTINCT0]]]<>} -// CHECK-SAME: {circt.nonlocal = [[NLA1]], class = "circt.tracker", id = distinct[[[DISTINCT1]]]<>} +// CHECK-SAME: {class = "circt.tracker", id = distinct[[[DISTINCT0]]]<>} firrtl.module @Child() {} } // ----- -// CHECK-LABEL: firrtl.circuit "DisambiguateNonLocalPath" -firrtl.circuit "DisambiguateNonLocalPath" { -// CHECK: hw.hierpath private [[NLA0:@.+]] [@DisambiguateNonLocalPath::[[SYM0:@.+]], @Child1::[[SYM1_0:@.+]], @Child2::[[SYM2:@.+]], @Child3::[[SYM3:@.+]], @Child4] -// CHECK: hw.hierpath private [[NLA1:@.+]] [@DisambiguateNonLocalPath::[[SYM0]], @Child1::[[SYM1_1:@.+]], @Child2::[[SYM2]], @Child3::[[SYM3]], @Child4] -firrtl.module @DisambiguateNonLocalPath() { +// CHECK-LABEL: firrtl.circuit "AmbiguousNonLocalPath" +firrtl.circuit "AmbiguousNonLocalPath" { +// CHECK: hw.hierpath private [[NLA:@.+]] [@Child2::[[SYM0:@.+]], @Child3::[[SYM1:@.+]], @Child4] +firrtl.module @AmbiguousNonLocalPath() { // CHECK: [[PATH0:%.+]] = firrtl.path reference distinct[[[DISTINCT0:.+]]]<> - // CHECK: [[PATH1:%.+]] = firrtl.path reference distinct[[[DISTINCT1:.+]]]<> - %0 = firrtl.unresolved_path "OMReferenceTarget:~DisambiguateNonLocalPath|Child2/child3:Child3/child4:Child4" + %0 = firrtl.unresolved_path "OMReferenceTarget:~AmbiguousNonLocalPath|Child2/child3:Child3/child4:Child4" - // CHECK: firrtl.list.create [[PATH0]], [[PATH1]] + // CHECK: firrtl.list.create [[PATH0]] %1 = firrtl.list.create %0 : !firrtl.list - // CHECK: firrtl.instance child1 sym [[SYM0]] + // CHECK: firrtl.instance child1 firrtl.instance child1 @Child1() } firrtl.module @Child1() { - // CHECK: firrtl.instance child2_0 sym [[SYM1_1]] + // CHECK: firrtl.instance child2_0 firrtl.instance child2_0 @Child2() - // CHECK: firrtl.instance child2_1 sym [[SYM1_0]] + // CHECK: firrtl.instance child2_1 firrtl.instance child2_1 @Child2() } firrtl.module @Child2() { - // CHECK: firrtl.instance child3 sym [[SYM2]] + // CHECK: firrtl.instance child3 sym [[SYM0]] firrtl.instance child3 @Child3() } firrtl.module @Child3() { - // CHECK: firrtl.instance child4 sym [[SYM3]] + // CHECK: firrtl.instance child4 sym [[SYM1]] firrtl.instance child4 @Child4() } // CHECK: firrtl.module @Child4 -// CHECK-SAME: {circt.nonlocal = [[NLA0]], class = "circt.tracker", id = distinct[[[DISTINCT0]]]<>} -// CHECK-SAME: {circt.nonlocal = [[NLA1]], class = "circt.tracker", id = distinct[[[DISTINCT1]]]<>} +// CHECK-SAME: {circt.nonlocal = [[NLA]], class = "circt.tracker", id = distinct[[[DISTINCT0]]]<>} firrtl.module @Child4() {} }