50#include "llvm/ADT/MapVector.h"
51#include "llvm/ADT/TypeSwitch.h"
52#include "llvm/Support/Debug.h"
53#include "llvm/Support/Threading.h"
57#define GEN_PASS_DEF_LOWERDOMAINS
58#include "circt/Dialect/FIRRTL/Passes.h.inc"
64using mlir::UnrealizedConversionCastOp;
72 impl::LowerDomainsBase<LowerDomainsPass>::getArgumentName().data()
76struct AssociationInfo {
78 DistinctAttr distinctAttr;
95 std::optional<unsigned> inputPort;
106 UnrealizedConversionCastOp temp;
110 SmallVector<AssociationInfo> associations{};
115 static DomainInfo input(
unsigned portIndex) {
116 return DomainInfo({{}, portIndex, portIndex + 1, {}, {}});
122 static DomainInfo output(
unsigned portIndex) {
123 return DomainInfo({{}, std::nullopt, portIndex, {}, {}});
144 llvm::once_flag flag;
150 llvm::once_flag flag;
151 StringAttr domainInfoIn;
152 StringAttr domainInfoOut;
153 StringAttr associationsIn;
154 StringAttr associationsOut;
161 ArrayAttr getEmptyArrayAttr() {
162 llvm::call_once(emptyArray.flag,
163 [&] { emptyArray.attr = ArrayAttr::get(context, {}); });
164 return emptyArray.attr;
169 void initClassOut() {
170 llvm::call_once(classOut.flag, [&] {
171 classOut.domainInfoIn = StringAttr::get(context,
"domainInfo_in");
172 classOut.domainInfoOut = StringAttr::get(context,
"domainInfo_out");
173 classOut.associationsIn = StringAttr::get(context,
"associations_in");
174 classOut.associationsOut = StringAttr::get(context,
"associations_out");
180 StringAttr getDomainInfoIn() {
182 return classOut.domainInfoIn;
186 StringAttr getDomainInfoOut() {
188 return classOut.domainInfoOut;
192 StringAttr getAssociationsIn() {
194 return classOut.associationsIn;
198 StringAttr getAssociationsOut() {
200 return classOut.associationsOut;
208 EmptyArray emptyArray;
218static UnrealizedConversionCastOp stubOut(Value value) {
219 if (!value.hasNUsesOrMore(1))
222 OpBuilder builder(value.getContext());
223 builder.setInsertionPointAfterValue(value);
224 auto temp = UnrealizedConversionCastOp::create(
225 builder, builder.getUnknownLoc(), {value.getType()}, {});
226 value.replaceAllUsesWith(temp.getResult(0));
240static UnrealizedConversionCastOp splice(UnrealizedConversionCastOp temp,
246 assert(temp && temp.getNumResults() == 1 && temp.getNumOperands() == 0);
251 auto oldValue = temp.getResult(0);
253 OpBuilder builder(temp);
254 auto splice = UnrealizedConversionCastOp::create(
255 builder, builder.getUnknownLoc(), {oldValue.getType()}, {value});
256 oldValue.replaceAllUsesWith(splice.getResult(0));
270 LowerModule(FModuleLike op,
const DenseMap<Attribute, Classes> &classes,
271 Constants &constants, InstanceGraph &instanceGraph)
272 : op(op), eraseVector(op.
getNumPorts()), domainToClasses(classes),
273 constants(constants), instanceGraph(instanceGraph) {}
277 LogicalResult lowerModule();
281 LogicalResult lowerInstances();
288 BitVector eraseVector;
292 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
295 SmallVector<std::pair<unsigned, unsigned>> resultMap;
298 const DenseMap<Attribute, Classes> &domainToClasses;
301 Constants &constants;
311 llvm::MapVector<unsigned, DomainInfo> indexToDomain;
314LogicalResult LowerModule::lowerModule() {
324 TypeSwitch<Operation *, std::optional<Block *>>(op)
325 .Case<FModuleOp>([](
auto op) {
return op.getBodyBlock(); })
326 .Case<FExtModuleOp>([](
auto) {
return nullptr; })
328 .Default([](
auto) {
return std::nullopt; });
331 Block *body = *shouldProcess;
333 auto *
context = op.getContext();
337 SmallVector<Attribute> portAnnotations;
346 OpBuilder::InsertPoint insertPoint;
348 insertPoint = {body, body->begin()};
349 auto ports = op.getPorts();
350 for (
unsigned i = 0, iDel = 0, iIns = 0, e = op.getNumPorts(); i != e; ++i) {
351 auto port = cast<PortInfo>(ports[i]);
355 if (
auto domainType = dyn_cast<DomainType>(port.type)) {
359 auto domain = domainType.getName();
360 auto [classIn, classOut] = domainToClasses.at(domain.getAttr());
362 indexToDomain[i] = port.direction == Direction::In
363 ? DomainInfo::input(iIns)
364 : DomainInfo::output(iIns);
369 ImplicitLocOpBuilder builder(port.loc,
context);
370 builder.restoreInsertionPoint(insertPoint);
373 auto object = ObjectOp::create(
375 StringAttr::get(
context, Twine(port.name) +
"_object"));
377 instanceGraph.
lookup(classOut));
378 indexToDomain[i].op = object;
379 indexToDomain[i].temp = stubOut(body->getArgument(i));
384 insertPoint = builder.saveInsertionPoint();
391 if (port.direction == Direction::In) {
392 newPorts.push_back({iDel, PortInfo(port.name, classIn.getInstanceType(),
394 portAnnotations.push_back(constants.getEmptyArrayAttr());
398 {iDel, PortInfo(StringAttr::get(
context, Twine(port.name) +
"_out"),
399 classOut.getInstanceType(), Direction::Out)});
403 portAnnotations.push_back(constants.getEmptyArrayAttr());
419 ArrayAttr domainAttr = cast_or_null<ArrayAttr>(port.domains);
420 if (!domainAttr || domainAttr.empty()) {
421 portAnnotations.push_back(port.annotations.getArrayAttr());
430 portAnnotations.push_back(port.annotations.getArrayAttr());
434 SmallVector<Annotation> newAnnotations;
436 for (
auto indexAttr : domainAttr.getAsRange<IntegerAttr>()) {
438 id = DistinctAttr::create(UnitAttr::get(
context));
439 newAnnotations.push_back(Annotation(DictionaryAttr::getWithSorted(
443 indexToDomain[indexAttr.getUInt()].associations.push_back({id, port.loc});
445 if (!newAnnotations.empty())
446 port.annotations.addAnnotations(newAnnotations);
447 portAnnotations.push_back(port.annotations.getArrayAttr());
451 op.erasePorts(eraseVector);
452 op.setDomainInfoAttr(constants.getEmptyArrayAttr());
456 op.insertPorts(newPorts);
459 for (
auto const &[_, info] : indexToDomain) {
460 auto [object, inputPort, outputPort, temp, associations] =
info;
461 OpBuilder builder(
object);
462 builder.setInsertionPointAfter(
object);
469 auto subDomainInfoIn =
470 ObjectSubfieldOp::create(builder,
object.
getLoc(),
object, 0);
472 PropAssignOp::create(builder,
object.
getLoc(), subDomainInfoIn,
473 body->getArgument(*inputPort));
474 splice(temp, body->getArgument(*inputPort));
476 splice(temp, subDomainInfoIn);
480 auto subAssociations =
481 ObjectSubfieldOp::create(builder,
object.
getLoc(),
object, 2);
482 SmallVector<Value> paths;
483 for (
auto [
id, loc] : associations) {
484 paths.push_back(PathOp::create(
485 builder, loc, TargetKindAttr::get(
context, TargetKind::Reference),
488 auto list = ListCreateOp::create(
490 ListType::get(
context, cast<PropertyType>(PathType::get(
context))),
492 PropAssignOp::create(builder,
object.
getLoc(), subAssociations, list);
495 PropAssignOp::create(builder,
object.
getLoc(),
496 body->getArgument(outputPort),
object);
510 DenseSet<Operation *> conversionsToErase;
511 DenseSet<Operation *> operationsToErase;
512 auto walkResult = op.walk([&](Operation *walkOp) {
516 if (operationsToErase.contains(walkOp)) {
518 return WalkResult::advance();
522 if (
auto castOp = dyn_cast<UnsafeDomainCastOp>(walkOp)) {
523 for (
auto value : castOp.getDomains()) {
524 auto *conversion = value.getDefiningOp();
525 assert(isa<UnrealizedConversionCastOp>(conversion));
526 conversionsToErase.insert(conversion);
529 castOp.getResult().replaceAllUsesWith(castOp.getInput());
531 return WalkResult::advance();
537 if (
auto anonDomain = dyn_cast<DomainCreateAnonOp>(walkOp)) {
539 auto noUser = llvm::all_of(anonDomain->getUsers(), [&](
auto *user) {
540 return operationsToErase.contains(user) ||
541 conversionsToErase.contains(user);
544 conversionsToErase.insert(anonDomain);
545 return WalkResult::advance();
550 OpBuilder builder(anonDomain);
552 domainToClasses.at(anonDomain.getDomainAttr().getAttr()).input;
553 anonDomain.replaceAllUsesWith(UnrealizedConversionCastOp::create(
554 builder, anonDomain.getLoc(), {anonDomain.getType()},
555 {UnknownValueOp::create(builder, anonDomain.getLoc(),
556 classIn.getInstanceType())
559 return WalkResult::advance();
563 if (
auto createDomain = dyn_cast<DomainCreateOp>(walkOp)) {
564 auto noUser = llvm::all_of(createDomain->getUsers(), [&](
auto *user) {
565 return operationsToErase.contains(user) ||
566 conversionsToErase.contains(user);
569 conversionsToErase.insert(createDomain);
570 return WalkResult::advance();
573 OpBuilder builder(createDomain);
575 domainToClasses.at(createDomain.getDomainAttr().getAttr()).input;
576 auto object = ObjectOp::create(builder, createDomain.getLoc(), classIn,
577 createDomain.getNameAttr());
578 instanceGraph.lookup(op)->addInstance(
object,
579 instanceGraph.lookup(classIn));
582 auto fieldValues = createDomain.getFieldValues();
586 for (
auto [fieldIdx, fieldValue] : llvm::enumerate(fieldValues)) {
587 auto inputPortIdx = fieldIdx * 2;
588 auto subfield = ObjectSubfieldOp::create(
589 builder, createDomain.getLoc(),
object, inputPortIdx);
590 PropAssignOp::create(builder, createDomain.getLoc(), subfield,
594 createDomain.replaceAllUsesWith(UnrealizedConversionCastOp::create(
595 builder, createDomain.getLoc(), {createDomain.getType()},
596 {object.getResult()}));
597 createDomain.erase();
598 return WalkResult::advance();
602 if (
auto castOp = dyn_cast<DomainAnyRefCastOp>(walkOp)) {
603 auto conversionCast = dyn_cast_or_null<UnrealizedConversionCastOp>(
604 castOp.getInput().getDefiningOp());
605 if (!conversionCast || conversionCast.getNumOperands() != 1) {
607 "has an input that is not a conversion cast with one operand");
608 return WalkResult::interrupt();
611 OpBuilder builder(castOp);
612 auto objectCast = ObjectAnyRefCastOp::create(
613 builder, castOp.getLoc(), conversionCast.getOperand(0));
614 conversionsToErase.insert(conversionCast);
615 castOp.replaceAllUsesWith(objectCast.getResult());
617 return WalkResult::advance();
621 if (
auto subfieldOp = dyn_cast<DomainSubfieldOp>(walkOp)) {
624 auto *inputOp = subfieldOp.getInput().getDefiningOp();
626 subfieldOp.emitOpError(
627 "has an input that is not defined by an operation");
628 return WalkResult::interrupt();
631 auto conversionCast = dyn_cast<UnrealizedConversionCastOp>(inputOp);
632 if (!conversionCast || conversionCast.getNumOperands() != 1) {
633 subfieldOp.emitOpError(
634 "has an input that is not a conversion cast with one operand");
635 return WalkResult::interrupt();
640 auto fieldIndex = subfieldOp.getFieldIndex();
641 auto outputPortIndex = fieldIndex * 2 + 1;
644 OpBuilder builder(subfieldOp);
645 auto objectSubfield = ObjectSubfieldOp::create(
646 builder, subfieldOp.getLoc(), conversionCast.getOperand(0),
650 conversionsToErase.insert(conversionCast);
652 subfieldOp.replaceAllUsesWith(objectSubfield.getResult());
654 return WalkResult::advance();
666 if (
auto wireOp = dyn_cast<WireOp>(walkOp)) {
668 if (
auto domainType =
670 if (wireOp.getResult().use_empty()) {
671 conversionsToErase.insert(wireOp);
672 return WalkResult::advance();
675 OpBuilder builder(wireOp);
677 domainToClasses.at(domainType.getName().getAttr()).input;
678 auto classWire = WireOp::create(
679 builder, wireOp.getLoc(), classIn.getInstanceType(),
680 wireOp.getNameAttr(), wireOp.getNameKindAttr(),
681 wireOp.getAnnotationsAttr(), wireOp.getInnerSymAttr(),
682 wireOp.getForceableAttr());
684 auto cast = UnrealizedConversionCastOp::create(
685 builder, wireOp.getLoc(), {domainType}, {classWire.getResult()});
686 wireOp.getResult().replaceAllUsesWith(cast.getResult(0));
687 conversionsToErase.insert(cast);
688 conversionsToErase.insert(wireOp);
692 if (!wireOp.getDomains().empty()) {
693 for (
auto domain : wireOp.getDomains())
694 if (
auto *defOp = domain.getDefiningOp())
695 conversionsToErase.insert(defOp);
696 wireOp->eraseOperands(0, wireOp.getNumOperands());
699 return WalkResult::advance();
703 auto defineOp = dyn_cast<DomainDefineOp>(walkOp);
705 return WalkResult::advance();
714 auto *src = defineOp.getSrc().getDefiningOp();
715 auto dest = dyn_cast<UnrealizedConversionCastOp>(
716 defineOp.getDest().getDefiningOp());
718 return WalkResult::advance();
720 conversionsToErase.insert(src);
721 conversionsToErase.insert(dest);
723 if (
auto srcCast = dyn_cast<UnrealizedConversionCastOp>(src)) {
724 assert(srcCast.getNumOperands() == 1 && srcCast.getNumResults() == 1);
725 OpBuilder builder(defineOp);
726 PropAssignOp::create(builder, defineOp.getLoc(), dest.getOperand(0),
727 srcCast.getOperand(0));
728 }
else if (!isa<DomainCreateAnonOp, DomainCreateOp>(src)) {
729 auto diag = defineOp.emitOpError()
730 <<
"has a source which cannot be lowered by 'LowerDomains'";
731 diag.attachNote(src->getLoc()) <<
"unsupported source is here";
732 return WalkResult::interrupt();
736 return WalkResult::advance();
739 if (walkResult.wasInterrupted())
743 for (
auto *op : conversionsToErase)
748 op.setPortAnnotationsAttr(ArrayAttr::get(
context, portAnnotations));
753LogicalResult LowerModule::lowerInstances() {
755 if (eraseVector.none() && newPorts.empty())
762 if (!isa<FModuleOp, FExtModuleOp>(op))
765 auto *node = instanceGraph.
lookup(cast<igraph::ModuleOpInterface>(*op));
766 for (
auto *use : llvm::make_early_inc_range(node->uses())) {
767 auto instanceOp = dyn_cast<InstanceOp>(*use->getInstance());
769 use->getInstance().emitOpError()
770 <<
"has an unimplemented lowering in LowerDomains";
773 LLVM_DEBUG(llvm::dbgs()
774 <<
" - " << instanceOp.getInstanceName() <<
"\n");
776 for (
auto i : eraseVector.set_bits())
777 indexToDomain[i].temp = stubOut(instanceOp.getResult(i));
779 auto erased = instanceOp.cloneWithErasedPortsAndReplaceUses(eraseVector);
780 auto inserted = erased.cloneWithInsertedPortsAndReplaceUses(newPorts);
783 for (
auto &[i, info] : indexToDomain) {
785 if (
info.inputPort) {
787 splicedValue = inserted->getResult(*
info.inputPort);
791 OpBuilder builder(inserted);
792 builder.setInsertionPointAfter(inserted);
794 ObjectSubfieldOp::create(builder, inserted.getLoc(),
795 inserted->getResult(
info.outputPort), 1);
798 splice(
info.temp, splicedValue);
814 LowerCircuit(CircuitOp circuit, InstanceGraph &instanceGraph,
815 llvm::Statistic &numDomains)
816 : circuit(circuit), instanceGraph(instanceGraph),
817 constants(circuit.getContext()), numDomains(numDomains) {}
820 LogicalResult lowerCircuit();
824 LogicalResult lowerDomain(DomainOp);
836 llvm::Statistic &numDomains;
840 DenseMap<Attribute, Classes> classes;
843LogicalResult LowerCircuit::lowerDomain(DomainOp op) {
844 ImplicitLocOpBuilder builder(op.getLoc(), op);
845 auto *
context = op.getContext();
846 auto name = op.getNameAttr();
847 SmallVector<PortInfo> classInPorts;
848 for (
auto field : op.getFields().getAsRange<DomainFieldAttr>())
849 classInPorts.append({{builder.getStringAttr(
850 Twine(field.getName().getValue()) +
"_in"),
851 field.getType(), Direction::In},
852 {builder.getStringAttr(
853 Twine(field.getName().getValue()) +
"_out"),
854 field.getType(), Direction::Out}});
855 auto classIn = ClassOp::create(builder, name, classInPorts);
856 auto classInType = classIn.getInstanceType();
858 ListType::get(
context, cast<PropertyType>(PathType::get(
context)));
860 ClassOp::create(builder, StringAttr::get(
context, Twine(name) +
"_out"),
861 {{constants.getDomainInfoIn(),
864 {constants.getDomainInfoOut(),
867 {constants.getAssociationsIn(),
870 {constants.getAssociationsOut(),
874 auto connectPairWise = [&builder](ClassOp &classOp) {
875 builder.setInsertionPointToStart(classOp.getBodyBlock());
876 for (
size_t i = 0, e = classOp.getNumPorts(); i != e; i += 2)
877 PropAssignOp::create(builder, classOp.getArgument(i + 1),
878 classOp.getArgument(i));
880 connectPairWise(classIn);
881 connectPairWise(classOut);
883 classes.insert({name, {classIn, classOut}});
891LogicalResult LowerCircuit::lowerCircuit() {
892 LLVM_DEBUG(llvm::dbgs() <<
"Processing domains:\n");
893 for (
auto domain : llvm::make_early_inc_range(circuit.getOps<DomainOp>())) {
894 LLVM_DEBUG(llvm::dbgs() <<
" - " << domain.getName() <<
"\n");
895 if (failed(lowerDomain(domain)))
899 LLVM_DEBUG(llvm::dbgs() <<
"Processing modules:\n");
901 auto moduleOp = dyn_cast<FModuleLike>(node.
getModule<Operation *>());
904 LLVM_DEBUG(llvm::dbgs()
905 <<
" - module: " << moduleOp.getModuleName() <<
"\n");
906 LowerModule lowerModule(moduleOp, classes, constants, instanceGraph);
907 if (failed(lowerModule.lowerModule()))
909 LLVM_DEBUG(llvm::dbgs() <<
" instances:\n");
910 return lowerModule.lowerInstances();
920 LowerCircuit lowerCircuit(getOperation(), getAnalysis<InstanceGraph>(),
922 if (failed(lowerCircuit.lowerCircuit()))
923 return signalPassFailure();
925 markAnalysesPreserved<InstanceGraph>();
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static Location getLoc(DefSlot slot)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
void runOnOperation() override
InstanceRecord * addInstance(InstanceOpInterface instance, InstanceGraphNode *target)
Record a new instance op in the body of this module.
auto getModule()
Get the module that this node is tracking.
virtual InstanceGraphNode * addModule(ModuleOpInterface module)
Add a newly created module to the instance graph.
virtual void replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst)
Replaces an instance of a module with another instance.
decltype(auto) walkPostOrder(Fn &&fn)
Perform a post-order walk across the modules.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
size_t getNumPorts(Operation *op)
Return the number of ports in a module-like thing (modules, memories, etc).
bool hasZeroBitWidth(FIRRTLType type)
Return true if the type has zero bit width.
BaseTy type_dyn_cast(Type type)
igraph::InstanceGraphNode InstanceGraphNode
std::map< std::string, std::set< std::string > > InstanceGraph
Iterates over the handshake::FuncOp's in the program to build an instance graph.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.