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/ouput 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 if (auto castOp = dyn_cast<UnsafeDomainCastOp>(user)) {
215 castOp.getResult().replaceAllUsesWith(castOp.getInput());
216 castOp.erase();
217 continue;
218 }
219 return user->emitOpError()
220 << "has an unimplemented lowering in the LowerDomains pass.";
221 }
222 return success();
223 }
224
225 /// The module this class is lowering
226 FModuleLike op;
227
228 /// Ports that should be erased
229 BitVector eraseVector;
230
231 /// The ports that should be inserted, _after deletion_ by application of
232 /// `eraseVector`.
233 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
234
235 /// A mapping of old result to new result
236 SmallVector<std::pair<unsigned, unsigned>> resultMap;
237
238 /// Mapping of domain name to the lowered input and output class
239 const DenseMap<Attribute, Classes> &domainToClasses;
240
241 /// Lazy constant pool
242 Constants &constants;
243
244 /// Reference to an instance graph. This _will_ be mutated.
245 ///
246 /// TODO: The mutation of this is _not_ thread safe. This needs to be fixed
247 /// if this pass is parallelized.
248 InstanceGraph &instanceGraph;
249};
250
251LogicalResult LowerModule::lowerModule() {
252 // Much of the lowering is conditioned on whether or not this module has a
253 // body. If it has a body, then we need to instantiate an object for each
254 // domain port and hook up all the domain ports to annotations added to each
255 // associated port. Skip modules which don't have domains.
256 auto shouldProcess =
257 TypeSwitch<Operation *, std::optional<Block *>>(op)
258 .Case<FModuleOp>([](auto op) { return op.getBodyBlock(); })
259 .Case<FExtModuleOp>([](auto) { return nullptr; })
260 // Skip all other modules.
261 .Default([](auto) { return std::nullopt; });
262 if (!shouldProcess)
263 return success();
264 Block *body = *shouldProcess;
265
266 auto *context = op.getContext();
267
268 // Information about a domain. This is built up during the first iteration
269 // over the ports. This needs to preserve insertion order.
270 llvm::MapVector<unsigned, DomainInfo> indexToDomain;
271
272 // The new port annotations. These will be set after all deletions and
273 // insertions.
274 SmallVector<Attribute> portAnnotations;
275
276 // Iterate over the ports, staging domain ports for removal and recording the
277 // associations of non-domain ports. After this, domain ports will be deleted
278 // and then class ports will be inserted. This loop therefore needs to track
279 // three indices:
280 // 1. i tracks the original port index.
281 // 2. iDel tracks the port index after deletion.
282 // 3. iIns tracks the port index after insertion.
283 auto ports = op.getPorts();
284 for (unsigned i = 0, iDel = 0, iIns = 0, e = op.getNumPorts(); i != e; ++i) {
285 auto port = cast<PortInfo>(ports[i]);
286
287 // Mark domain type ports for removal. Add information to `domainInfo`.
288 if (auto domain = dyn_cast<FlatSymbolRefAttr>(port.domains)) {
289 eraseVector.set(i);
290
291 // Instantiate a domain object with association information.
292 auto [classIn, classOut] = domainToClasses.at(domain.getAttr());
293
294 if (body) {
295 auto builder = ImplicitLocOpBuilder::atBlockEnd(port.loc, body);
296 auto object = ObjectOp::create(
297 builder, classOut,
298 StringAttr::get(context, Twine(port.name) + "_object"));
299 instanceGraph.lookup(op)->addInstance(object,
300 instanceGraph.lookup(classOut));
301 if (port.direction == Direction::In)
302 indexToDomain[i] = {object, iIns, iIns + 1};
303 else
304 indexToDomain[i] = {object, std::nullopt, iIns};
305
306 // Erase users of the domain in the module body.
307 if (failed(eraseDomainUsers(body->getArgument(i))))
308 return failure();
309 }
310
311 // Add input and output property ports that encode the property inputs
312 // (which the user must provide for the domain) and the outputs that
313 // encode this information and the associations.
314 if (port.direction == Direction::In) {
315 newPorts.push_back({iDel, PortInfo(port.name, classIn.getInstanceType(),
316 Direction::In)});
317 portAnnotations.push_back(constants.getEmptyArrayAttr());
318 ++iIns;
319 }
320 newPorts.push_back(
321 {iDel, PortInfo(StringAttr::get(context, Twine(port.name) + "_out"),
322 classOut.getInstanceType(), Direction::Out)});
323 portAnnotations.push_back(constants.getEmptyArrayAttr());
324 ++iIns;
325
326 // Don't increment the iDel since we deleted one port.
327 continue;
328 }
329
330 // Record the mapping of the old port to the new port. This can be used
331 // later to update instances. This port will not be deleted, so
332 // post-increment both indices.
333 resultMap.emplace_back(iDel++, iIns++);
334
335 // If this port has domain associations, then we need to add port annotation
336 // trackers. These will be hooked up to the Object's associations later.
337 // However, if there is no domain information, then annotations do not need
338 // to be modified. Early continue first, adding trackers otherwise. Only
339 // create one tracker for all associations.
340 ArrayAttr domainAttr = cast<ArrayAttr>(port.domains);
341 if (domainAttr.empty()) {
342 portAnnotations.push_back(port.annotations.getArrayAttr());
343 continue;
344 }
345
346 SmallVector<Annotation> newAnnotations;
347 DistinctAttr id;
348 for (auto indexAttr : domainAttr.getAsRange<IntegerAttr>()) {
349 if (!id) {
350 id = DistinctAttr::create(UnitAttr::get(context));
351 newAnnotations.push_back(Annotation(DictionaryAttr::getWithSorted(
352 context, {{"class", StringAttr::get(context, "circt.tracker")},
353 {"id", id}})));
354 }
355 indexToDomain[indexAttr.getUInt()].associations.push_back({id, port.loc});
356 }
357 if (!newAnnotations.empty())
358 port.annotations.addAnnotations(newAnnotations);
359 portAnnotations.push_back(port.annotations.getArrayAttr());
360 }
361
362 // Erase domain ports and clear domain association information.
363 op.erasePorts(eraseVector);
364 op.setDomainInfoAttr(constants.getEmptyArrayAttr());
365
366 // Insert new property ports and hook these up to the object that was
367 // instantiated earlier.
368 op.insertPorts(newPorts);
369
370 if (body) {
371 for (auto const &[_, info] : indexToDomain) {
372 auto [object, inputPort, outputPort, associations] = info;
373 OpBuilder builder(object);
374 builder.setInsertionPointAfter(object);
375 // Assign input domain info if needed.
376 //
377 // TODO: Change this to hook up to its connection once domain connects are
378 // available.
379 if (inputPort) {
380 auto subDomainInfoIn =
381 ObjectSubfieldOp::create(builder, object.getLoc(), object, 0);
382 PropAssignOp::create(builder, object.getLoc(), subDomainInfoIn,
383 body->getArgument(*inputPort));
384 }
385 auto subAssociations =
386 ObjectSubfieldOp::create(builder, object.getLoc(), object, 2);
387 // Assign associations.
388 SmallVector<Value> paths;
389 for (auto [id, loc] : associations) {
390 paths.push_back(PathOp::create(
391 builder, loc, TargetKindAttr::get(context, TargetKind::Reference),
392 id));
393 }
394 auto list = ListCreateOp::create(
395 builder, object.getLoc(),
396 ListType::get(context, cast<PropertyType>(PathType::get(context))),
397 paths);
398 PropAssignOp::create(builder, object.getLoc(), subAssociations, list);
399 // Connect the object to the output port.
400 PropAssignOp::create(builder, object.getLoc(),
401 body->getArgument(outputPort), object);
402 }
403 }
404
405 // Set new port annotations.
406 op.setPortAnnotationsAttr(ArrayAttr::get(context, portAnnotations));
407
408 LLVM_DEBUG({
409 llvm::dbgs() << " portMap:\n";
410 for (auto [oldIndex, newIndex] : resultMap)
411 llvm::dbgs() << " - " << oldIndex << ": " << newIndex << "\n";
412 });
413
414 return success();
415}
416
417LogicalResult LowerModule::lowerInstances() {
418 // TODO: There is nothing to do unless this instance is a module or external
419 // module. This mirros code in the `lowerModule` member function. Figure out
420 // a way to clean this up, possible by making `LowerModule` a true noop if
421 // this is not one of these kinds of modulelikes.
422 if (!isa<FModuleOp, FExtModuleOp>(op))
423 return success();
424
425 auto *node = instanceGraph.lookup(cast<igraph::ModuleOpInterface>(*op));
426 for (auto *use : llvm::make_early_inc_range(node->uses())) {
427 auto instanceOp = dyn_cast<InstanceOp>(*use->getInstance());
428 if (!instanceOp) {
429 use->getInstance().emitOpError()
430 << "has an unimplemented lowering in LowerDomains";
431 return failure();
432 }
433 LLVM_DEBUG(llvm::dbgs()
434 << " - " << instanceOp.getInstanceName() << "\n");
435
436 for (auto bit : eraseVector.set_bits())
437 if (failed(eraseDomainUsers(instanceOp.getResult(bit))))
438 return failure();
439
440 ImplicitLocOpBuilder builder(instanceOp.getLoc(), instanceOp);
441 auto erased = instanceOp.erasePorts(builder, eraseVector);
442 auto inserted = erased.cloneAndInsertPorts(newPorts);
443 for (auto [oldIndex, newIndex] : resultMap) {
444 auto oldPort = erased.getResult(oldIndex);
445 auto newPort = inserted.getResult(newIndex);
446 oldPort.replaceAllUsesWith(newPort);
447 }
448 instanceGraph.replaceInstance(instanceOp, inserted);
449
450 instanceOp.erase();
451 erased.erase();
452 }
453
454 return success();
455}
456
457/// Class used to lwoer a circuit that contains domains. This hides any state
458/// that may need to be cleared across invocations of this pass to keep the
459/// actual pass code cleaner.
460class LowerCircuit {
461
462public:
463 LowerCircuit(CircuitOp circuit, InstanceGraph &instanceGraph,
464 llvm::Statistic &numDomains)
465 : circuit(circuit), instanceGraph(instanceGraph),
466 constants(circuit.getContext()), numDomains(numDomains) {}
467
468 /// Lower the circuit, removing all domains.
469 LogicalResult lowerCircuit();
470
471private:
472 /// Lower one domain.
473 LogicalResult lowerDomain(DomainOp);
474
475 /// The circuit this class is lowering.
476 CircuitOp circuit;
477
478 /// A reference to an instance graph. This will be mutated.
479 InstanceGraph &instanceGraph;
480
481 /// Internal store of lazily constructed constants.
482 Constants constants;
483
484 /// Mutable reference to the number of domains this class will lower.
485 llvm::Statistic &numDomains;
486
487 /// Store of the mapping from a domain name to the classes that it has been
488 /// lowered into.
489 DenseMap<Attribute, Classes> classes;
490};
491
492LogicalResult LowerCircuit::lowerDomain(DomainOp op) {
493 ImplicitLocOpBuilder builder(op.getLoc(), op);
494 auto *context = op.getContext();
495 auto name = op.getNameAttr();
496 // TODO: Update this once DomainOps have properties.
497 auto classIn = ClassOp::create(builder, name, {});
498 auto classInType = classIn.getInstanceType();
499 auto pathListType =
500 ListType::get(context, cast<PropertyType>(PathType::get(context)));
501 auto classOut =
502 ClassOp::create(builder, StringAttr::get(context, Twine(name) + "_out"),
503 {{/*name=*/constants.getDomainInfoIn(),
504 /*type=*/classInType,
505 /*dir=*/Direction::In},
506 {/*name=*/constants.getDomainInfoOut(),
507 /*type=*/classInType,
508 /*dir=*/Direction::Out},
509 {/*name=*/constants.getAssociationsIn(),
510 /*type=*/pathListType,
511 /*dir=*/Direction::In},
512 {/*name=*/constants.getAssociationsOut(),
513 /*type=*/pathListType,
514 /*dir=*/Direction::Out}});
515 builder.setInsertionPointToStart(classOut.getBodyBlock());
516 PropAssignOp::create(builder, classOut.getArgument(1),
517 classOut.getArgument(0));
518 PropAssignOp::create(builder, classOut.getArgument(3),
519 classOut.getArgument(2));
520 classes.insert({name, {classIn, classOut}});
521 instanceGraph.addModule(classIn);
522 instanceGraph.addModule(classOut);
523 op.erase();
524 ++numDomains;
525 return success();
526}
527
528LogicalResult LowerCircuit::lowerCircuit() {
529 LLVM_DEBUG(llvm::dbgs() << "Processing domains:\n");
530 for (auto domain : llvm::make_early_inc_range(circuit.getOps<DomainOp>())) {
531 LLVM_DEBUG(llvm::dbgs() << " - " << domain.getName() << "\n");
532 if (failed(lowerDomain(domain)))
533 return failure();
534 }
535
536 LLVM_DEBUG(llvm::dbgs() << "Processing modules:\n");
537 return instanceGraph.walkPostOrder([&](InstanceGraphNode &node) {
538 auto moduleOp = dyn_cast<FModuleLike>(node.getModule<Operation *>());
539 if (!moduleOp)
540 return success();
541 LLVM_DEBUG(llvm::dbgs() << " - module: " << moduleOp.getName() << "\n");
542 LowerModule lowerModule(moduleOp, classes, constants, instanceGraph);
543 if (failed(lowerModule.lowerModule()))
544 return failure();
545 LLVM_DEBUG(llvm::dbgs() << " instances:\n");
546 return lowerModule.lowerInstances();
547 });
548
549 return success();
550}
551} // namespace
552
555
556 LowerCircuit lowerCircuit(getOperation(), getAnalysis<InstanceGraph>(),
557 numDomains);
558 if (failed(lowerCircuit.lowerCircuit()))
559 return signalPassFailure();
560
561 markAllAnalysesPreserved();
562}
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:216
#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.