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;
62
63class LowerDomainsPass : public impl::LowerDomainsBase<LowerDomainsPass> {
64 using Base::Base;
65 void runOnOperation() override;
66};
67
68#define DEBUG_TYPE \
69 impl::LowerDomainsBase<LowerDomainsPass>::getArgumentName().data()
70
71namespace {
72/// Minimally track information about an association of a port to a domain.
73struct AssociationInfo {
74 /// The DistinctAttr (annotation) that is used to identify the port.
75 DistinctAttr distinctAttr;
76
77 /// The port's location. This is used to generate exact information about
78 /// certain property ops created later.
79 Location loc;
80};
81
82/// Track information about the lowering of a domain port.
83struct DomainInfo {
84 /// An instance of an object which will be used to track an instance of the
85 /// domain-lowered class (which is the identity of the domain) and all its
86 /// associations.
87 ObjectOp op;
88
89 /// The index of the optional input port that will be hooked up to a field of
90 /// the ObjectOp. This port is an instance of the domain-lowered class. If
91 /// this is created due to an output domain port, then this is nullopt.
92 std::optional<unsigned> inputPort;
93
94 /// The index of the output port that the ObjectOp will be connected to. This
95 /// port communicates back to the user information about the associations.
96 unsigned outputPort;
97
98 /// A vector of minimal association info that will be hooked up to the
99 /// associations of this ObjectOp.
100 SmallVector<AssociationInfo> associations{};
101};
102
103/// Struct of the two classes created from a domain, an input class (which is
104/// one-to-one with the domain) and an output class (which tracks the input
105/// class and any associations).
106struct Classes {
107 /// The domain-lowered class.
108 ClassOp input;
109
110 /// A class tracking an instance of the input class and a list of
111 /// associations.
112 ClassOp output;
113};
114
115/// Thread safe, lazy pool of constant attributes
116class Constants {
117
118 /// Lazily constructed empty array attribute.
119 struct EmptyArray {
120 llvm::once_flag flag;
121 ArrayAttr attr;
122 };
123
124 /// Lazy constructed attributes necessary for building an output class.
125 struct ClassOut {
126 llvm::once_flag flag;
127 StringAttr domainInfoIn;
128 StringAttr domainInfoOut;
129 StringAttr associationsIn;
130 StringAttr associationsOut;
131 };
132
133public:
134 Constants(MLIRContext *context) : context(context) {}
135
136 /// Return an empty ArrayAttr.
137 ArrayAttr getEmptyArrayAttr() {
138 llvm::call_once(emptyArray.flag,
139 [&] { emptyArray.attr = ArrayAttr::get(context, {}); });
140 return emptyArray.attr;
141 }
142
143private:
144 /// Construct all the field info attributes.
145 void initClassOut() {
146 llvm::call_once(classOut.flag, [&] {
147 classOut.domainInfoIn = StringAttr::get(context, "domainInfo_in");
148 classOut.domainInfoOut = StringAttr::get(context, "domainInfo_out");
149 classOut.associationsIn = StringAttr::get(context, "associations_in");
150 classOut.associationsOut = StringAttr::get(context, "associations_out");
151 });
152 }
153
154public:
155 /// Return a "domainInfo_in" attr.
156 StringAttr getDomainInfoIn() {
157 initClassOut();
158 return classOut.domainInfoIn;
159 }
160
161 /// Return a "domainInfo_out" attr.
162 StringAttr getDomainInfoOut() {
163 initClassOut();
164 return classOut.domainInfoOut;
165 }
166
167 /// Return an "associations_in" attr.
168 StringAttr getAssociationsIn() {
169 initClassOut();
170 return classOut.associationsIn;
171 }
172
173 /// Return an "associations_out" attr.
174 StringAttr getAssociationsOut() {
175 initClassOut();
176 return classOut.associationsOut;
177 }
178
179private:
180 /// An MLIR context necessary for creating new attributes.
181 MLIRContext *context;
182
183 /// Lazily constructed attributes
184 EmptyArray emptyArray;
185 ClassOut classOut;
186};
187
188/// Class that is used to lower a module that may contain domain ports. This is
189/// intended to be used by calling `lowerModule()` to lower the module. This
190/// builds up state in the class which is then used when calling
191/// `lowerInstances()` to update all instantiations of this module. If
192/// multi-threading, care needs to be taken to only call `lowerInstances()`
193/// when instantiating modules are not _also_ being updated.
194class LowerModule {
195
196public:
197 LowerModule(FModuleLike op, const DenseMap<Attribute, Classes> &classes,
198 Constants &constants, InstanceGraph &instanceGraph)
199 : op(op), eraseVector(op.getNumPorts()), domainToClasses(classes),
200 constants(constants), instanceGraph(instanceGraph) {}
201
202 /// Lower the associated module. Replace domain ports with input/output class
203 /// ports.
204 LogicalResult lowerModule();
205
206 /// Lower all instances of the associated module. This relies on state built
207 /// up during `lowerModule` and must be run _afterwards_.
208 LogicalResult lowerInstances();
209
210private:
211 /// Erase all users of domain type ports.
212 LogicalResult eraseDomainUsers(Value value) {
213 for (auto *user : llvm::make_early_inc_range(value.getUsers())) {
214 // Casts disappear by forwarding their source to destination.
215 if (auto castOp = dyn_cast<UnsafeDomainCastOp>(user)) {
216 castOp.getResult().replaceAllUsesWith(castOp.getInput());
217 castOp.erase();
218 continue;
219 }
220 // All other known users are deleted.
221 if (isa<DomainDefineOp>(user)) {
222 user->erase();
223 continue;
224 }
225 return user->emitOpError()
226 << "has an unimplemented lowering in the LowerDomains pass.";
227 }
228 return success();
229 }
230
231 /// The module this class is lowering
232 FModuleLike op;
233
234 /// Ports that should be erased
235 BitVector eraseVector;
236
237 /// The ports that should be inserted, _after deletion_ by application of
238 /// `eraseVector`.
239 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
240
241 /// A mapping of old result to new result
242 SmallVector<std::pair<unsigned, unsigned>> resultMap;
243
244 /// Mapping of domain name to the lowered input and output class
245 const DenseMap<Attribute, Classes> &domainToClasses;
246
247 /// Lazy constant pool
248 Constants &constants;
249
250 /// Reference to an instance graph. This _will_ be mutated.
251 ///
252 /// TODO: The mutation of this is _not_ thread safe. This needs to be fixed
253 /// if this pass is parallelized.
254 InstanceGraph &instanceGraph;
255};
256
257LogicalResult LowerModule::lowerModule() {
258 // Early exit if there is no domain information. This _shouldn't_ be the case
259 // when this pass runs, but it avoids
260 if (op.getDomainInfo().empty())
261 return success();
262
263 // Much of the lowering is conditioned on whether or not this module has a
264 // body. If it has a body, then we need to instantiate an object for each
265 // domain port and hook up all the domain ports to annotations added to each
266 // associated port. Skip modules which don't have domains.
267 auto shouldProcess =
268 TypeSwitch<Operation *, std::optional<Block *>>(op)
269 .Case<FModuleOp>([](auto op) { return op.getBodyBlock(); })
270 .Case<FExtModuleOp>([](auto) { return nullptr; })
271 // Skip all other modules.
272 .Default([](auto) { return std::nullopt; });
273 if (!shouldProcess)
274 return success();
275 Block *body = *shouldProcess;
276
277 auto *context = op.getContext();
278
279 // Information about a domain. This is built up during the first iteration
280 // over the ports. This needs to preserve insertion order.
281 llvm::MapVector<unsigned, DomainInfo> indexToDomain;
282
283 // The new port annotations. These will be set after all deletions and
284 // insertions.
285 SmallVector<Attribute> portAnnotations;
286
287 // Iterate over the ports, staging domain ports for removal and recording the
288 // associations of non-domain ports. After this, domain ports will be deleted
289 // and then class ports will be inserted. This loop therefore needs to track
290 // three indices:
291 // 1. i tracks the original port index.
292 // 2. iDel tracks the port index after deletion.
293 // 3. iIns tracks the port index after insertion.
294 auto ports = op.getPorts();
295 for (unsigned i = 0, iDel = 0, iIns = 0, e = op.getNumPorts(); i != e; ++i) {
296 auto port = cast<PortInfo>(ports[i]);
297
298 // Mark domain type ports for removal. Add information to `domainInfo`.
299 if (auto domain = dyn_cast_or_null<FlatSymbolRefAttr>(port.domains)) {
300 eraseVector.set(i);
301
302 // Instantiate a domain object with association information.
303 auto [classIn, classOut] = domainToClasses.at(domain.getAttr());
304
305 if (body) {
306 auto builder = ImplicitLocOpBuilder::atBlockEnd(port.loc, body);
307 auto object = ObjectOp::create(
308 builder, classOut,
309 StringAttr::get(context, Twine(port.name) + "_object"));
310 instanceGraph.lookup(op)->addInstance(object,
311 instanceGraph.lookup(classOut));
312 if (port.direction == Direction::In)
313 indexToDomain[i] = {object, iIns, iIns + 1};
314 else
315 indexToDomain[i] = {object, std::nullopt, iIns};
316
317 // Erase users of the domain in the module body.
318 if (failed(eraseDomainUsers(body->getArgument(i))))
319 return failure();
320 }
321
322 // Add input and output property ports that encode the property inputs
323 // (which the user must provide for the domain) and the outputs that
324 // encode this information and the associations.
325 if (port.direction == Direction::In) {
326 newPorts.push_back({iDel, PortInfo(port.name, classIn.getInstanceType(),
327 Direction::In)});
328 portAnnotations.push_back(constants.getEmptyArrayAttr());
329 ++iIns;
330 }
331 newPorts.push_back(
332 {iDel, PortInfo(StringAttr::get(context, Twine(port.name) + "_out"),
333 classOut.getInstanceType(), Direction::Out)});
334 portAnnotations.push_back(constants.getEmptyArrayAttr());
335 ++iIns;
336
337 // Don't increment the iDel since we deleted one port.
338 continue;
339 }
340
341 // Record the mapping of the old port to the new port. This can be used
342 // later to update instances. This port will not be deleted, so
343 // post-increment both indices.
344 resultMap.emplace_back(iDel++, iIns++);
345
346 // If this port has domain associations, then we need to add port annotation
347 // trackers. These will be hooked up to the Object's associations later.
348 // However, if there is no domain information, then annotations do not need
349 // to be modified. Early continue first, adding trackers otherwise. Only
350 // create one tracker for all associations.
351 ArrayAttr domainAttr = cast_or_null<ArrayAttr>(port.domains);
352 if (!domainAttr || domainAttr.empty()) {
353 portAnnotations.push_back(port.annotations.getArrayAttr());
354 continue;
355 }
356
357 SmallVector<Annotation> newAnnotations;
358 DistinctAttr id;
359 for (auto indexAttr : domainAttr.getAsRange<IntegerAttr>()) {
360 if (!id) {
361 id = DistinctAttr::create(UnitAttr::get(context));
362 newAnnotations.push_back(Annotation(DictionaryAttr::getWithSorted(
363 context, {{"class", StringAttr::get(context, "circt.tracker")},
364 {"id", id}})));
365 }
366 indexToDomain[indexAttr.getUInt()].associations.push_back({id, port.loc});
367 }
368 if (!newAnnotations.empty())
369 port.annotations.addAnnotations(newAnnotations);
370 portAnnotations.push_back(port.annotations.getArrayAttr());
371 }
372
373 // Erase domain ports and clear domain association information.
374 op.erasePorts(eraseVector);
375 op.setDomainInfoAttr(constants.getEmptyArrayAttr());
376
377 // Insert new property ports and hook these up to the object that was
378 // instantiated earlier.
379 op.insertPorts(newPorts);
380
381 if (body) {
382 for (auto const &[_, info] : indexToDomain) {
383 auto [object, inputPort, outputPort, associations] = info;
384 OpBuilder builder(object);
385 builder.setInsertionPointAfter(object);
386 // Assign input domain info if needed.
387 //
388 // TODO: Change this to hook up to its connection once domain connects are
389 // available.
390 if (inputPort) {
391 auto subDomainInfoIn =
392 ObjectSubfieldOp::create(builder, object.getLoc(), object, 0);
393 PropAssignOp::create(builder, object.getLoc(), subDomainInfoIn,
394 body->getArgument(*inputPort));
395 }
396 auto subAssociations =
397 ObjectSubfieldOp::create(builder, object.getLoc(), object, 2);
398 // Assign associations.
399 SmallVector<Value> paths;
400 for (auto [id, loc] : associations) {
401 paths.push_back(PathOp::create(
402 builder, loc, TargetKindAttr::get(context, TargetKind::Reference),
403 id));
404 }
405 auto list = ListCreateOp::create(
406 builder, object.getLoc(),
407 ListType::get(context, cast<PropertyType>(PathType::get(context))),
408 paths);
409 PropAssignOp::create(builder, object.getLoc(), subAssociations, list);
410 // Connect the object to the output port.
411 PropAssignOp::create(builder, object.getLoc(),
412 body->getArgument(outputPort), object);
413 }
414 }
415
416 // Set new port annotations.
417 op.setPortAnnotationsAttr(ArrayAttr::get(context, portAnnotations));
418
419 LLVM_DEBUG({
420 llvm::dbgs() << " portMap:\n";
421 for (auto [oldIndex, newIndex] : resultMap)
422 llvm::dbgs() << " - " << oldIndex << ": " << newIndex << "\n";
423 });
424
425 return success();
426}
427
428LogicalResult LowerModule::lowerInstances() {
429 // Early exit if there is no work to do.
430 if (eraseVector.none() && newPorts.empty())
431 return success();
432
433 // TODO: There is nothing to do unless this instance is a module or external
434 // module. This mirros code in the `lowerModule` member function. Figure out
435 // a way to clean this up, possible by making `LowerModule` a true noop if
436 // this is not one of these kinds of modulelikes.
437 if (!isa<FModuleOp, FExtModuleOp>(op))
438 return success();
439
440 auto *node = instanceGraph.lookup(cast<igraph::ModuleOpInterface>(*op));
441 for (auto *use : llvm::make_early_inc_range(node->uses())) {
442 auto instanceOp = dyn_cast<InstanceOp>(*use->getInstance());
443 if (!instanceOp) {
444 use->getInstance().emitOpError()
445 << "has an unimplemented lowering in LowerDomains";
446 return failure();
447 }
448 LLVM_DEBUG(llvm::dbgs()
449 << " - " << instanceOp.getInstanceName() << "\n");
450
451 for (auto bit : eraseVector.set_bits())
452 if (failed(eraseDomainUsers(instanceOp.getResult(bit))))
453 return failure();
454
455 auto erased = instanceOp.cloneWithErasedPortsAndReplaceUses(eraseVector);
456 auto inserted = erased.cloneWithInsertedPortsAndReplaceUses(newPorts);
457 instanceGraph.replaceInstance(instanceOp, inserted);
458
459 instanceOp.erase();
460 erased.erase();
461 }
462
463 return success();
464}
465
466/// Class used to lwoer a circuit that contains domains. This hides any state
467/// that may need to be cleared across invocations of this pass to keep the
468/// actual pass code cleaner.
469class LowerCircuit {
470
471public:
472 LowerCircuit(CircuitOp circuit, InstanceGraph &instanceGraph,
473 llvm::Statistic &numDomains)
474 : circuit(circuit), instanceGraph(instanceGraph),
475 constants(circuit.getContext()), numDomains(numDomains) {}
476
477 /// Lower the circuit, removing all domains.
478 LogicalResult lowerCircuit();
479
480private:
481 /// Lower one domain.
482 LogicalResult lowerDomain(DomainOp);
483
484 /// The circuit this class is lowering.
485 CircuitOp circuit;
486
487 /// A reference to an instance graph. This will be mutated.
488 InstanceGraph &instanceGraph;
489
490 /// Internal store of lazily constructed constants.
491 Constants constants;
492
493 /// Mutable reference to the number of domains this class will lower.
494 llvm::Statistic &numDomains;
495
496 /// Store of the mapping from a domain name to the classes that it has been
497 /// lowered into.
498 DenseMap<Attribute, Classes> classes;
499};
500
501LogicalResult LowerCircuit::lowerDomain(DomainOp op) {
502 ImplicitLocOpBuilder builder(op.getLoc(), op);
503 auto *context = op.getContext();
504 auto name = op.getNameAttr();
505 SmallVector<PortInfo> classInPorts;
506 for (auto field : op.getFields().getAsRange<DomainFieldAttr>())
507 classInPorts.append({{/*name=*/builder.getStringAttr(
508 Twine(field.getName().getValue()) + "_in"),
509 /*type=*/field.getType(), /*dir=*/Direction::In},
510 {/*name=*/builder.getStringAttr(
511 Twine(field.getName().getValue()) + "_out"),
512 /*type=*/field.getType(), /*dir=*/Direction::Out}});
513 auto classIn = ClassOp::create(builder, name, classInPorts);
514 auto classInType = classIn.getInstanceType();
515 auto pathListType =
516 ListType::get(context, cast<PropertyType>(PathType::get(context)));
517 auto classOut =
518 ClassOp::create(builder, StringAttr::get(context, Twine(name) + "_out"),
519 {{/*name=*/constants.getDomainInfoIn(),
520 /*type=*/classInType,
521 /*dir=*/Direction::In},
522 {/*name=*/constants.getDomainInfoOut(),
523 /*type=*/classInType,
524 /*dir=*/Direction::Out},
525 {/*name=*/constants.getAssociationsIn(),
526 /*type=*/pathListType,
527 /*dir=*/Direction::In},
528 {/*name=*/constants.getAssociationsOut(),
529 /*type=*/pathListType,
530 /*dir=*/Direction::Out}});
531
532 auto connectPairWise = [&builder](ClassOp &classOp) {
533 builder.setInsertionPointToStart(classOp.getBodyBlock());
534 for (size_t i = 0, e = classOp.getNumPorts(); i != e; i += 2)
535 PropAssignOp::create(builder, classOp.getArgument(i + 1),
536 classOp.getArgument(i));
537 };
538 connectPairWise(classIn);
539 connectPairWise(classOut);
540
541 classes.insert({name, {classIn, classOut}});
542 instanceGraph.addModule(classIn);
543 instanceGraph.addModule(classOut);
544 op.erase();
545 ++numDomains;
546 return success();
547}
548
549LogicalResult LowerCircuit::lowerCircuit() {
550 LLVM_DEBUG(llvm::dbgs() << "Processing domains:\n");
551 for (auto domain : llvm::make_early_inc_range(circuit.getOps<DomainOp>())) {
552 LLVM_DEBUG(llvm::dbgs() << " - " << domain.getName() << "\n");
553 if (failed(lowerDomain(domain)))
554 return failure();
555 }
556
557 LLVM_DEBUG(llvm::dbgs() << "Processing modules:\n");
558 return instanceGraph.walkPostOrder([&](InstanceGraphNode &node) {
559 auto moduleOp = dyn_cast<FModuleLike>(node.getModule<Operation *>());
560 if (!moduleOp)
561 return success();
562 LLVM_DEBUG(llvm::dbgs() << " - module: " << moduleOp.getName() << "\n");
563 LowerModule lowerModule(moduleOp, classes, constants, instanceGraph);
564 if (failed(lowerModule.lowerModule()))
565 return failure();
566 LLVM_DEBUG(llvm::dbgs() << " instances:\n");
567 return lowerModule.lowerInstances();
568 });
569
570 return success();
571}
572} // namespace
573
576
577 LowerCircuit lowerCircuit(getOperation(), getAnalysis<InstanceGraph>(),
578 numDomains);
579 if (failed(lowerCircuit.lowerCircuit()))
580 return signalPassFailure();
581
582 markAllAnalysesPreserved();
583}
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)
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.