CIRCT 22.0.0git
Loading...
Searching...
No Matches
LowerDomains.cpp
Go to the documentation of this file.
1//===- LowerDomains.cpp - Lower domain information to properties ----------===//
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 lowers all FIRRTL domain information into classes, objects, and
10// properties. This is part of the compilation of FIRRTL domains where they are
11// inferred and checked (See: the InferDomains pass) and then lowered (this
12// pass). After this pass runs, all domain information has been removed from
13// its original representation.
14//
15// Each domain is lowered into two classes: (1) a class that has the exact same
16// input/output properties as its corresponding domain and (2) a class that is
17// used to track the associations of the domain. Every input domain port is
18// lowered to an input of type (1) and an output of type (2). Every output
19// domain port is lowered to an output of type (2).
20//
21// Intuitively, (1) is the information that a user must specify about a domain
22// and (2) is the associations for that domain.
23//
24// This pass needs to run after InferDomains and before LowerClasses. This pass
25// assumes that all domain information is available. It is not written in such
26// a way that partial domain information can be lowered incrementally, e.g.,
27// interleaving InferDomains and LowerDomains with passes that incrementally add
28// domain information will not work. This is because LowerDomains is closer to
29// a conversion than a pass. It is expected that this is part of the FIRRTL to
30// HW pass pipeline.
31//
32// There are a number of limitations in this pass presently, much of which are
33// coupled to the representation of domains. Currently, domain information on
34// ports marks a port as being either a domain or having domain association
35// information, but not both. This precludes having aggregates that contain
36// domain types. (Or: this pass assumes that a pass like LowerOpenAggs has run
37// to do this splitting.) There are no requirements that LowerTypes has run,
38// assuming this post-LowerOpenAggs representation.
39//
40// As the representation of domains changes to allow for associations on fields
41// and domain types to be part of aggregates, this pass will require updates.
42//
43//===----------------------------------------------------------------------===//
44
47#include "circt/Support/Debug.h"
48#include "llvm/ADT/MapVector.h"
49#include "llvm/ADT/TypeSwitch.h"
50#include "llvm/Support/Debug.h"
51#include "llvm/Support/Threading.h"
52
53namespace circt {
54namespace firrtl {
55#define GEN_PASS_DEF_LOWERDOMAINS
56#include "circt/Dialect/FIRRTL/Passes.h.inc"
57} // namespace firrtl
58} // namespace circt
59
60using namespace circt;
61using namespace firrtl;
62using mlir::UnrealizedConversionCastOp;
63
64class LowerDomainsPass : public impl::LowerDomainsBase<LowerDomainsPass> {
65 using Base::Base;
66 void runOnOperation() override;
67};
68
69#define DEBUG_TYPE \
70 impl::LowerDomainsBase<LowerDomainsPass>::getArgumentName().data()
71
72namespace {
73/// Minimally track information about an association of a port to a domain.
74struct AssociationInfo {
75 /// The DistinctAttr (annotation) that is used to identify the port.
76 DistinctAttr distinctAttr;
77
78 /// The port's location. This is used to generate exact information about
79 /// certain property ops created later.
80 Location loc;
81};
82
83/// Track information about the lowering of a domain port.
84struct DomainInfo {
85 /// An instance of an object which will be used to track an instance of the
86 /// domain-lowered class (which is the identity of the domain) and all its
87 /// associations.
88 ObjectOp op;
89
90 /// The index of the optional input port that will be hooked up to a field of
91 /// the ObjectOp. This port is an instance of the domain-lowered class. If
92 /// this is created due to an output domain port, then this is nullopt.
93 std::optional<unsigned> inputPort;
94
95 /// The index of the output port that the ObjectOp will be connected to. This
96 /// port communicates back to the user information about the associations.
97 unsigned outputPort;
98
99 /// A conversion cast that is used to temporarily replace the port while it is
100 /// being deleted. If the port has no uses, then this will be empty. Note:
101 /// this is used as a true temporary and may be overwritten. During
102 /// `lowerModules()` this is used to store a module port temporary. During
103 /// `lowerInstances()`, this stores an instance port temporary.
104 UnrealizedConversionCastOp temp;
105
106 /// A vector of minimal association info that will be hooked up to the
107 /// associations of this ObjectOp.
108 SmallVector<AssociationInfo> associations{};
109
110 /// Return a DomainInfo for an input domain port. This will have both an
111 /// input port at the current index and an output port at the next index.
112 /// Other members are default-initialized and will be set later.
113 static DomainInfo input(unsigned portIndex) {
114 return DomainInfo({{}, portIndex, portIndex + 1, {}, {}});
115 }
116
117 /// Return a DomainInfo for an output domain port. This creates only an
118 /// output port at the current index. Other members are default-initialized
119 /// and will be set later.
120 static DomainInfo output(unsigned portIndex) {
121 return DomainInfo({{}, std::nullopt, portIndex, {}, {}});
122 }
123};
124
125/// Struct of the two classes created from a domain, an input class (which is
126/// one-to-one with the domain) and an output class (which tracks the input
127/// class and any associations).
128struct Classes {
129 /// The domain-lowered class.
130 ClassOp input;
131
132 /// A class tracking an instance of the input class and a list of
133 /// associations.
134 ClassOp output;
135};
136
137/// Thread safe, lazy pool of constant attributes
138class Constants {
139
140 /// Lazily constructed empty array attribute.
141 struct EmptyArray {
142 llvm::once_flag flag;
143 ArrayAttr attr;
144 };
145
146 /// Lazy constructed attributes necessary for building an output class.
147 struct ClassOut {
148 llvm::once_flag flag;
149 StringAttr domainInfoIn;
150 StringAttr domainInfoOut;
151 StringAttr associationsIn;
152 StringAttr associationsOut;
153 };
154
155public:
156 Constants(MLIRContext *context) : context(context) {}
157
158 /// Return an empty ArrayAttr.
159 ArrayAttr getEmptyArrayAttr() {
160 llvm::call_once(emptyArray.flag,
161 [&] { emptyArray.attr = ArrayAttr::get(context, {}); });
162 return emptyArray.attr;
163 }
164
165private:
166 /// Construct all the field info attributes.
167 void initClassOut() {
168 llvm::call_once(classOut.flag, [&] {
169 classOut.domainInfoIn = StringAttr::get(context, "domainInfo_in");
170 classOut.domainInfoOut = StringAttr::get(context, "domainInfo_out");
171 classOut.associationsIn = StringAttr::get(context, "associations_in");
172 classOut.associationsOut = StringAttr::get(context, "associations_out");
173 });
174 }
175
176public:
177 /// Return a "domainInfo_in" attr.
178 StringAttr getDomainInfoIn() {
179 initClassOut();
180 return classOut.domainInfoIn;
181 }
182
183 /// Return a "domainInfo_out" attr.
184 StringAttr getDomainInfoOut() {
185 initClassOut();
186 return classOut.domainInfoOut;
187 }
188
189 /// Return an "associations_in" attr.
190 StringAttr getAssociationsIn() {
191 initClassOut();
192 return classOut.associationsIn;
193 }
194
195 /// Return an "associations_out" attr.
196 StringAttr getAssociationsOut() {
197 initClassOut();
198 return classOut.associationsOut;
199 }
200
201private:
202 /// An MLIR context necessary for creating new attributes.
203 MLIRContext *context;
204
205 /// Lazily constructed attributes
206 EmptyArray emptyArray;
207 ClassOut classOut;
208};
209
210/// Replace the value with a temporary 0-1 unrealized conversion. Return the
211/// single conversion operation. This is used to "stub out" the users of a
212/// module or instance port while it is being converted from a domain port to a
213/// class port. This function lets us erase the port. This function is
214/// intended to be used with `splice` to replace the 0-1 conversion with a 1-1
215/// conversion once the new port, of the new type, is available.
216static UnrealizedConversionCastOp stubOut(Value value) {
217 if (!value.hasNUsesOrMore(1))
218 return {};
219
220 OpBuilder builder(value.getContext());
221 builder.setInsertionPointAfterValue(value);
222 auto temp = UnrealizedConversionCastOp::create(
223 builder, builder.getUnknownLoc(), {value.getType()}, {});
224 value.replaceAllUsesWith(temp.getResult(0));
225 return temp;
226}
227
228/// Replace a temporary 0-1 conversion cast with a 1-1 conversion cast. Erase
229/// the old conversion. Return the new conversion. This function is used to
230/// hook up a module or instance port to an operation user of the old type.
231/// After all ports have been spliced, the splice-operation-splice can be
232/// replaced with a new operation that handles the new port types.
233///
234/// Assumptions:
235///
236/// 1. `temp` is either null or a 0-1 conversion cast.
237/// 2. If `temp` is non-null, then `value` is non-null.
238static UnrealizedConversionCastOp splice(UnrealizedConversionCastOp temp,
239 Value value) {
240 if (!temp)
241 return {};
242
243 // This must be a 0-1 unrealized conversion. Anything else is unexpected.
244 assert(temp && temp.getNumResults() == 1 && temp.getNumOperands() == 0);
245
246 // Value must be non-null.
247 assert(value);
248
249 auto oldValue = temp.getResult(0);
250
251 OpBuilder builder(temp);
252 auto splice = UnrealizedConversionCastOp::create(
253 builder, builder.getUnknownLoc(), {oldValue.getType()}, {value});
254 oldValue.replaceAllUsesWith(splice.getResult(0));
255 temp.erase();
256 return splice;
257}
258
259/// Class that is used to lower a module that may contain domain ports. This is
260/// intended to be used by calling `lowerModule()` to lower the module. This
261/// builds up state in the class which is then used when calling
262/// `lowerInstances()` to update all instantiations of this module. If
263/// multi-threading, care needs to be taken to only call `lowerInstances()`
264/// when instantiating modules are not _also_ being updated.
265class LowerModule {
266
267public:
268 LowerModule(FModuleLike op, const DenseMap<Attribute, Classes> &classes,
269 Constants &constants, InstanceGraph &instanceGraph)
270 : op(op), eraseVector(op.getNumPorts()), domainToClasses(classes),
271 constants(constants), instanceGraph(instanceGraph) {}
272
273 /// Lower the associated module. Replace domain ports with input/output class
274 /// ports.
275 LogicalResult lowerModule();
276
277 /// Lower all instances of the associated module. This relies on state built
278 /// up during `lowerModule` and must be run _afterwards_.
279 LogicalResult lowerInstances();
280
281private:
282 /// The module this class is lowering
283 FModuleLike op;
284
285 /// Ports that should be erased
286 BitVector eraseVector;
287
288 /// The ports that should be inserted, _after deletion_ by application of
289 /// `eraseVector`.
290 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
291
292 /// A mapping of old result to new result
293 SmallVector<std::pair<unsigned, unsigned>> resultMap;
294
295 /// Mapping of domain name to the lowered input and output class
296 const DenseMap<Attribute, Classes> &domainToClasses;
297
298 /// Lazy constant pool
299 Constants &constants;
300
301 /// Reference to an instance graph. This _will_ be mutated.
302 ///
303 /// TODO: The mutation of this is _not_ thread safe. This needs to be fixed
304 /// if this pass is parallelized.
305 InstanceGraph &instanceGraph;
306
307 // Information about a domain. This is built up during the first iteration
308 // over the ports. This needs to preserve insertion order.
309 llvm::MapVector<unsigned, DomainInfo> indexToDomain;
310};
311
312LogicalResult LowerModule::lowerModule() {
313 // TOOD: Is there an early exit condition here? It is not as simple as
314 // checking for domain ports as the module may have no domain ports, but have
315 // been modified by an earlier `lowerInstances()` call.
316
317 // Much of the lowering is conditioned on whether or not this module has a
318 // body. If it has a body, then we need to instantiate an object for each
319 // domain port and hook up all the domain ports to annotations added to each
320 // associated port. Skip modules which don't have domains.
321 auto shouldProcess =
322 TypeSwitch<Operation *, std::optional<Block *>>(op)
323 .Case<FModuleOp>([](auto op) { return op.getBodyBlock(); })
324 .Case<FExtModuleOp>([](auto) { return nullptr; })
325 // Skip all other modules.
326 .Default([](auto) { return std::nullopt; });
327 if (!shouldProcess)
328 return success();
329 Block *body = *shouldProcess;
330
331 auto *context = op.getContext();
332
333 // The new port annotations. These will be set after all deletions and
334 // insertions.
335 SmallVector<Attribute> portAnnotations;
336
337 // Iterate over the ports, staging domain ports for removal and recording the
338 // associations of non-domain ports. After this, domain ports will be deleted
339 // and then class ports will be inserted. This loop therefore needs to track
340 // three indices:
341 // 1. i tracks the original port index.
342 // 2. iDel tracks the port index after deletion.
343 // 3. iIns tracks the port index after insertion.
344 OpBuilder::InsertPoint insertPoint;
345 if (body)
346 insertPoint = {body, body->begin()};
347 auto ports = op.getPorts();
348 for (unsigned i = 0, iDel = 0, iIns = 0, e = op.getNumPorts(); i != e; ++i) {
349 auto port = cast<PortInfo>(ports[i]);
350
351 // Mark domain type ports for removal. Add information to `domainInfo`.
352 if (auto domain = dyn_cast_or_null<FlatSymbolRefAttr>(port.domains)) {
353 eraseVector.set(i);
354
355 // Instantiate a domain object with association information.
356 auto [classIn, classOut] = domainToClasses.at(domain.getAttr());
357
358 indexToDomain[i] = port.direction == Direction::In
359 ? DomainInfo::input(iIns)
360 : DomainInfo::output(iIns);
361
362 if (body) {
363 // Insert objects in-order at the top of the module's body. These
364 // cannot be inserted at the end as they may have users.
365 ImplicitLocOpBuilder builder(port.loc, context);
366 builder.restoreInsertionPoint(insertPoint);
367
368 // Create the object, add information about it to domain info.
369 auto object = ObjectOp::create(
370 builder, classOut,
371 StringAttr::get(context, Twine(port.name) + "_object"));
372 instanceGraph.lookup(op)->addInstance(object,
373 instanceGraph.lookup(classOut));
374 indexToDomain[i].op = object;
375 indexToDomain[i].temp = stubOut(body->getArgument(i));
376
377 // Save the insertion point for the next go-around. This allows the
378 // objects to be inserted in port order as opposed to reverse port
379 // order.
380 insertPoint = builder.saveInsertionPoint();
381 }
382
383 // Add input and output property ports that encode the property inputs
384 // (which the user must provide for the domain) and the outputs that
385 // encode this information and the associations. Keep iIns up to date
386 // based on the number of ports added.
387 if (port.direction == Direction::In) {
388 newPorts.push_back({iDel, PortInfo(port.name, classIn.getInstanceType(),
389 Direction::In)});
390 portAnnotations.push_back(constants.getEmptyArrayAttr());
391 ++iIns;
392 }
393 newPorts.push_back(
394 {iDel, PortInfo(StringAttr::get(context, Twine(port.name) + "_out"),
395 classOut.getInstanceType(), Direction::Out)});
396 ++iIns;
397
398 // Update annotations.
399 portAnnotations.push_back(constants.getEmptyArrayAttr());
400
401 // Don't increment the iDel since we deleted one port.
402 continue;
403 }
404
405 // This is a non-domain port. It will NOT be deleted. Increment both
406 // indices.
407 ++iDel;
408 ++iIns;
409
410 // If this port has domain associations, then we need to add port annotation
411 // trackers. These will be hooked up to the Object's associations later.
412 // However, if there is no domain information, then annotations do not need
413 // to be modified. Early continue first, adding trackers otherwise. Only
414 // create one tracker for all associations.
415 ArrayAttr domainAttr = cast_or_null<ArrayAttr>(port.domains);
416 if (!domainAttr || domainAttr.empty()) {
417 portAnnotations.push_back(port.annotations.getArrayAttr());
418 continue;
419 }
420
421 // If the port is zero-width, then we don't need annotation trackers.
422 // LowerToHW removes zero-width ports and it will error if there are inner
423 // symbols on zero-width things it is trying to remove.
424 if (auto firrtlType = type_dyn_cast<FIRRTLType>(port.type))
425 if (hasZeroBitWidth(firrtlType)) {
426 portAnnotations.push_back(port.annotations.getArrayAttr());
427 continue;
428 }
429
430 SmallVector<Annotation> newAnnotations;
431 DistinctAttr id;
432 for (auto indexAttr : domainAttr.getAsRange<IntegerAttr>()) {
433 if (!id) {
434 id = DistinctAttr::create(UnitAttr::get(context));
435 newAnnotations.push_back(Annotation(DictionaryAttr::getWithSorted(
436 context, {{"class", StringAttr::get(context, "circt.tracker")},
437 {"id", id}})));
438 }
439 indexToDomain[indexAttr.getUInt()].associations.push_back({id, port.loc});
440 }
441 if (!newAnnotations.empty())
442 port.annotations.addAnnotations(newAnnotations);
443 portAnnotations.push_back(port.annotations.getArrayAttr());
444 }
445
446 // Erase domain ports and clear domain association information.
447 op.erasePorts(eraseVector);
448 op.setDomainInfoAttr(constants.getEmptyArrayAttr());
449
450 // Insert new property ports and hook these up to the object that was
451 // instantiated earlier.
452 op.insertPorts(newPorts);
453
454 if (body) {
455 for (auto const &[_, info] : indexToDomain) {
456 auto [object, inputPort, outputPort, temp, associations] = info;
457 OpBuilder builder(object);
458 builder.setInsertionPointAfter(object);
459 // Hook up domain ports. If this was an input domain port, then drive the
460 // object's "domain in" port with the new input class port. Splice
461 // references to the old port with the new port---users (e.g., domain
462 // defines) will be updated later. If an output, then splice to the
463 // object's "domain in" port. If this participates in domain defines,
464 // this will be hoked up later.
465 auto subDomainInfoIn =
466 ObjectSubfieldOp::create(builder, object.getLoc(), object, 0);
467 if (inputPort) {
468 PropAssignOp::create(builder, object.getLoc(), subDomainInfoIn,
469 body->getArgument(*inputPort));
470 splice(temp, body->getArgument(*inputPort));
471 } else {
472 splice(temp, subDomainInfoIn);
473 }
474
475 // Hook up the "association in" port.
476 auto subAssociations =
477 ObjectSubfieldOp::create(builder, object.getLoc(), object, 2);
478 SmallVector<Value> paths;
479 for (auto [id, loc] : associations) {
480 paths.push_back(PathOp::create(
481 builder, loc, TargetKindAttr::get(context, TargetKind::Reference),
482 id));
483 }
484 auto list = ListCreateOp::create(
485 builder, object.getLoc(),
486 ListType::get(context, cast<PropertyType>(PathType::get(context))),
487 paths);
488 PropAssignOp::create(builder, object.getLoc(), subAssociations, list);
489
490 // Connect the object to the output port.
491 PropAssignOp::create(builder, object.getLoc(),
492 body->getArgument(outputPort), object);
493 }
494
495 // Remove all domain users. Delay deleting conversions until the module
496 // body is walked as it is possible that conversions have multiple users.
497 //
498 // This has the effect of removing all the conversions that we created.
499 // Note: this relies on the fact that we don't create conversions if there
500 // are no users. (See early exits in `stubOut` and `splice`.)
501 //
502 // Note: this cannot visit conversions directly as we don't have guarantees
503 // that there won't be other conversions flying around. E.g., LowerDPI
504 // leaves conversions that are cleaned up by LowerToHW. (This is likely
505 // wrong, but it doesn't cost us anything to do it this way.)
506 DenseSet<Operation *> conversionsToErase;
507 DenseSet<Operation *> operationsToErase;
508 auto walkResult = op.walk([&](Operation *walkOp) {
509 // This is an operation that we have previously determined can be deleted
510 // when examining an earlier operation. Delete it now as it is only safe
511 // to do so when visiting it.
512 if (operationsToErase.contains(walkOp)) {
513 walkOp->erase();
514 return WalkResult::advance();
515 }
516
517 // Handle UnsafeDomainCastOp.
518 if (auto castOp = dyn_cast<UnsafeDomainCastOp>(walkOp)) {
519 for (auto value : castOp.getDomains()) {
520 auto *conversion = value.getDefiningOp();
521 assert(isa<UnrealizedConversionCastOp>(conversion));
522 conversionsToErase.insert(conversion);
523 }
524
525 castOp.getResult().replaceAllUsesWith(castOp.getInput());
526 castOp.erase();
527 return WalkResult::advance();
528 }
529
530 // Track anonymous domains for later traversal and erasure.
531 if (auto anonDomain = dyn_cast<DomainCreateAnonOp>(walkOp)) {
532 conversionsToErase.insert(anonDomain);
533 return WalkResult::advance();
534 }
535
536 // If we see a WireOp of a domain type, then we want to erase it. To do
537 // this, find what is driving it and what it is driving and then replace
538 // that triplet of operations with a single domain define inserted before
539 // the latest define. If the wire is undriven or if the wire drives
540 // nothing, then everything will be deleted.
541 //
542 // Before:
543 //
544 // %a = firrtl.wire : !firrtl.domain // <- operation being visited
545 // firrtl.domain.define %a, %src
546 // firrtl.domain.define %dst, %a
547 //
548 // After:
549 // %a = firrtl.wire : !firrtl.domain // <- to-be-deleted after walk
550 // firrtl.domain.define %a, %src // <- to-be-deleted when visited
551 // firrtl.domain.define %dst, %src // <- added
552 // firrtl.domain.define %dst, %a // <- to-be-deleted when visited
553 if (auto wireOp = dyn_cast<WireOp>(walkOp)) {
554 if (type_isa<DomainType>(wireOp.getResult().getType())) {
555 Value src;
556 SmallVector<Value> dsts;
557 DomainDefineOp lastDefineOp;
558 for (auto *user : llvm::make_early_inc_range(wireOp->getUsers())) {
559 auto domainDefineOp = dyn_cast<DomainDefineOp>(user);
560 if (operationsToErase.contains(domainDefineOp))
561 continue;
562 if (!domainDefineOp) {
563 auto diag = wireOp.emitOpError()
564 << "cannot be lowered by `LowerDomains` because it "
565 "has a user that is not a domain define op";
566 diag.attachNote(user->getLoc()) << "is one such user";
567 return WalkResult::interrupt();
568 }
569 if (!lastDefineOp || lastDefineOp->isBeforeInBlock(domainDefineOp))
570 lastDefineOp = domainDefineOp;
571 if (wireOp == domainDefineOp.getSrc().getDefiningOp())
572 dsts.push_back(domainDefineOp.getDest());
573 else
574 src = domainDefineOp.getSrc();
575 operationsToErase.insert(domainDefineOp);
576 }
577 conversionsToErase.insert(wireOp);
578
579 // If this wire is dead or undriven, then there's nothing to do.
580 if (!src || dsts.empty())
581 return WalkResult::advance();
582 // Insert a domain define that removes the need for the wire. This is
583 // inserted just before the latest domain define involving the wire.
584 // This is done to prevent unnecessary permutations of the IR.
585 OpBuilder builder(lastDefineOp);
586 for (auto dst : llvm::reverse(dsts))
587 DomainDefineOp::create(builder, builder.getUnknownLoc(), dst, src);
588 }
589 return WalkResult::advance();
590 }
591
592 // Handle DomainDefineOp. Skip all other operations.
593 auto defineOp = dyn_cast<DomainDefineOp>(walkOp);
594 if (!defineOp)
595 return WalkResult::advance();
596
597 // There are only two possibilities for kinds of `DomainDefineOp`s that we
598 // can see a this point: the destination is always a conversion cast and
599 // the source is _either_ (1) a conversion cast if the source is a module
600 // or instance port or (2) an anonymous domain op. This relies on the
601 // earlier "canonicalization" that erased `WireOp`s to leave only
602 // `DomainDefineOp`s.
603 auto *src = defineOp.getSrc().getDefiningOp();
604 auto dest = dyn_cast<UnrealizedConversionCastOp>(
605 defineOp.getDest().getDefiningOp());
606 if (!src || !dest)
607 return WalkResult::advance();
608
609 conversionsToErase.insert(src);
610 conversionsToErase.insert(dest);
611
612 if (auto srcCast = dyn_cast<UnrealizedConversionCastOp>(src)) {
613 assert(srcCast.getNumOperands() == 1 && srcCast.getNumResults() == 1);
614 OpBuilder builder(defineOp);
615 PropAssignOp::create(builder, defineOp.getLoc(), dest.getOperand(0),
616 srcCast.getOperand(0));
617 } else if (!isa<DomainCreateAnonOp>(src)) {
618 auto diag = defineOp.emitOpError()
619 << "has a source which cannot be lowered by 'LowerDomains'";
620 diag.attachNote(src->getLoc()) << "unsupported source is here";
621 return WalkResult::interrupt();
622 }
623
624 defineOp->erase();
625 return WalkResult::advance();
626 });
627
628 if (walkResult.wasInterrupted())
629 return failure();
630
631 // Erase all the conversions.
632 for (auto *op : conversionsToErase)
633 op->erase();
634 }
635
636 // Set new port annotations.
637 op.setPortAnnotationsAttr(ArrayAttr::get(context, portAnnotations));
638
639 return success();
640}
641
642LogicalResult LowerModule::lowerInstances() {
643 // Early exit if there is no work to do.
644 if (eraseVector.none() && newPorts.empty())
645 return success();
646
647 // TODO: There is nothing to do unless this instance is a module or external
648 // module. This mirros code in the `lowerModule` member function. Figure out
649 // a way to clean this up, possible by making `LowerModule` a true noop if
650 // this is not one of these kinds of modulelikes.
651 if (!isa<FModuleOp, FExtModuleOp>(op))
652 return success();
653
654 auto *node = instanceGraph.lookup(cast<igraph::ModuleOpInterface>(*op));
655 for (auto *use : llvm::make_early_inc_range(node->uses())) {
656 auto instanceOp = dyn_cast<InstanceOp>(*use->getInstance());
657 if (!instanceOp) {
658 use->getInstance().emitOpError()
659 << "has an unimplemented lowering in LowerDomains";
660 return failure();
661 }
662 LLVM_DEBUG(llvm::dbgs()
663 << " - " << instanceOp.getInstanceName() << "\n");
664
665 for (auto i : eraseVector.set_bits())
666 indexToDomain[i].temp = stubOut(instanceOp.getResult(i));
667
668 auto erased = instanceOp.cloneWithErasedPortsAndReplaceUses(eraseVector);
669 auto inserted = erased.cloneWithInsertedPortsAndReplaceUses(newPorts);
670 instanceGraph.replaceInstance(instanceOp, inserted);
671
672 for (auto &[i, info] : indexToDomain) {
673 Value splicedValue;
674 if (info.inputPort) {
675 // Handle input port. Just hook it up.
676 splicedValue = inserted.getResult(*info.inputPort);
677 } else {
678 // Handle output port. Splice in the output field that contains the
679 // domain object. This requires creating an object subfield.
680 OpBuilder builder(inserted);
681 builder.setInsertionPointAfter(inserted);
682 splicedValue = ObjectSubfieldOp::create(
683 builder, inserted.getLoc(), inserted.getResult(info.outputPort), 1);
684 }
685
686 splice(info.temp, splicedValue);
687 }
688
689 instanceOp.erase();
690 erased.erase();
691 }
692
693 return success();
694}
695
696/// Class used to lwoer a circuit that contains domains. This hides any state
697/// that may need to be cleared across invocations of this pass to keep the
698/// actual pass code cleaner.
699class LowerCircuit {
700
701public:
702 LowerCircuit(CircuitOp circuit, InstanceGraph &instanceGraph,
703 llvm::Statistic &numDomains)
704 : circuit(circuit), instanceGraph(instanceGraph),
705 constants(circuit.getContext()), numDomains(numDomains) {}
706
707 /// Lower the circuit, removing all domains.
708 LogicalResult lowerCircuit();
709
710private:
711 /// Lower one domain.
712 LogicalResult lowerDomain(DomainOp);
713
714 /// The circuit this class is lowering.
715 CircuitOp circuit;
716
717 /// A reference to an instance graph. This will be mutated.
718 InstanceGraph &instanceGraph;
719
720 /// Internal store of lazily constructed constants.
721 Constants constants;
722
723 /// Mutable reference to the number of domains this class will lower.
724 llvm::Statistic &numDomains;
725
726 /// Store of the mapping from a domain name to the classes that it has been
727 /// lowered into.
728 DenseMap<Attribute, Classes> classes;
729};
730
731LogicalResult LowerCircuit::lowerDomain(DomainOp op) {
732 ImplicitLocOpBuilder builder(op.getLoc(), op);
733 auto *context = op.getContext();
734 auto name = op.getNameAttr();
735 SmallVector<PortInfo> classInPorts;
736 for (auto field : op.getFields().getAsRange<DomainFieldAttr>())
737 classInPorts.append({{/*name=*/builder.getStringAttr(
738 Twine(field.getName().getValue()) + "_in"),
739 /*type=*/field.getType(), /*dir=*/Direction::In},
740 {/*name=*/builder.getStringAttr(
741 Twine(field.getName().getValue()) + "_out"),
742 /*type=*/field.getType(), /*dir=*/Direction::Out}});
743 auto classIn = ClassOp::create(builder, name, classInPorts);
744 auto classInType = classIn.getInstanceType();
745 auto pathListType =
746 ListType::get(context, cast<PropertyType>(PathType::get(context)));
747 auto classOut =
748 ClassOp::create(builder, StringAttr::get(context, Twine(name) + "_out"),
749 {{/*name=*/constants.getDomainInfoIn(),
750 /*type=*/classInType,
751 /*dir=*/Direction::In},
752 {/*name=*/constants.getDomainInfoOut(),
753 /*type=*/classInType,
754 /*dir=*/Direction::Out},
755 {/*name=*/constants.getAssociationsIn(),
756 /*type=*/pathListType,
757 /*dir=*/Direction::In},
758 {/*name=*/constants.getAssociationsOut(),
759 /*type=*/pathListType,
760 /*dir=*/Direction::Out}});
761
762 auto connectPairWise = [&builder](ClassOp &classOp) {
763 builder.setInsertionPointToStart(classOp.getBodyBlock());
764 for (size_t i = 0, e = classOp.getNumPorts(); i != e; i += 2)
765 PropAssignOp::create(builder, classOp.getArgument(i + 1),
766 classOp.getArgument(i));
767 };
768 connectPairWise(classIn);
769 connectPairWise(classOut);
770
771 classes.insert({name, {classIn, classOut}});
772 instanceGraph.addModule(classIn);
773 instanceGraph.addModule(classOut);
774 op.erase();
775 ++numDomains;
776 return success();
777}
778
779LogicalResult LowerCircuit::lowerCircuit() {
780 LLVM_DEBUG(llvm::dbgs() << "Processing domains:\n");
781 for (auto domain : llvm::make_early_inc_range(circuit.getOps<DomainOp>())) {
782 LLVM_DEBUG(llvm::dbgs() << " - " << domain.getName() << "\n");
783 if (failed(lowerDomain(domain)))
784 return failure();
785 }
786
787 LLVM_DEBUG(llvm::dbgs() << "Processing modules:\n");
788 return instanceGraph.walkPostOrder([&](InstanceGraphNode &node) {
789 auto moduleOp = dyn_cast<FModuleLike>(node.getModule<Operation *>());
790 if (!moduleOp)
791 return success();
792 LLVM_DEBUG(llvm::dbgs() << " - module: " << moduleOp.getName() << "\n");
793 LowerModule lowerModule(moduleOp, classes, constants, instanceGraph);
794 if (failed(lowerModule.lowerModule()))
795 return failure();
796 LLVM_DEBUG(llvm::dbgs() << " instances:\n");
797 return lowerModule.lowerInstances();
798 });
799
800 return success();
801}
802} // namespace
803
806
807 LowerCircuit lowerCircuit(getOperation(), getAnalysis<InstanceGraph>(),
808 numDomains);
809 if (failed(lowerCircuit.lowerCircuit()))
810 return signalPassFailure();
811
812 markAnalysesPreserved<InstanceGraph>();
813}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:216
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
void runOnOperation() override
This class provides a read-only projection of an annotation.
This graph tracks modules and where they are instantiated.
This is a Node in the InstanceGraph.
auto getModule()
Get the module that this node is tracking.
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.
void info(Twine message)
Definition LSPUtils.cpp:20
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
This holds the name and type that describes the module's ports.