CIRCT 24.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
49#include "circt/Support/Debug.h"
50#include "llvm/ADT/MapVector.h"
51#include "llvm/ADT/TypeSwitch.h"
52#include "llvm/Support/Debug.h"
53#include "llvm/Support/Threading.h"
54
55namespace circt {
56namespace firrtl {
57#define GEN_PASS_DEF_LOWERDOMAINS
58#include "circt/Dialect/FIRRTL/Passes.h.inc"
59} // namespace firrtl
60} // namespace circt
61
62using namespace circt;
63using namespace firrtl;
64using mlir::UnrealizedConversionCastOp;
65
66class LowerDomainsPass : public impl::LowerDomainsBase<LowerDomainsPass> {
67 using Base::Base;
68 void runOnOperation() override;
69};
70
71#define DEBUG_TYPE \
72 impl::LowerDomainsBase<LowerDomainsPass>::getArgumentName().data()
73
74namespace {
75/// Minimally track information about an association of a port to a domain.
76struct AssociationInfo {
77 /// The DistinctAttr (annotation) that is used to identify the port.
78 DistinctAttr distinctAttr;
79
80 /// The port's location. This is used to generate exact information about
81 /// certain property ops created later.
82 Location loc;
83};
84
85/// Track information about the lowering of a domain port.
86struct DomainInfo {
87 /// An instance of an object which will be used to track an instance of the
88 /// domain-lowered class (which is the identity of the domain) and all its
89 /// associations.
90 ObjectOp op;
91
92 /// The index of the optional input port that will be hooked up to a field of
93 /// the ObjectOp. This port is an instance of the domain-lowered class. If
94 /// this is created due to an output domain port, then this is nullopt.
95 std::optional<unsigned> inputPort;
96
97 /// The index of the output port that the ObjectOp will be connected to. This
98 /// port communicates back to the user information about the associations.
99 unsigned outputPort;
100
101 /// A conversion cast that is used to temporarily replace the port while it is
102 /// being deleted. If the port has no uses, then this will be empty. Note:
103 /// this is used as a true temporary and may be overwritten. During
104 /// `lowerModules()` this is used to store a module port temporary. During
105 /// `lowerInstances()`, this stores an instance port temporary.
106 UnrealizedConversionCastOp temp;
107
108 /// A vector of minimal association info that will be hooked up to the
109 /// associations of this ObjectOp.
110 SmallVector<AssociationInfo> associations{};
111
112 /// Return a DomainInfo for an input domain port. This will have both an
113 /// input port at the current index and an output port at the next index.
114 /// Other members are default-initialized and will be set later.
115 static DomainInfo input(unsigned portIndex) {
116 return DomainInfo({{}, portIndex, portIndex + 1, {}, {}});
117 }
118
119 /// Return a DomainInfo for an output domain port. This creates only an
120 /// output port at the current index. Other members are default-initialized
121 /// and will be set later.
122 static DomainInfo output(unsigned portIndex) {
123 return DomainInfo({{}, std::nullopt, portIndex, {}, {}});
124 }
125};
126
127/// Struct of the two classes created from a domain, an input class (which is
128/// one-to-one with the domain) and an output class (which tracks the input
129/// class and any associations).
130struct Classes {
131 /// The domain-lowered class.
132 ClassOp input;
133
134 /// A class tracking an instance of the input class and a list of
135 /// associations.
136 ClassOp output;
137};
138
139/// Thread safe, lazy pool of constant attributes
140class Constants {
141
142 /// Lazily constructed empty array attribute.
143 struct EmptyArray {
144 llvm::once_flag flag;
145 ArrayAttr attr;
146 };
147
148 /// Lazy constructed attributes necessary for building an output class.
149 struct ClassOut {
150 llvm::once_flag flag;
151 StringAttr domainInfoIn;
152 StringAttr domainInfoOut;
153 StringAttr associationsIn;
154 StringAttr associationsOut;
155 };
156
157public:
158 Constants(MLIRContext *context) : context(context) {}
159
160 /// Return an empty ArrayAttr.
161 ArrayAttr getEmptyArrayAttr() {
162 llvm::call_once(emptyArray.flag,
163 [&] { emptyArray.attr = ArrayAttr::get(context, {}); });
164 return emptyArray.attr;
165 }
166
167private:
168 /// Construct all the field info attributes.
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");
175 });
176 }
177
178public:
179 /// Return a "domainInfo_in" attr.
180 StringAttr getDomainInfoIn() {
181 initClassOut();
182 return classOut.domainInfoIn;
183 }
184
185 /// Return a "domainInfo_out" attr.
186 StringAttr getDomainInfoOut() {
187 initClassOut();
188 return classOut.domainInfoOut;
189 }
190
191 /// Return an "associations_in" attr.
192 StringAttr getAssociationsIn() {
193 initClassOut();
194 return classOut.associationsIn;
195 }
196
197 /// Return an "associations_out" attr.
198 StringAttr getAssociationsOut() {
199 initClassOut();
200 return classOut.associationsOut;
201 }
202
203private:
204 /// An MLIR context necessary for creating new attributes.
205 MLIRContext *context;
206
207 /// Lazily constructed attributes
208 EmptyArray emptyArray;
209 ClassOut classOut;
210};
211
212/// Replace the value with a temporary 0-1 unrealized conversion. Return the
213/// single conversion operation. This is used to "stub out" the users of a
214/// module or instance port while it is being converted from a domain port to a
215/// class port. This function lets us erase the port. This function is
216/// intended to be used with `splice` to replace the 0-1 conversion with a 1-1
217/// conversion once the new port, of the new type, is available.
218static UnrealizedConversionCastOp stubOut(Value value) {
219 if (!value.hasNUsesOrMore(1))
220 return {};
221
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));
227 return temp;
228}
229
230/// Replace a temporary 0-1 conversion cast with a 1-1 conversion cast. Erase
231/// the old conversion. Return the new conversion. This function is used to
232/// hook up a module or instance port to an operation user of the old type.
233/// After all ports have been spliced, the splice-operation-splice can be
234/// replaced with a new operation that handles the new port types.
235///
236/// Assumptions:
237///
238/// 1. `temp` is either null or a 0-1 conversion cast.
239/// 2. If `temp` is non-null, then `value` is non-null.
240static UnrealizedConversionCastOp splice(UnrealizedConversionCastOp temp,
241 Value value) {
242 if (!temp)
243 return {};
244
245 // This must be a 0-1 unrealized conversion. Anything else is unexpected.
246 assert(temp && temp.getNumResults() == 1 && temp.getNumOperands() == 0);
247
248 // Value must be non-null.
249 assert(value);
250
251 auto oldValue = temp.getResult(0);
252
253 OpBuilder builder(temp);
254 auto splice = UnrealizedConversionCastOp::create(
255 builder, builder.getUnknownLoc(), {oldValue.getType()}, {value});
256 oldValue.replaceAllUsesWith(splice.getResult(0));
257 temp.erase();
258 return splice;
259}
260
261/// Class that is used to lower a module that may contain domain ports. This is
262/// intended to be used by calling `lowerModule()` to lower the module. This
263/// builds up state in the class which is then used when calling
264/// `lowerInstances()` to update all instantiations of this module. If
265/// multi-threading, care needs to be taken to only call `lowerInstances()`
266/// when instantiating modules are not _also_ being updated.
267class LowerModule {
268
269public:
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) {}
274
275 /// Lower the associated module. Replace domain ports with input/output class
276 /// ports.
277 LogicalResult lowerModule();
278
279 /// Lower all instances of the associated module. This relies on state built
280 /// up during `lowerModule` and must be run _afterwards_.
281 LogicalResult lowerInstances();
282
283private:
284 /// The module this class is lowering
285 FModuleLike op;
286
287 /// Ports that should be erased
288 BitVector eraseVector;
289
290 /// The ports that should be inserted, _after deletion_ by application of
291 /// `eraseVector`.
292 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
293
294 /// A mapping of old result to new result
295 SmallVector<std::pair<unsigned, unsigned>> resultMap;
296
297 /// Mapping of domain name to the lowered input and output class
298 const DenseMap<Attribute, Classes> &domainToClasses;
299
300 /// Lazy constant pool
301 Constants &constants;
302
303 /// Reference to an instance graph. This _will_ be mutated.
304 ///
305 /// TODO: The mutation of this is _not_ thread safe. This needs to be fixed
306 /// if this pass is parallelized.
307 InstanceGraph &instanceGraph;
308
309 // Information about a domain. This is built up during the first iteration
310 // over the ports. This needs to preserve insertion order.
311 llvm::MapVector<unsigned, DomainInfo> indexToDomain;
312};
313
314LogicalResult LowerModule::lowerModule() {
315 // TOOD: Is there an early exit condition here? It is not as simple as
316 // checking for domain ports as the module may have no domain ports, but have
317 // been modified by an earlier `lowerInstances()` call.
318
319 // Much of the lowering is conditioned on whether or not this module has a
320 // body. If it has a body, then we need to instantiate an object for each
321 // domain port and hook up all the domain ports to annotations added to each
322 // associated port. Skip modules which don't have domains.
323 auto shouldProcess =
324 TypeSwitch<Operation *, std::optional<Block *>>(op)
325 .Case<FModuleOp>([](auto op) { return op.getBodyBlock(); })
326 .Case<FExtModuleOp>([](auto) { return nullptr; })
327 // Skip all other modules.
328 .Default([](auto) { return std::nullopt; });
329 if (!shouldProcess)
330 return success();
331 Block *body = *shouldProcess;
332
333 auto *context = op.getContext();
334
335 // The new port annotations. These will be set after all deletions and
336 // insertions.
337 SmallVector<Attribute> portAnnotations;
338
339 // Iterate over the ports, staging domain ports for removal and recording the
340 // associations of non-domain ports. After this, domain ports will be deleted
341 // and then class ports will be inserted. This loop therefore needs to track
342 // three indices:
343 // 1. i tracks the original port index.
344 // 2. iDel tracks the port index after deletion.
345 // 3. iIns tracks the port index after insertion.
346 OpBuilder::InsertPoint insertPoint;
347 if (body)
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]);
352
353 // Mark domain type ports for removal. Add information to `domainInfo`.
354 // Domain information is now stored in the type itself.
355 if (auto domainType = dyn_cast<DomainType>(port.type)) {
356 eraseVector.set(i);
357
358 // Instantiate a domain object with association information.
359 auto domain = domainType.getName();
360 auto [classIn, classOut] = domainToClasses.at(domain.getAttr());
361
362 indexToDomain[i] = port.direction == Direction::In
363 ? DomainInfo::input(iIns)
364 : DomainInfo::output(iIns);
365
366 if (body) {
367 // Insert objects in-order at the top of the module's body. These
368 // cannot be inserted at the end as they may have users.
369 ImplicitLocOpBuilder builder(port.loc, context);
370 builder.restoreInsertionPoint(insertPoint);
371
372 // Create the object, add information about it to domain info.
373 auto object = ObjectOp::create(
374 builder, classOut,
375 StringAttr::get(context, Twine(port.name) + "_object"));
376 instanceGraph.lookup(op)->addInstance(object,
377 instanceGraph.lookup(classOut));
378 indexToDomain[i].op = object;
379 indexToDomain[i].temp = stubOut(body->getArgument(i));
380
381 // Save the insertion point for the next go-around. This allows the
382 // objects to be inserted in port order as opposed to reverse port
383 // order.
384 insertPoint = builder.saveInsertionPoint();
385 }
386
387 // Add input and output property ports that encode the property inputs
388 // (which the user must provide for the domain) and the outputs that
389 // encode this information and the associations. Keep iIns up to date
390 // based on the number of ports added.
391 if (port.direction == Direction::In) {
392 newPorts.push_back({iDel, PortInfo(port.name, classIn.getInstanceType(),
393 Direction::In)});
394 portAnnotations.push_back(constants.getEmptyArrayAttr());
395 ++iIns;
396 }
397 newPorts.push_back(
398 {iDel, PortInfo(StringAttr::get(context, Twine(port.name) + "_out"),
399 classOut.getInstanceType(), Direction::Out)});
400 ++iIns;
401
402 // Update annotations.
403 portAnnotations.push_back(constants.getEmptyArrayAttr());
404
405 // Don't increment the iDel since we deleted one port.
406 continue;
407 }
408
409 // This is a non-domain port. It will NOT be deleted. Increment both
410 // indices.
411 ++iDel;
412 ++iIns;
413
414 // If this port has domain associations, then we need to add port annotation
415 // trackers. These will be hooked up to the Object's associations later.
416 // However, if there is no domain information, then annotations do not need
417 // to be modified. Early continue first, adding trackers otherwise. Only
418 // create one tracker for all associations.
419 ArrayAttr domainAttr = cast_or_null<ArrayAttr>(port.domains);
420 if (!domainAttr || domainAttr.empty()) {
421 portAnnotations.push_back(port.annotations.getArrayAttr());
422 continue;
423 }
424
425 // If the port is zero-width, then we don't need annotation trackers.
426 // LowerToHW removes zero-width ports and it will error if there are inner
427 // symbols on zero-width things it is trying to remove.
428 if (auto firrtlType = type_dyn_cast<FIRRTLType>(port.type))
429 if (hasZeroBitWidth(firrtlType)) {
430 portAnnotations.push_back(port.annotations.getArrayAttr());
431 continue;
432 }
433
434 SmallVector<Annotation> newAnnotations;
435 DistinctAttr id;
436 for (auto indexAttr : domainAttr.getAsRange<IntegerAttr>()) {
437 if (!id) {
438 id = DistinctAttr::create(UnitAttr::get(context));
439 newAnnotations.push_back(Annotation(DictionaryAttr::getWithSorted(
440 context, {{"class", StringAttr::get(context, "circt.tracker")},
441 {"id", id}})));
442 }
443 indexToDomain[indexAttr.getUInt()].associations.push_back({id, port.loc});
444 }
445 if (!newAnnotations.empty())
446 port.annotations.addAnnotations(newAnnotations);
447 portAnnotations.push_back(port.annotations.getArrayAttr());
448 }
449
450 // Erase domain ports and clear domain association information.
451 op.erasePorts(eraseVector);
452 op.setDomainInfoAttr(constants.getEmptyArrayAttr());
453
454 // Insert new property ports and hook these up to the object that was
455 // instantiated earlier.
456 op.insertPorts(newPorts);
457
458 if (body) {
459 for (auto const &[_, info] : indexToDomain) {
460 auto [object, inputPort, outputPort, temp, associations] = info;
461 OpBuilder builder(object);
462 builder.setInsertionPointAfter(object);
463 // Hook up domain ports. If this was an input domain port, then drive the
464 // object's "domain in" port with the new input class port. Splice
465 // references to the old port with the new port---users (e.g., domain
466 // defines) will be updated later. If an output, then splice to the
467 // object's "domain in" port. If this participates in domain defines,
468 // this will be hoked up later.
469 auto subDomainInfoIn =
470 ObjectSubfieldOp::create(builder, object.getLoc(), object, 0);
471 if (inputPort) {
472 PropAssignOp::create(builder, object.getLoc(), subDomainInfoIn,
473 body->getArgument(*inputPort));
474 splice(temp, body->getArgument(*inputPort));
475 } else {
476 splice(temp, subDomainInfoIn);
477 }
478
479 // Hook up the "association in" port.
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),
486 id));
487 }
488 auto list = ListCreateOp::create(
489 builder, object.getLoc(),
490 ListType::get(context, cast<PropertyType>(PathType::get(context))),
491 paths);
492 PropAssignOp::create(builder, object.getLoc(), subAssociations, list);
493
494 // Connect the object to the output port.
495 PropAssignOp::create(builder, object.getLoc(),
496 body->getArgument(outputPort), object);
497 }
498
499 // Remove all domain users. Delay deleting conversions until the module
500 // body is walked as it is possible that conversions have multiple users.
501 //
502 // This has the effect of removing all the conversions that we created.
503 // Note: this relies on the fact that we don't create conversions if there
504 // are no users. (See early exits in `stubOut` and `splice`.)
505 //
506 // Note: this cannot visit conversions directly as we don't have guarantees
507 // that there won't be other conversions flying around. E.g., LowerDPI
508 // leaves conversions that are cleaned up by LowerToHW. (This is likely
509 // wrong, but it doesn't cost us anything to do it this way.)
510 DenseSet<Operation *> conversionsToErase;
511 DenseSet<Operation *> operationsToErase;
512 auto walkResult = op.walk([&](Operation *walkOp) {
513 // This is an operation that we have previously determined can be deleted
514 // when examining an earlier operation. Delete it now as it is only safe
515 // to do so when visiting it.
516 if (operationsToErase.contains(walkOp)) {
517 walkOp->erase();
518 return WalkResult::advance();
519 }
520
521 // Handle UnsafeDomainCastOp.
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);
527 }
528
529 castOp.getResult().replaceAllUsesWith(castOp.getInput());
530 castOp.erase();
531 return WalkResult::advance();
532 }
533
534 // Replace an anonymous domain with an anoymous value op and a conversion.
535 // Only do this if the domain has users that won't be deleted. Otherwise,
536 // erase it.
537 if (auto anonDomain = dyn_cast<DomainCreateAnonOp>(walkOp)) {
538 // Short circuit erasure.
539 auto noUser = llvm::all_of(anonDomain->getUsers(), [&](auto *user) {
540 return operationsToErase.contains(user) ||
541 conversionsToErase.contains(user);
542 });
543 if (noUser) {
544 conversionsToErase.insert(anonDomain);
545 return WalkResult::advance();
546 }
547
548 // The op has at least one use. Replace it. This changes the type, so
549 // wrap this in a cast. The cast will be removed later.
550 OpBuilder builder(anonDomain);
551 auto classIn =
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())
557 .getResult()}));
558 anonDomain.erase();
559 return WalkResult::advance();
560 }
561
562 // Replace a named domain create with an object instantiation.
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);
567 });
568 if (noUser) {
569 conversionsToErase.insert(createDomain);
570 return WalkResult::advance();
571 }
572
573 OpBuilder builder(createDomain);
574 auto classIn =
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));
580
581 // Get field values from the DomainCreateOp
582 auto fieldValues = createDomain.getFieldValues();
583
584 // Each domain field is lowered to an input port (fieldIdx * 2) and an
585 // output port (fieldIdx * 2 + 1). Assign field values to input ports.
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,
591 fieldValue);
592 }
593
594 createDomain.replaceAllUsesWith(UnrealizedConversionCastOp::create(
595 builder, createDomain.getLoc(), {createDomain.getType()},
596 {object.getResult()}));
597 createDomain.erase();
598 return WalkResult::advance();
599 }
600
601 // Replace a domain-to-anyref cast with an object-to-anyref cast.
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) {
606 castOp.emitOpError(
607 "has an input that is not a conversion cast with one operand");
608 return WalkResult::interrupt();
609 }
610
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());
616 castOp.erase();
617 return WalkResult::advance();
618 }
619
620 // Replace a domain subfield with an object subfield.
621 if (auto subfieldOp = dyn_cast<DomainSubfieldOp>(walkOp)) {
622 // The input should be a conversion cast wrapping an object or a class
623 // value (e.g., a port).
624 auto *inputOp = subfieldOp.getInput().getDefiningOp();
625 if (!inputOp) {
626 subfieldOp.emitOpError(
627 "has an input that is not defined by an operation");
628 return WalkResult::interrupt();
629 }
630
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();
636 }
637
638 // Each domain field is lowered to an input port (fieldIdx * 2) and an
639 // output port (fieldIdx * 2 + 1). Get the output port for this field.
640 auto fieldIndex = subfieldOp.getFieldIndex();
641 auto outputPortIndex = fieldIndex * 2 + 1;
642
643 // Create an object subfield to extract the field value.
644 OpBuilder builder(subfieldOp);
645 auto objectSubfield = ObjectSubfieldOp::create(
646 builder, subfieldOp.getLoc(), conversionCast.getOperand(0),
647 outputPortIndex);
648
649 // Mark the conversion cast for deletion.
650 conversionsToErase.insert(conversionCast);
651
652 subfieldOp.replaceAllUsesWith(objectSubfield.getResult());
653 subfieldOp.erase();
654 return WalkResult::advance();
655 }
656
657 // Handle WireOp.
658 //
659 // For domain-typed wires, create a class-typed property wire and replace
660 // all uses of the original wire with a temporary conversion cast. The
661 // driver may occur later in the IR, so DomainDefineOps are handled when
662 // visited. Assigning the complete class reference preserves the source
663 // object's identity through chains of domain wires.
664 //
665 // For non-domain-typed wires, erase any domain associations.
666 if (auto wireOp = dyn_cast<WireOp>(walkOp)) {
667 // Handle domain-typed wires.
668 if (auto domainType =
669 type_dyn_cast<DomainType>(wireOp.getResult().getType())) {
670 if (wireOp.getResult().use_empty()) {
671 conversionsToErase.insert(wireOp);
672 return WalkResult::advance();
673 }
674
675 OpBuilder builder(wireOp);
676 auto classIn =
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());
683
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);
689 }
690
691 // Handle non-domain-typed wires.
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());
697 }
698
699 return WalkResult::advance();
700 }
701
702 // Handle DomainDefineOp. Skip all other operations.
703 auto defineOp = dyn_cast<DomainDefineOp>(walkOp);
704 if (!defineOp)
705 return WalkResult::advance();
706
707 // The destination of a DomainDefineOp is always a conversion cast. The
708 // source is _either_ (1) a conversion cast if the source is a module or
709 // instance port or (2) an anonymous domain op or a domain create op.
710 //
711 // Domain wires lower to property wires, so a single prop.assign connects
712 // the complete source reference to the destination and preserves its
713 // identity.
714 auto *src = defineOp.getSrc().getDefiningOp();
715 auto dest = dyn_cast<UnrealizedConversionCastOp>(
716 defineOp.getDest().getDefiningOp());
717 if (!src || !dest)
718 return WalkResult::advance();
719
720 conversionsToErase.insert(src);
721 conversionsToErase.insert(dest);
722
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();
733 }
734
735 defineOp->erase();
736 return WalkResult::advance();
737 });
738
739 if (walkResult.wasInterrupted())
740 return failure();
741
742 // Erase all the conversions.
743 for (auto *op : conversionsToErase)
744 op->erase();
745 }
746
747 // Set new port annotations.
748 op.setPortAnnotationsAttr(ArrayAttr::get(context, portAnnotations));
749
750 return success();
751}
752
753LogicalResult LowerModule::lowerInstances() {
754 // Early exit if there is no work to do.
755 if (eraseVector.none() && newPorts.empty())
756 return success();
757
758 // TODO: There is nothing to do unless this instance is a module or external
759 // module. This mirros code in the `lowerModule` member function. Figure out
760 // a way to clean this up, possible by making `LowerModule` a true noop if
761 // this is not one of these kinds of modulelikes.
762 if (!isa<FModuleOp, FExtModuleOp>(op))
763 return success();
764
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());
768 if (!instanceOp) {
769 use->getInstance().emitOpError()
770 << "has an unimplemented lowering in LowerDomains";
771 return failure();
772 }
773 LLVM_DEBUG(llvm::dbgs()
774 << " - " << instanceOp.getInstanceName() << "\n");
775
776 for (auto i : eraseVector.set_bits())
777 indexToDomain[i].temp = stubOut(instanceOp.getResult(i));
778
779 auto erased = instanceOp.cloneWithErasedPortsAndReplaceUses(eraseVector);
780 auto inserted = erased.cloneWithInsertedPortsAndReplaceUses(newPorts);
781 instanceGraph.replaceInstance(instanceOp, inserted);
782
783 for (auto &[i, info] : indexToDomain) {
784 Value splicedValue;
785 if (info.inputPort) {
786 // Handle input port. Just hook it up.
787 splicedValue = inserted->getResult(*info.inputPort);
788 } else {
789 // Handle output port. Splice in the output field that contains the
790 // domain object. This requires creating an object subfield.
791 OpBuilder builder(inserted);
792 builder.setInsertionPointAfter(inserted);
793 splicedValue =
794 ObjectSubfieldOp::create(builder, inserted.getLoc(),
795 inserted->getResult(info.outputPort), 1);
796 }
797
798 splice(info.temp, splicedValue);
799 }
800
801 instanceOp.erase();
802 erased.erase();
803 }
804
805 return success();
806}
807
808/// Class used to lwoer a circuit that contains domains. This hides any state
809/// that may need to be cleared across invocations of this pass to keep the
810/// actual pass code cleaner.
811class LowerCircuit {
812
813public:
814 LowerCircuit(CircuitOp circuit, InstanceGraph &instanceGraph,
815 llvm::Statistic &numDomains)
816 : circuit(circuit), instanceGraph(instanceGraph),
817 constants(circuit.getContext()), numDomains(numDomains) {}
818
819 /// Lower the circuit, removing all domains.
820 LogicalResult lowerCircuit();
821
822private:
823 /// Lower one domain.
824 LogicalResult lowerDomain(DomainOp);
825
826 /// The circuit this class is lowering.
827 CircuitOp circuit;
828
829 /// A reference to an instance graph. This will be mutated.
830 InstanceGraph &instanceGraph;
831
832 /// Internal store of lazily constructed constants.
833 Constants constants;
834
835 /// Mutable reference to the number of domains this class will lower.
836 llvm::Statistic &numDomains;
837
838 /// Store of the mapping from a domain name to the classes that it has been
839 /// lowered into.
840 DenseMap<Attribute, Classes> classes;
841};
842
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({{/*name=*/builder.getStringAttr(
850 Twine(field.getName().getValue()) + "_in"),
851 /*type=*/field.getType(), /*dir=*/Direction::In},
852 {/*name=*/builder.getStringAttr(
853 Twine(field.getName().getValue()) + "_out"),
854 /*type=*/field.getType(), /*dir=*/Direction::Out}});
855 auto classIn = ClassOp::create(builder, name, classInPorts);
856 auto classInType = classIn.getInstanceType();
857 auto pathListType =
858 ListType::get(context, cast<PropertyType>(PathType::get(context)));
859 auto classOut =
860 ClassOp::create(builder, StringAttr::get(context, Twine(name) + "_out"),
861 {{/*name=*/constants.getDomainInfoIn(),
862 /*type=*/classInType,
863 /*dir=*/Direction::In},
864 {/*name=*/constants.getDomainInfoOut(),
865 /*type=*/classInType,
866 /*dir=*/Direction::Out},
867 {/*name=*/constants.getAssociationsIn(),
868 /*type=*/pathListType,
869 /*dir=*/Direction::In},
870 {/*name=*/constants.getAssociationsOut(),
871 /*type=*/pathListType,
872 /*dir=*/Direction::Out}});
873
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));
879 };
880 connectPairWise(classIn);
881 connectPairWise(classOut);
882
883 classes.insert({name, {classIn, classOut}});
884 instanceGraph.addModule(classIn);
885 instanceGraph.addModule(classOut);
886 op.erase();
887 ++numDomains;
888 return success();
889}
890
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)))
896 return failure();
897 }
898
899 LLVM_DEBUG(llvm::dbgs() << "Processing modules:\n");
900 return instanceGraph.walkPostOrder([&](InstanceGraphNode &node) {
901 auto moduleOp = dyn_cast<FModuleLike>(node.getModule<Operation *>());
902 if (!moduleOp)
903 return success();
904 LLVM_DEBUG(llvm::dbgs()
905 << " - module: " << moduleOp.getModuleName() << "\n");
906 LowerModule lowerModule(moduleOp, classes, constants, instanceGraph);
907 if (failed(lowerModule.lowerModule()))
908 return failure();
909 LLVM_DEBUG(llvm::dbgs() << " instances:\n");
910 return lowerModule.lowerInstances();
911 });
912
913 return success();
914}
915} // namespace
916
919
920 LowerCircuit lowerCircuit(getOperation(), getAnalysis<InstanceGraph>(),
921 numDomains);
922 if (failed(lowerCircuit.lowerCircuit()))
923 return signalPassFailure();
924
925 markAnalysesPreserved<InstanceGraph>();
926}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:193
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
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.
void info(Twine message)
Definition LSPUtils.cpp:20
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.