CIRCT 24.0.0git
Loading...
Searching...
No Matches
InferDomains.cpp
Go to the documentation of this file.
1//===- InferDomains.cpp - Infer and Check FIRRTL Domains ------------------===//
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// InferDomains implements FIRRTL domain inference and checking. This pass is
10// a bottom-up transform acting on modules. For each moduleOp, we ensure there
11// are no domain crossings, and we make explicit the domain associations of
12// ports.
13//
14// This pass does not require that ExpandWhens has run, but it should have run.
15// If ExpandWhens has not been run, then duplicate connections will influence
16// domain inference and this can result in errors.
17//
18//===----------------------------------------------------------------------===//
19
24#include "circt/Support/Debug.h"
26#include "mlir/IR/AsmState.h"
27#include "mlir/Interfaces/SideEffectInterfaces.h"
28#include "llvm/ADT/DenseMap.h"
29#include "llvm/ADT/DenseSet.h"
30#include "llvm/ADT/STLExtras.h"
31#include "llvm/ADT/SmallVector.h"
32#include "llvm/ADT/TinyPtrVector.h"
33
34#define DEBUG_TYPE "firrtl-infer-domains"
35
36namespace circt {
37namespace firrtl {
38#define GEN_PASS_DEF_INFERDOMAINS
39#include "circt/Dialect/FIRRTL/Passes.h.inc"
40} // namespace firrtl
41} // namespace circt
42
43using namespace circt;
44using namespace firrtl;
45
48using mlir::AsmState;
49using mlir::InFlightDiagnostic;
50
51namespace {
52struct VariableTerm;
53} // namespace
54
55//====--------------------------------------------------------------------------
56// Helpers.
57//====--------------------------------------------------------------------------
58
59using DomainValue = mlir::TypedValue<DomainType>;
60
61using PortInsertions = SmallVector<std::pair<unsigned, PortInfo>>;
62
63/// From a domain info attribute, get the row of associated domains for a
64/// hardware value at index i.
65static auto getPortDomainAssociation(ArrayAttr info, size_t i) {
66 if (info.empty())
67 return info.getAsRange<IntegerAttr>();
68 return cast<ArrayAttr>(info[i]).getAsRange<IntegerAttr>();
69}
70
71/// Return true if the value is a port on the module.
72static bool isPort(BlockArgument arg) {
73 return isa<FModuleOp>(arg.getOwner()->getParentOp());
74}
75
76/// Return true if the value is a port on the module.
77static bool isPort(Value value) {
78 auto arg = dyn_cast<BlockArgument>(value);
79 if (!arg)
80 return false;
81 return isPort(arg);
82}
83
84/// Returns true if the value is driven by a connect op.
85static bool isDriven(DomainValue port) {
86 for (auto *user : port.getUsers())
87 if (auto connect = dyn_cast<FConnectLike>(user))
88 if (connect.getDest() == port)
89 return true;
90 return false;
91}
92
93/// True if a value of the given type could be associated with a domain.
94static bool isHardware(Type type) {
95 return type_isa<FIRRTLBaseType, RefType>(type);
96}
97
98/// True if the given value could be association with a domain.
99static bool isHardware(Value value) { return isHardware(value.getType()); }
100
101//====--------------------------------------------------------------------------
102// Global State.
103//====--------------------------------------------------------------------------
104
105/// Each domain type declared in the circuit is assigned a type-id, based on the
106/// order of declaration. Domain associations for hardware values are
107/// represented as a list, or row, of domains. The domains in a row are ordered
108/// according to their type's id.
109namespace {
110struct DomainTypeID {
111 size_t index;
112};
113} // namespace
114
115/// Information about the changes made to the interface of a moduleOp, which can
116/// be replayed onto an instance.
117namespace {
118struct ModuleUpdateInfo {
119 /// The updated domain information for a moduleOp.
120 ArrayAttr portDomainInfo;
121 /// The domain ports which have been inserted into a moduleOp.
122 PortInsertions portInsertions;
123};
124} // namespace
125
126namespace {
127struct CircuitState {
128 CircuitState(CircuitOp circuit, InstanceGraph &instanceGraph,
129 InnerRefNamespace &innerRefNamespace, InferDomainsMode mode)
130 : circuit(circuit), instanceGraph(instanceGraph),
131 innerRefNamespace(innerRefNamespace), mode(mode) {
132 processCircuit(circuit);
133 }
134
135 LogicalResult run();
136
137 ArrayRef<DomainOp> getDomains() const { return domainTable; }
138 size_t getNumDomains() const { return domainTable.size(); }
139 DomainOp getDomain(DomainTypeID id) const { return domainTable[id.index]; }
140 DomainTypeID getDomainTypeID(Type type) { return typeIDTable[type]; }
141
142 void dirty() { asmState = nullptr; }
143 AsmState &getAsmState() {
144 if (!asmState) {
145 asmState = std::make_unique<AsmState>(
146 circuit, mlir::OpPrintingFlags().assumeVerified());
147 }
148 return *asmState;
149 }
150
151 size_t getVariableID(VariableTerm *term) {
152 return variableIDTable.insert({term, variableIDTable.size() + 1})
153 .first->second;
154 }
155
156 DenseMap<StringAttr, ModuleUpdateInfo> &getModuleUpdateTable() {
157 return moduleUpdateTable;
158 }
159
160 InnerRefNamespace &getInnerRefNamespace() { return innerRefNamespace; }
161
162 DenseSet<Value> inserted;
163
164private:
165 LogicalResult runOnModule(Operation *moduleOp);
166
167 void processDomain(DomainOp op) {
168 auto index = domainTable.size();
169 auto domainType = DomainType::getFromDomainOp(op);
170 domainTable.push_back(op);
171 typeIDTable.insert({domainType, {index}});
172 }
173
174 void processCircuit(CircuitOp circuit) {
175 for (auto decl : circuit.getOps<DomainOp>())
176 processDomain(decl);
177 }
178
179 CircuitOp circuit;
180 InstanceGraph &instanceGraph;
181 InnerRefNamespace &innerRefNamespace;
182 InferDomainsMode mode;
183 SmallVector<DomainOp> domainTable;
184 DenseMap<Type, DomainTypeID> typeIDTable;
185 DenseMap<VariableTerm *, size_t> variableIDTable;
186 std::unique_ptr<AsmState> asmState;
187 DenseMap<StringAttr, ModuleUpdateInfo> moduleUpdateTable;
188};
189} // namespace
190
191//====--------------------------------------------------------------------------
192// Terms: Syntax for unifying domain and domain-rows.
193//====--------------------------------------------------------------------------
194
195/// The different sorts of terms in the unification engine.
196namespace {
197enum class TermKind {
198 Variable,
199 Value,
200 Row,
201};
202} // namespace
203
204/// A term in the unification engine.
205namespace {
206struct Term {
207 constexpr Term(TermKind kind) : kind(kind) {}
208 TermKind kind;
209};
210} // namespace
211
212/// Helper to define a term kind.
213namespace {
214template <TermKind K>
215struct TermBase : Term {
216 static bool classof(const Term *term) { return term->kind == K; }
217 TermBase() : Term(K) {}
218};
219} // namespace
220
221/// An unknown value.
222namespace {
223struct VariableTerm : public TermBase<TermKind::Variable> {
224 VariableTerm() : leader(nullptr) {}
225 VariableTerm(Term *leader) : leader(leader) {}
226 Term *leader;
227};
228} // namespace
229
230/// A concrete value defined in the IR.
231namespace {
232struct ValueTerm : public TermBase<TermKind::Value> {
233 ValueTerm(DomainValue value) : value(value) {}
234 DomainValue value;
235};
236} // namespace
237
238/// A row of domains.
239namespace {
240struct RowTerm : public TermBase<TermKind::Row> {
241 RowTerm(ArrayRef<Term *> elements) : elements(elements) {}
242 ArrayRef<Term *> elements;
243};
244} // namespace
245
246//====--------------------------------------------------------------------------
247// Module processing: solve for the domain associations of hardware.
248//====--------------------------------------------------------------------------
249
250/// A map from unsolved variables to a port index, where that port has not yet
251/// been created. Eventually we will have an input domain at the port index,
252/// which will be the solution to the recorded variable.
253using PendingSolutions = DenseMap<VariableTerm *, unsigned>;
254
255/// A map from local domains to an aliasing port index, where that port has not
256/// yet been created. Eventually we will be exporting the domain value at the
257/// port index.
259
260namespace {
261struct PendingUpdates {
262 PortInsertions insertions;
263 PendingSolutions solutions;
264 PendingExports exports;
265};
266} // namespace
267
268/// A map from domain IR values defined internal to the moduleOp, to ports that
269/// alias that domain. These ports make the domain useable as associations of
270/// ports, and we say these are exporting ports.
271using ExportTable = DenseMap<DomainValue, TinyPtrVector<DomainValue>>;
272
273namespace {
274class ModuleState {
275public:
276 explicit ModuleState(CircuitState &globals) : globals(globals) {}
277
278 ArrayRef<DomainOp> getDomains() { return globals.getDomains(); }
279 size_t getNumDomains() { return globals.getNumDomains(); }
280 DomainOp getDomain(DomainTypeID id) { return globals.getDomain(id); }
281 DomainTypeID getDomainTypeID(Type type) {
282 return globals.getDomainTypeID(type);
283 }
284 DomainTypeID getDomainTypeID(FModuleLike module, size_t i) {
285 return globals.getDomainTypeID(module.getPortType(i));
286 }
287 DomainTypeID getDomainTypeID(FInstanceLike op, size_t i) const {
288 return globals.getDomainTypeID(op->getResult(i).getType());
289 }
290 DomainTypeID getDomainTypeID(DomainValue value) const {
291 return globals.getDomainTypeID(value.getType());
292 }
293 auto &getModuleUpdateTable() { return globals.getModuleUpdateTable(); }
294
295 mlir::AsmState &getAsmState() { return globals.getAsmState(); }
296 void dirty() { globals.dirty(); }
297
298 template <typename T>
299 void render(Operation *op, T &out);
300 template <typename T>
301 void render(Value value, T &out);
302 template <typename T>
303 void renderLong(Value value, T &out);
304 template <typename T>
305 void render(Term *term, T &out);
306 template <typename T>
307 struct Render;
308 template <typename T>
309 Render<T> render(T &&subject);
310 struct RenderLong;
311 RenderLong renderLong(Value value);
312
313 Term *find(Term *x);
314 LogicalResult unify(Term *lhs, Term *rhs);
315 LogicalResult unify(VariableTerm *x, Term *y);
316 LogicalResult unify(ValueTerm *xv, Term *y);
317 LogicalResult unify(RowTerm *lhsRow, Term *rhs);
318 void solve(Term *lhs, Term *rhs);
319
320 [[nodiscard]] RowTerm *allocRow(size_t size);
321 [[nodiscard]] RowTerm *allocRow(ArrayRef<Term *> elements);
322 [[nodiscard]] VariableTerm *allocVar();
323 [[nodiscard]] ValueTerm *allocVal(DomainValue value);
324 template <typename T, typename... Args>
325 T *alloc(Args &&...args);
326 ArrayRef<Term *> allocArray(ArrayRef<Term *> elements);
327
328 DomainValue getOptUnderlyingDomain(DomainValue value);
329 Term *getOptTermForDomain(DomainValue value);
330 Term *getTermForDomain(DomainValue value);
331 void setTermForDomain(DomainValue value, Term *term);
332
333 Term *getOptDomainAssociation(Value value);
334 Term *getDomainAssociation(Value value);
335 void setDomainAssociation(Value value, Term *term);
336
337 /// True if the value is "colorless": it is only driven by nodes or primops
338 /// whose inputs all terminate in constants, and therefore is not tied to any
339 /// domain. A colorless value imposes and inherits no domain constraints and
340 /// may be freely consumed by a value in any domain. All ports and wires are
341 /// treated as colored. Computed structurally over the SSA graph, memoized per
342 /// module.
343 bool isColorless(Value value);
344
345 void processDomainDefinition(DomainValue domain);
346 RowTerm *getDomainAssociationAsRow(Value value);
347
348 void noteLocation(InFlightDiagnostic &diag, Operation *op);
349 void noteDomain(InFlightDiagnostic &diag, DomainValue domain);
350 void noteDomainSource(InFlightDiagnostic &diag, DomainValue domain);
351 void noteDomainSource(InFlightDiagnostic &diag, Term *term);
352 void emitDomainCrossingError(Operation *op, Value lhs, Term *lhsTerm,
353 Value rhs, Term *rhsTerm);
354 template <typename T>
355 void emitDuplicatePortDomainError(T op, size_t i, DomainTypeID domainTypeID,
356 IntegerAttr domainPortIndexAttr1,
357 IntegerAttr domainPortIndexAttr2);
358 template <typename T>
359 void emitDomainPortInferenceError(T op, size_t i);
360 template <typename T>
361 void emitAmbiguousPortDomainAssociation(
362 T op, const llvm::TinyPtrVector<DomainValue> &exports,
363 DomainTypeID typeID, size_t i);
364 template <typename T>
365 void emitMissingPortDomainAssociationError(T op, DomainTypeID typeID,
366 size_t i);
367
368 LogicalResult unifyAssociations(Operation *op, Value lhs, Value rhs);
369 template <typename T>
370 LogicalResult unifyAssociations(Operation *op, T &&range);
371 LogicalResult unifyAssociations(Operation *op);
372
373 LogicalResult processModulePorts(FModuleOp moduleOp);
374 template <typename T>
375 LogicalResult processInstancePorts(T op);
376 FInstanceLike fixInstancePorts(FInstanceLike op,
377 const ModuleUpdateInfo &update);
378 LogicalResult processOp(FInstanceLike op);
379 LogicalResult processOp(UnsafeDomainCastOp op);
380 LogicalResult processOp(DomainDefineOp op);
381 LogicalResult processOp(WireOp op);
382 LogicalResult processOp(RWProbeOp op);
383 LogicalResult processOp(Operation *op);
384 LogicalResult processModuleBody(FModuleOp moduleOp);
385 LogicalResult processModule(FModuleOp moduleOp);
386
387 ExportTable initializeExportTable(FModuleOp moduleOp);
388 void ensureSolved(Namespace &ns, DomainTypeID typeID, size_t ip,
389 LocationAttr loc, VariableTerm *var,
390 PendingUpdates &pending);
391 void ensureExported(Namespace &ns, const ExportTable &exports,
392 DomainTypeID typeID, size_t ip, LocationAttr loc,
393 ValueTerm *val, PendingUpdates &pending);
394 void getUpdatesForDomainAssociationOfPort(Namespace &ns,
395 PendingUpdates &pending,
396 DomainTypeID typeID, size_t ip,
397 LocationAttr loc, Term *term,
398 const ExportTable &exports);
399 void getUpdatesForDomainAssociationOfPort(Namespace &ns,
400 const ExportTable &exports,
401 size_t ip, LocationAttr loc,
402 RowTerm *row,
403 PendingUpdates &pending);
404 void getUpdatesForModulePorts(FModuleOp moduleOp, const ExportTable &exports,
405 Namespace &ns, PendingUpdates &pending);
406 void getUpdatesForModule(FModuleOp moduleOp, const ExportTable &exports,
407 PendingUpdates &pending);
408 void applyUpdatesToModule(FModuleOp moduleOp, ExportTable &exports,
409 const PendingUpdates &pending);
410 SmallVector<Attribute> copyPortDomainAssociations(FModuleOp moduleOp,
411 ArrayAttr moduleDomainInfo,
412 size_t portIndex);
413 LogicalResult driveModuleOutputDomainPorts(FModuleOp moduleOp);
414 LogicalResult updateModuleDomainInfo(FModuleOp moduleOp,
415 const ExportTable &exportTable,
416 ArrayAttr &result);
418 solveVarWithAnonDomain(OpBuilder &builder,
419 DenseMap<DomainValue, DomainValue> &domainsInScope,
420 Operation *user, DomainType type, VariableTerm *var);
422 getDomainInScope(OpBuilder &builder,
423 DenseMap<DomainValue, DomainValue> &domainsInScope,
424 DomainValue domain);
425 LogicalResult
426 updateInstance(DenseMap<DomainValue, DomainValue> &domainsInScope,
427 FInstanceLike op);
428 LogicalResult updateWire(DenseMap<DomainValue, DomainValue> &domainsInScope,
429 WireOp wireOp);
430 LogicalResult updateModuleBody(FModuleOp moduleOp);
431 LogicalResult updateModule(FModuleOp moduleOp);
432
433 LogicalResult checkModulePorts(FModuleLike moduleOp);
434 LogicalResult checkModuleDomainPortDrivers(FModuleOp moduleOp);
435 LogicalResult checkInstanceDomainPortDrivers(FInstanceLike op);
436 LogicalResult checkModuleBody(FModuleOp moduleOp);
437
438 LogicalResult inferModule(FModuleOp moduleOp);
439 LogicalResult checkModule(FModuleOp moduleOp);
440 LogicalResult checkModule(FExtModuleOp extModuleOp);
441 LogicalResult checkAndInferModule(FModuleOp moduleOp);
442
443private:
444 CircuitState &globals;
445 DenseMap<Value, Term *> termTable;
446 DenseMap<Value, Term *> associationTable;
447 /// Memoization for `isColorless`. Absent = not computed; present = result.
448 DenseMap<Value, bool> colorlessTable;
449 llvm::BumpPtrAllocator allocator;
450};
451} // namespace
452
453template <typename T>
454void ModuleState::render(Operation *op, T &out) {
455 op->print(out, getAsmState());
456}
457
458template <typename T>
459void ModuleState::render(Value value, T &out) {
460 if (!value) {
461 out << "null";
462 return;
463 }
464
465 auto [name, _] = getFieldName(value);
466 if (name.empty()) {
467 llvm::raw_string_ostream os(name);
468 value.printAsOperand(os, globals.getAsmState());
469 }
470 out << name;
471}
472
473template <typename T>
474void ModuleState::renderLong(Value value, T &out) {
475 if (auto arg = dyn_cast<BlockArgument>(value)) {
476 if (auto moduleOp = llvm::dyn_cast_if_present<FModuleLike>(
477 arg.getOwner()->getParentOp())) {
479 moduleOp.getPortDirection(arg.getArgNumber()));
480 out << " module port ";
481 }
482 } else if (auto result = dyn_cast<OpResult>(value)) {
483 auto *op = result.getOwner();
484 if (auto inst = dyn_cast<FInstanceLike>(op)) {
486 inst.getPortDirection(result.getResultNumber()));
487 out << " instance port ";
488 }
489 }
490
491 render(value, out);
492}
493
494template <typename T>
495// NOLINTNEXTLINE(misc-no-recursion)
496void ModuleState::render(Term *term, T &out) {
497 if (!term) {
498 out << "null";
499 return;
500 }
501 term = find(term);
502 if (auto *var = dyn_cast<VariableTerm>(term)) {
503 out << "?" << globals.getVariableID(var);
504 return;
505 }
506 if (auto *val = dyn_cast<ValueTerm>(term)) {
507 auto value = val->value;
508 render(value, out);
509 return;
510 }
511 if (auto *row = dyn_cast<RowTerm>(term)) {
512 out << "[";
513 llvm::interleaveComma(
514 llvm::seq(size_t(0), getNumDomains()), out, [&](auto i) {
515 render(row->elements[i], out);
516 out << " : " << getDomain(DomainTypeID{i}).getSymName();
517 });
518 out << "]";
519 return;
520 }
521 out << "unknown";
522}
523
524template <typename T>
525struct ModuleState::Render {
526 ModuleState *state;
528};
529
530template <typename T>
531ModuleState::Render<T> ModuleState::render(T &&subject) {
532 return Render<T>{this, std::forward<T>(subject)};
533}
534
535template <typename T>
536static llvm::raw_ostream &operator<<(llvm::raw_ostream &out,
537 ModuleState::Render<T> r) {
538 r.state->render(r.subject, out);
539 return out;
540}
541
543 ModuleState *state;
544 Value value;
545};
546
547ModuleState::RenderLong ModuleState::renderLong(Value value) {
548 return RenderLong{this, value};
549}
550
551static Diagnostic &operator<<(Diagnostic &diag, ModuleState::RenderLong r) {
552 r.state->renderLong(r.value, diag);
553 return diag;
554}
555
556// NOLINTNEXTLINE(misc-no-recursion)
557Term *ModuleState::find(Term *x) {
558 if (!x)
559 return nullptr;
560
561 if (auto *var = dyn_cast<VariableTerm>(x)) {
562 if (var->leader == nullptr)
563 return var;
564
565 auto *leader = find(var->leader);
566 if (leader != var->leader)
567 var->leader = leader;
568 return leader;
569 }
570
571 return x;
572}
573
574LogicalResult ModuleState::unify(VariableTerm *x, Term *y) {
575 assert(!x->leader);
576 x->leader = y;
577 return success();
578}
579
580LogicalResult ModuleState::unify(ValueTerm *xv, Term *y) {
581 if (auto *yv = dyn_cast<VariableTerm>(y)) {
582 yv->leader = xv;
583 return success();
584 }
585
586 if (auto *yv = dyn_cast<ValueTerm>(y))
587 return success(xv == yv);
588
589 return failure();
590}
591
592// NOLINTNEXTLINE(misc-no-recursion)
593LogicalResult ModuleState::unify(RowTerm *lhsRow, Term *rhs) {
594 if (auto *rhsVar = dyn_cast<VariableTerm>(rhs)) {
595 rhsVar->leader = lhsRow;
596 return success();
597 }
598 if (auto *rhsRow = dyn_cast<RowTerm>(rhs)) {
599 for (auto [x, y] : llvm::zip_equal(lhsRow->elements, rhsRow->elements))
600 if (failed(unify(x, y)))
601 return failure();
602 return success();
603 }
604 return failure();
605}
606
607// NOLINTNEXTLINE(misc-no-recursion)
608LogicalResult ModuleState::unify(Term *lhs, Term *rhs) {
609 if (!lhs || !rhs)
610 return success();
611 lhs = find(lhs);
612 rhs = find(rhs);
613 if (lhs == rhs)
614 return success();
615
616 LLVM_DEBUG(llvm::dbgs().indent(6)
617 << "unify " << render(lhs) << " = " << render(rhs) << "\n");
618
619 if (auto *lhsVar = dyn_cast<VariableTerm>(lhs))
620 return unify(lhsVar, rhs);
621 if (auto *lhsVal = dyn_cast<ValueTerm>(lhs))
622 return unify(lhsVal, rhs);
623 if (auto *lhsRow = dyn_cast<RowTerm>(lhs))
624 return unify(lhsRow, rhs);
625 return failure();
626}
627
628void ModuleState::solve(Term *lhs, Term *rhs) {
629 [[maybe_unused]] auto result = unify(lhs, rhs);
630 assert(result.succeeded());
631}
632
633RowTerm *ModuleState::allocRow(size_t size) {
634 SmallVector<Term *> elements;
635 elements.resize(size);
636 return allocRow(elements);
637}
638
639RowTerm *ModuleState::allocRow(ArrayRef<Term *> elements) {
640 auto ds = allocArray(elements);
641 return alloc<RowTerm>(ds);
642}
643
644VariableTerm *ModuleState::allocVar() { return alloc<VariableTerm>(); }
645
646ValueTerm *ModuleState::allocVal(DomainValue value) {
647 return alloc<ValueTerm>(value);
648}
649
650template <typename T, typename... Args>
651T *ModuleState::alloc(Args &&...args) {
652 static_assert(std::is_base_of_v<Term, T>, "T must be a term");
653 return new (allocator) T(std::forward<Args>(args)...);
654}
655
656ArrayRef<Term *> ModuleState::allocArray(ArrayRef<Term *> elements) {
657 auto size = elements.size();
658 if (size == 0)
659 return {};
660
661 auto *result = allocator.Allocate<Term *>(size);
662 llvm::uninitialized_copy(elements, result);
663 for (size_t i = 0; i < size; ++i)
664 if (!result[i])
665 result[i] = alloc<VariableTerm>();
666
667 return ArrayRef(result, size);
668}
669
670DomainValue ModuleState::getOptUnderlyingDomain(DomainValue value) {
671 auto *term = getOptTermForDomain(value);
672 if (auto *val = llvm::dyn_cast_if_present<ValueTerm>(term))
673 return val->value;
674 return nullptr;
675}
676
677Term *ModuleState::getOptTermForDomain(DomainValue value) {
678 assert(isa<DomainType>(value.getType()));
679 auto it = termTable.find(value);
680 if (it == termTable.end())
681 return nullptr;
682 return find(it->second);
683}
684
685Term *ModuleState::getTermForDomain(DomainValue value) {
686 assert(isa<DomainType>(value.getType()));
687 if (auto *term = getOptTermForDomain(value))
688 return term;
689 auto *term = allocVar();
690 setTermForDomain(value, term);
691 return term;
692}
693
694void ModuleState::setTermForDomain(DomainValue value, Term *term) {
695 assert(term);
696 assert(!termTable.contains(value));
697 termTable.insert({value, term});
698 LLVM_DEBUG(llvm::dbgs().indent(6)
699 << "set " << render(value) << " := " << render(term) << "\n");
700}
701
702Term *ModuleState::getOptDomainAssociation(Value value) {
703 assert(isHardware(value));
704 auto it = associationTable.find(value);
705 if (it == associationTable.end())
706 return nullptr;
707 return find(it->second);
708}
709
710Term *ModuleState::getDomainAssociation(Value value) {
711 auto *term = getOptDomainAssociation(value);
712 assert(term);
713 return term;
714}
715
716void ModuleState::setDomainAssociation(Value value, Term *term) {
717 assert(isHardware(value));
718 assert(term);
719 term = find(term);
720 associationTable.insert({value, term});
721 LLVM_DEBUG({
722 llvm::dbgs().indent(6) << "set domains(" << render(value)
723 << ") := " << render(term) << "\n";
724 });
725}
726
727bool ModuleState::isColorless(Value value) {
728 // Non-hardware values (domains, properties, indices, ...) never participate
729 // in coloring, so treat them as colorless: they impose no constraint.
730 if (!isHardware(value))
731 return true;
732
733 // Consult the memo table. A value is visited (expanded) at most once.
734 if (auto it = colorlessTable.find(value); it != colorlessTable.end())
735 return it->second;
736
737 // Classify a single value structurally, without recursing. A "look-through"
738 // value (a node or a pure primop) is colorless iff all of its hardware
739 // operands are colorless. For every look-through op the operands to explore
740 // are exactly all of its operands (a node and a forwarding cast have a single
741 // input operand; a pure expression is only look-through when all of its
742 // operands are hardware), so the caller can iterate the defining op's operand
743 // list directly rather than collecting a subset here. Everything else is
744 // either a colorless constant root or a colored leaf. In particular, all
745 // ports (block arguments, instance results) and wires are colored and must be
746 // assigned a domain.
747 enum class Kind { Colorless, Colored, LookThrough };
748 auto classify = [&](Value v) -> Kind {
749 if (!isHardware(v))
750 return Kind::Colorless;
751
752 auto *op = v.getDefiningOp();
753 // Block arguments (ports) have no defining op and are always colored.
754 if (!op)
755 return Kind::Colored;
756
757 // Constants are the only colorless roots.
758 if (op->hasTrait<OpTrait::ConstantLike>())
759 return Kind::Colorless;
760
761 // A node forwards its single input.
762 if (isa<NodeOp>(op))
763 return Kind::LookThrough;
764
765 // An unsafe domain cast with explicit domain operands is an explicit
766 // coloring point and is always colored. A cast with no domain operands is
767 // a pure forwarding cast that inherits colorlessness from its input.
768 if (auto castOp = dyn_cast<UnsafeDomainCastOp>(op)) {
769 if (!castOp.getDomains().empty())
770 return Kind::Colored;
771 return Kind::LookThrough;
772 }
773
774 // Pure, memory-effect-free expression ops (prim ops, muxes, casts,
775 // aggregate projections) fan out to their hardware-typed operands. For
776 // the ops that are eligible to propagate colorlessness (arithmetic and
777 // bitwise prim ops, muxes, casts, aggregate projections) every SSA operand
778 // is hardware-typed; scalar indices and amounts are attributes, not
779 // operands. A non-hardware SSA operand (a property, domain, or other
780 // opaque value, e.g. a `verbatim.expr` substitution) therefore only
781 // appears on ops that reference external state, which are colored. An
782 // expression with no hardware operands is likewise a non-constant root
783 // (e.g. an `xmr.ref`) and is colored.
784 if (isExpression(op) && mlir::isMemoryEffectFree(op)) {
785 if (op->getNumOperands() == 0)
786 return Kind::Colored;
787 for (auto operand : op->getOperands())
788 if (!isHardware(operand))
789 return Kind::Colored;
790 return Kind::LookThrough;
791 }
792
793 // Everything else (wires, instance results, registers, memories, explicit
794 // domain casts, invalid values, probes, ...) is a colored leaf.
795 return Kind::Colored;
796 };
797
798 // Iterative post-order DFS. Each frame tracks a look-through value (a value
799 // whose colorlessness is not yet known) and requires exploring its operands.
800 // Every look-through op explores all of its operands, so the frame only needs
801 // the value (whose defining op supplies the operands) and the index of the
802 // _next_ operand to visit. A value's colorlessness is the conjunction of its
803 // operands' colorlessness. As soon as a colored operand is found the frame
804 // short-circuits to colored. Since look-through values (constants, nodes,
805 // primops) reference only dominating SSA operands, the explored subgraph is
806 // acyclic (combinational loops only close through wires, which are colored
807 // leaves).
808 //
809 // Note: this DFS is _not_ sufficient to determine colorlessness through
810 // nodes. It is assumed that a post-condition of this pass is that all wires
811 // are assigned domains.
812 struct Frame {
813 // The lookthrough value whose colorlessness is being resolved. Its
814 // defining op supplies the operands to explore; every look-through op
815 // explores all of its operands.
816 Value value;
817 // The index of the next operand to explore.
818 unsigned index = 0;
819 };
820 SmallVector<Frame> stack;
821
822 // Push the first value onto the stack (or exit).
823 switch (classify(value)) {
824 case Kind::Colored:
825 return colorlessTable[value] = false;
826 case Kind::Colorless:
827 return colorlessTable[value] = true;
828 case Kind::LookThrough:
829 stack.push_back({value});
830 break;
831 }
832
833 // Run the DFS.
834 while (!stack.empty()) {
835 auto &frame = stack.back();
836 auto *op = frame.value.getDefiningOp();
837 bool colored = false, pushed = false;
838
839 while (frame.index < op->getNumOperands()) {
840 Value child = op->getOperand(frame.index);
841
842 // If already resolved, short-circuit on a colored operand or advance.
843 if (auto it = colorlessTable.find(child); it != colorlessTable.end()) {
844 if (!it->second) {
845 colored = true;
846 break;
847 }
848 ++frame.index;
849 continue;
850 }
851
852 // Classify the operand. Classify if not lookthrough. Otherwise, push
853 // the lookthrough operand onto the stack and break so that we descend
854 // into it.
855 switch (classify(child)) {
856 case Kind::Colored:
857 colorlessTable[child] = false;
858 colored = true;
859 break;
860 case Kind::Colorless:
861 colorlessTable[child] = true;
862 ++frame.index;
863 continue;
864 case Kind::LookThrough:
865 stack.push_back({child});
866 pushed = true;
867 break;
868 }
869 break;
870 }
871
872 // We hit a lookthrough operand. Dexcend into this. We will revisit the
873 // current frame.index once we have an answer for that operand.
874 if (pushed)
875 continue;
876
877 // All operands resolved (or a colored operand short-circuited). Record the
878 // result and pop this frame.
879 colorlessTable[frame.value] = !colored;
880 stack.pop_back();
881 }
882
883 return colorlessTable[value];
884}
885
886void ModuleState::processDomainDefinition(DomainValue domain) {
887 assert(isa<DomainType>(domain.getType()));
888 auto *newTerm = allocVal(domain);
889 auto *oldTerm = getOptTermForDomain(domain);
890 if (!oldTerm) {
891 setTermForDomain(domain, newTerm);
892 return;
893 }
894
895 [[maybe_unused]] auto result = unify(oldTerm, newTerm);
896 assert(result.succeeded());
897}
898
899RowTerm *ModuleState::getDomainAssociationAsRow(Value value) {
900 assert(isHardware(value));
901 auto *term = getOptDomainAssociation(value);
902
903 // If the term is unknown, allocate a fresh row and set the association.
904 if (!term) {
905 auto *row = allocRow(getNumDomains());
906 setDomainAssociation(value, row);
907 return row;
908 }
909
910 // If the term is already a row, return it.
911 if (auto *row = dyn_cast<RowTerm>(term))
912 return row;
913
914 // Otherwise, unify the term with a fresh row of domains.
915 if (auto *var = dyn_cast<VariableTerm>(term)) {
916 auto *row = allocRow(getNumDomains());
917 solve(var, row);
918 return row;
919 }
920
921 assert(false && "unhandled term type");
922 return nullptr;
923}
924
925void ModuleState::noteLocation(InFlightDiagnostic &diag, Operation *op) {
926 auto &note = diag.attachNote(op->getLoc());
927 if (auto mod = dyn_cast<FModuleOp>(op)) {
928 note << "in module " << mod.getModuleNameAttr();
929 return;
930 }
931 if (auto mod = dyn_cast<FExtModuleOp>(op)) {
932 note << "in extmodule " << mod.getModuleNameAttr();
933 return;
934 }
935 if (auto inst = dyn_cast<InstanceOp>(op)) {
936 note << "in instance " << inst.getInstanceNameAttr();
937 return;
938 }
939 if (auto inst = dyn_cast<InstanceChoiceOp>(op)) {
940 note << "in instance_choice " << inst.getNameAttr();
941 return;
942 }
943
944 note << "here";
945}
946
947void ModuleState::noteDomain(InFlightDiagnostic &diag, DomainValue domain) {
948 auto &note = diag.attachNote(domain.getLoc());
949 note << renderLong(domain);
950
951 if (globals.inserted.contains(domain)) {
952 note << " automatically inserted here";
953 return;
954 }
955
956 note << " declared here";
957}
958
959void ModuleState::noteDomainSource(InFlightDiagnostic &diag,
960 DomainValue domain) {
961 auto &irns = globals.getInnerRefNamespace();
962 SmallVector<FInstanceLike> stack;
963 llvm::SmallDenseSet<DomainValue> seen;
964
965 // This is reusing "domain" across iterations of the while loop.
966
967 auto chaseConnect = [&]() {
968 for (auto *user : domain.getUsers()) {
969 if (auto defineOp = dyn_cast<DomainDefineOp>(user)) {
970 if (defineOp.getDest() != domain)
971 continue;
972 auto src = defineOp.getSrc();
973 diag.attachNote(defineOp.getLoc())
974 << renderLong(domain) << " aliases " << renderLong(src);
975 domain = defineOp.getSrc();
976 return true;
977 }
978 }
979 return false;
980 };
981
982 auto chaseModulePort = [&]() {
983 auto arg = dyn_cast<BlockArgument>(domain);
984 if (!arg)
985 return false;
986
987 auto module =
988 llvm::dyn_cast_if_present<FModuleOp>(arg.getOwner()->getParentOp());
989 if (!module)
990 return false;
991
992 auto name = module.getModuleNameAttr();
993 while (!stack.empty()) {
994 auto instance = stack.back();
995 stack.pop_back();
996 auto referenced = instance.getReferencedModuleNamesAttr().getValue();
997 if (llvm::is_contained(referenced, name)) {
998 domain = cast<DomainValue>(instance->getResult(arg.getArgNumber()));
999 return true;
1000 }
1001 }
1002 return false;
1003 };
1004
1005 auto chaseInstancePort = [&]() {
1006 auto result = dyn_cast<OpResult>(domain);
1007 if (!result)
1008 return false;
1009
1010 auto inst = dyn_cast<FInstanceLike>(result.getOwner());
1011 if (!inst)
1012 return false;
1013
1014 auto index = result.getResultNumber();
1015 if (inst.getPortDirection(index) == Direction::In)
1016 return false;
1017
1018 auto names = inst.getReferencedModuleNamesAttr().getAsRange<StringAttr>();
1019 for (auto name : names) {
1020 auto moduleLike = cast<FModuleLike>(irns.symTable.lookup(name));
1021 if (auto moduleOp = dyn_cast<FModuleOp>(moduleLike.getOperation())) {
1022 stack.push_back(inst);
1023 domain = cast<DomainValue>(moduleOp.getArgument(index));
1024 return true;
1025 }
1026 }
1027 return false;
1028 };
1029
1030 auto chaseUnderlying = [&]() {
1031 if (auto *term = getOptTermForDomain(domain)) {
1032 if (auto *val = dyn_cast<ValueTerm>(term)) {
1033 if (domain != val->value) {
1034 diag.attachNote(domain.getLoc())
1035 << renderLong(domain) << " aliases " << renderLong(val->value);
1036 domain = val->value;
1037 return true;
1038 }
1039 }
1040 }
1041 return false;
1042 };
1043
1044 while (true) {
1045 auto [it, inserted] = seen.insert(domain);
1046 if (!inserted)
1047 return;
1048
1049 noteDomain(diag, domain);
1050 chaseConnect() || chaseModulePort() || chaseInstancePort() ||
1051 chaseUnderlying();
1052 }
1053}
1054
1055void ModuleState::noteDomainSource(InFlightDiagnostic &diag, Term *term) {
1056 auto *val = dyn_cast<ValueTerm>(find(term));
1057 if (!val)
1058 return;
1059
1060 noteDomainSource(diag, val->value);
1061}
1062
1063void ModuleState::emitDomainCrossingError(Operation *op, Value lhs,
1064 Term *lhsTerm, Value rhs,
1065 Term *rhsTerm) {
1066 auto *lhsRow = cast<RowTerm>(lhsTerm);
1067 auto *rhsRow = cast<RowTerm>(rhsTerm);
1068 auto diag =
1069 op->emitError("illegal domain crossing in operation between operands ");
1070 render(lhs, diag);
1071 diag << " and ";
1072 render(rhs, diag);
1073 auto &note1 = diag.attachNote(lhs.getLoc());
1074 render(lhs, note1);
1075 note1 << " has domains ";
1076 render(lhsRow, note1);
1077 auto &note2 = diag.attachNote(rhs.getLoc());
1078 render(rhs, note2);
1079 note2 << " has domains ";
1080 render(rhsRow, note2);
1081
1082 for (size_t i = 0, e = getNumDomains(); i < e; ++i) {
1083 auto *lhsDomain = find(lhsRow->elements[i]);
1084 auto *rhsDomain = find(rhsRow->elements[i]);
1085 if (lhsDomain == rhsDomain)
1086 continue;
1087
1088 noteDomainSource(diag, lhsDomain);
1089 noteDomainSource(diag, rhsDomain);
1090 }
1091}
1092
1093template <typename T>
1094void ModuleState::emitDuplicatePortDomainError(
1095 T op, size_t i, DomainTypeID domainTypeID, IntegerAttr domainPortIndexAttr1,
1096 IntegerAttr domainPortIndexAttr2) {
1097 auto portName = op.getPortNameAttr(i);
1098 auto portLoc = op.getPortLocation(i);
1099 auto domainDecl = getDomain(domainTypeID);
1100 auto domainName = domainDecl.getNameAttr();
1101 auto domainPortIndex1 = domainPortIndexAttr1.getUInt();
1102 auto domainPortIndex2 = domainPortIndexAttr2.getUInt();
1103 auto domainPortName1 = op.getPortNameAttr(domainPortIndex1);
1104 auto domainPortName2 = op.getPortNameAttr(domainPortIndex2);
1105 auto domainPortLoc1 = op.getPortLocation(domainPortIndex1);
1106 auto domainPortLoc2 = op.getPortLocation(domainPortIndex2);
1107 auto diag = emitError(portLoc);
1108 diag << "duplicate " << domainName << " association for port " << portName;
1109 auto &note1 = diag.attachNote(domainPortLoc1);
1110 note1 << "associated with " << domainName << " port " << domainPortName1;
1111 auto &note2 = diag.attachNote(domainPortLoc2);
1112 note2 << "associated with " << domainName << " port " << domainPortName2;
1113 noteLocation(diag, op);
1114}
1115
1116/// Emit an error when we fail to infer the concrete domain to drive to a
1117/// domain port.
1118template <typename T>
1119void ModuleState::emitDomainPortInferenceError(T op, size_t i) {
1120 auto name = op.getPortNameAttr(i);
1121 auto diag = emitError(op->getLoc());
1122 auto info = op.getDomainInfo();
1123 diag << "unable to infer value for undriven domain port " << name;
1124 for (size_t j = 0, e = op.getNumPorts(); j < e; ++j) {
1125 if (auto assocs = dyn_cast<ArrayAttr>(info[j])) {
1126 for (auto assoc : assocs) {
1127 if (i == cast<IntegerAttr>(assoc).getValue()) {
1128 auto name = op.getPortNameAttr(j);
1129 auto loc = op.getPortLocation(j);
1130 diag.attachNote(loc) << "associated with hardware port " << name;
1131 break;
1132 }
1133 }
1134 }
1135 }
1136 noteLocation(diag, op);
1137}
1138
1139template <typename T>
1140void ModuleState::emitAmbiguousPortDomainAssociation(
1141 T op, const llvm::TinyPtrVector<DomainValue> &exports, DomainTypeID typeID,
1142 size_t i) {
1143 auto portName = op.getPortNameAttr(i);
1144 auto portLoc = op.getPortLocation(i);
1145 auto domainDecl = getDomain(typeID);
1146 auto domainName = domainDecl.getNameAttr();
1147 auto diag = emitError(portLoc) << "ambiguous " << domainName
1148 << " association for port " << portName;
1149 for (auto e : exports) {
1150 auto arg = cast<BlockArgument>(e);
1151 auto name = op.getPortNameAttr(arg.getArgNumber());
1152 auto loc = op.getPortLocation(arg.getArgNumber());
1153 diag.attachNote(loc) << "candidate association " << name;
1154 }
1155 noteLocation(diag, op);
1156}
1157
1158template <typename T>
1159void ModuleState::emitMissingPortDomainAssociationError(T op,
1160 DomainTypeID typeID,
1161 size_t i) {
1162 auto domainName = getDomain(typeID).getNameAttr();
1163 auto portName = op.getPortNameAttr(i);
1164 auto diag = emitError(op.getPortLocation(i))
1165 << "missing " << domainName << " association for port "
1166 << portName;
1167 noteLocation(diag, op);
1168}
1169
1170LogicalResult ModuleState::unifyAssociations(Operation *op, Value lhs,
1171 Value rhs) {
1172 if (!lhs || !rhs)
1173 return success();
1174
1175 if (lhs == rhs)
1176 return success();
1177
1178 if (!isHardware(lhs) || !isHardware(rhs))
1179 return success();
1180
1181 // Colorless values impose and receive no association: colorless is below
1182 // every color in the lattice.
1183 if (isColorless(lhs) || isColorless(rhs))
1184 return success();
1185
1186 LLVM_DEBUG({
1187 llvm::dbgs().indent(6) << "unify domains(" << render(lhs) << ") = domains("
1188 << render(rhs) << ")\n";
1189 });
1190
1191 auto *lhsTerm = getOptDomainAssociation(lhs);
1192 auto *rhsTerm = getOptDomainAssociation(rhs);
1193
1194 if (lhsTerm) {
1195 if (rhsTerm) {
1196 if (failed(unify(lhsTerm, rhsTerm))) {
1197 emitDomainCrossingError(op, lhs, lhsTerm, rhs, rhsTerm);
1198 return failure();
1199 }
1200 return success();
1201 }
1202 setDomainAssociation(rhs, lhsTerm);
1203 return success();
1204 }
1205
1206 if (rhsTerm) {
1207 setDomainAssociation(lhs, rhsTerm);
1208 return success();
1209 }
1210
1211 auto *var = allocVar();
1212 setDomainAssociation(lhs, var);
1213 setDomainAssociation(rhs, var);
1214 return success();
1215}
1216
1217template <typename T>
1218LogicalResult ModuleState::unifyAssociations(Operation *op, T &&range) {
1219 Value lhs;
1220 for (auto rhs : std::forward<T>(range)) {
1221 if (!isHardware(rhs) || isColorless(rhs))
1222 continue;
1223 if (failed(unifyAssociations(op, lhs, rhs)))
1224 return failure();
1225 lhs = rhs;
1226 }
1227
1228 return success();
1229}
1230
1231LogicalResult ModuleState::unifyAssociations(Operation *op) {
1232 return unifyAssociations(
1233 op, llvm::concat<Value>(op->getOperands(), op->getResults()));
1234}
1235
1236LogicalResult ModuleState::processModulePorts(FModuleOp moduleOp) {
1237 auto numDomains = getNumDomains();
1238 auto domainInfo = moduleOp.getDomainInfoAttr();
1239 auto numPorts = moduleOp.getNumPorts();
1240
1241 DenseMap<unsigned, DomainTypeID> domainTypeIDTable;
1242 for (size_t i = 0; i < numPorts; ++i) {
1243 auto port = dyn_cast<DomainValue>(moduleOp.getArgument(i));
1244 if (!port)
1245 continue;
1246
1247 LLVM_DEBUG(llvm::dbgs().indent(4)
1248 << "process port " << render(port) << "\n");
1249
1250 if (moduleOp.getPortDirection(i) == Direction::In)
1251 processDomainDefinition(port);
1252
1253 domainTypeIDTable[i] = getDomainTypeID(moduleOp, i);
1254 }
1255
1256 for (size_t i = 0; i < numPorts; ++i) {
1257 BlockArgument port = moduleOp.getArgument(i);
1258 if (!isHardware(port))
1259 continue;
1260
1261 LLVM_DEBUG(llvm::dbgs().indent(4)
1262 << "process port " << render(port) << "\n");
1263
1264 SmallVector<IntegerAttr> associations(numDomains);
1265 for (auto domainPortIndex : getPortDomainAssociation(domainInfo, i)) {
1266 auto domainTypeID = domainTypeIDTable.at(domainPortIndex.getUInt());
1267 auto prevDomainPortIndex = associations[domainTypeID.index];
1268 if (prevDomainPortIndex) {
1269 emitDuplicatePortDomainError(moduleOp, i, domainTypeID,
1270 prevDomainPortIndex, domainPortIndex);
1271 return failure();
1272 }
1273 associations[domainTypeID.index] = domainPortIndex;
1274 }
1275
1276 SmallVector<Term *> elements(numDomains);
1277 for (size_t domainTypeIndex = 0; domainTypeIndex < numDomains;
1278 ++domainTypeIndex) {
1279 auto domainPortIndex = associations[domainTypeIndex];
1280 if (!domainPortIndex)
1281 continue;
1282 auto domainPortValue =
1283 cast<DomainValue>(moduleOp.getArgument(domainPortIndex.getUInt()));
1284 elements[domainTypeIndex] = getTermForDomain(domainPortValue);
1285 }
1286
1287 auto *domainAssociations = allocRow(elements);
1288 setDomainAssociation(port, domainAssociations);
1289 }
1290
1291 return success();
1292}
1293
1294template <typename T>
1295LogicalResult ModuleState::processInstancePorts(T op) {
1296 auto numDomains = getNumDomains();
1297 auto domainInfo = op.getDomainInfoAttr();
1298 auto numPorts = op.getNumPorts();
1299
1300 DenseMap<unsigned, DomainTypeID> domainTypeIDTable;
1301 for (size_t i = 0; i < numPorts; ++i) {
1302 auto port = dyn_cast<DomainValue>(op->getResult(i));
1303 if (!port)
1304 continue;
1305
1306 if (op.getPortDirection(i) == Direction::Out)
1307 processDomainDefinition(port);
1308
1309 domainTypeIDTable[i] = getDomainTypeID(op, i);
1310 }
1311
1312 for (size_t i = 0; i < numPorts; ++i) {
1313 Value port = op->getResult(i);
1314 if (!isHardware(port))
1315 continue;
1316
1317 SmallVector<IntegerAttr> associations(numDomains);
1318 for (auto domainPortIndex : getPortDomainAssociation(domainInfo, i)) {
1319 auto domainTypeID = domainTypeIDTable.at(domainPortIndex.getUInt());
1320 auto prevDomainPortIndex = associations[domainTypeID.index];
1321 if (prevDomainPortIndex) {
1322 emitDuplicatePortDomainError(op, i, domainTypeID, prevDomainPortIndex,
1323 domainPortIndex);
1324 return failure();
1325 }
1326 associations[domainTypeID.index] = domainPortIndex;
1327 }
1328
1329 SmallVector<Term *> elements(numDomains);
1330 for (size_t domainTypeIndex = 0; domainTypeIndex < numDomains;
1331 ++domainTypeIndex) {
1332 auto domainPortIndex = associations[domainTypeIndex];
1333 if (!domainPortIndex)
1334 continue;
1335 auto domainPortValue =
1336 cast<DomainValue>(op->getResult(domainPortIndex.getUInt()));
1337 elements[domainTypeIndex] = getTermForDomain(domainPortValue);
1338 }
1339
1340 auto *domainAssociations = allocRow(elements);
1341 setDomainAssociation(port, domainAssociations);
1342 }
1343
1344 return success();
1345}
1346
1347FInstanceLike ModuleState::fixInstancePorts(FInstanceLike op,
1348 const ModuleUpdateInfo &update) {
1349 auto clone = op.cloneWithInsertedPortsAndReplaceUses(update.portInsertions);
1350 clone.setDomainInfoAttr(update.portDomainInfo);
1351 op->erase();
1352 dirty();
1353 LLVM_DEBUG(llvm::dbgs().indent(6) << "fixup " << render(clone) << "\n");
1354 return clone;
1355}
1356
1357LogicalResult ModuleState::processOp(FInstanceLike op) {
1358 auto moduleName =
1359 cast<StringAttr>(cast<ArrayAttr>(op.getReferencedModuleNamesAttr())[0]);
1360 auto updateTable = getModuleUpdateTable();
1361 auto lookup = updateTable.find(moduleName);
1362 if (lookup != updateTable.end())
1363 op = fixInstancePorts(op, lookup->second);
1364 return processInstancePorts(op);
1365}
1366
1367LogicalResult ModuleState::processOp(UnsafeDomainCastOp op) {
1368 auto domains = op.getDomains();
1369 if (domains.empty())
1370 return unifyAssociations(op, op.getInput(), op.getResult());
1371
1372 auto input = op.getInput();
1373
1374 SmallVector<Term *> elements(getNumDomains());
1375 if (isHardware(input) && !isColorless(input)) {
1376 auto *inputRow = getDomainAssociationAsRow(input);
1377 elements.assign(inputRow->elements);
1378 }
1379
1380 for (auto value : op.getDomains()) {
1381 auto domain = cast<DomainValue>(value);
1382 auto typeID = getDomainTypeID(domain);
1383 elements[typeID.index] = getTermForDomain(domain);
1384 }
1385
1386 auto *row = allocRow(elements);
1387 setDomainAssociation(op.getResult(), row);
1388 return success();
1389}
1390
1391LogicalResult ModuleState::processOp(DomainDefineOp op) {
1392 auto src = op.getSrc();
1393 auto dst = op.getDest();
1394
1395 auto *srcTerm = getTermForDomain(src);
1396 auto *dstTerm = getTermForDomain(dst);
1397 if (succeeded(unify(dstTerm, srcTerm)))
1398 return success();
1399
1400 auto diag =
1401 op->emitOpError()
1402 << "defines a domain value that was inferred to be a different domain '";
1403 render(dstTerm, diag);
1404 diag << "'";
1405
1406 return failure();
1407}
1408
1409LogicalResult ModuleState::processOp(WireOp op) {
1410 // If the wire has explicit domain operands, seed the domain table with them
1411 // as constraints. When this op is visited, connections have not yet been
1412 // processed (wire declarations precede their uses), so the existing row
1413 // contains only fresh variables that unify unconditionally. Any conflict
1414 // between an explicit wire domain and a connection's inferred domain is
1415 // caught later by the connection's own processOp.
1416 if (op.getDomains().empty())
1417 return unifyAssociations(op, op.getResults());
1418
1419 // Build a row with the explicitly-specified domain slots filled in and set
1420 // it as the association for this wire result.
1421 SmallVector<Term *> elements(getNumDomains());
1422 for (auto domain : op.getDomains()) {
1423 auto domainValue = cast<DomainValue>(domain);
1424 auto typeID = getDomainTypeID(domainValue);
1425 elements[typeID.index] = getTermForDomain(domainValue);
1426 }
1427
1428 auto *row = allocRow(elements);
1429 for (auto result : op.getResults())
1430 setDomainAssociation(result, row);
1431
1432 return success();
1433}
1434
1435LogicalResult ModuleState::processOp(RWProbeOp op) {
1436 auto target = globals.getInnerRefNamespace().lookup(op.getTarget());
1437
1438 if (target.isPort()) {
1439 auto targetOp = cast<FModuleOp>(target.getOp());
1440 auto targetValue = targetOp.getArgument(target.getPort());
1441 return unifyAssociations(op, targetValue, op.getResult());
1442 }
1443
1444 auto targetOp = cast<hw::InnerSymbolOpInterface>(target.getOp());
1445 auto targetValue = targetOp.getTargetResult();
1446 return unifyAssociations(op, targetValue, op.getResult());
1447}
1448
1449LogicalResult ModuleState::processOp(Operation *op) {
1450 LLVM_DEBUG(llvm::dbgs().indent(4) << "process " << render(op) << "\n");
1451 if (auto instance = dyn_cast<FInstanceLike>(op))
1452 return processOp(instance);
1453 if (auto wireOp = dyn_cast<WireOp>(op))
1454 return processOp(wireOp);
1455 if (auto cast = dyn_cast<UnsafeDomainCastOp>(op))
1456 return processOp(cast);
1457 if (auto def = dyn_cast<DomainDefineOp>(op))
1458 return processOp(def);
1459 if (auto probe = dyn_cast<RWProbeOp>(op))
1460 return processOp(probe);
1461 if (auto create = dyn_cast<DomainCreateOp>(op)) {
1462 processDomainDefinition(create);
1463 return success();
1464 }
1465 if (auto createAnon = dyn_cast<DomainCreateAnonOp>(op)) {
1466 processDomainDefinition(createAnon);
1467 return success();
1468 }
1469
1470 return unifyAssociations(op);
1471}
1472
1473LogicalResult ModuleState::processModuleBody(FModuleOp moduleOp) {
1474 return failure(
1475 moduleOp.getBody()
1476 .walk([&](Operation *op) -> WalkResult { return processOp(op); })
1477 .wasInterrupted());
1478}
1479
1480LogicalResult ModuleState::processModule(FModuleOp moduleOp) {
1481 LLVM_DEBUG(llvm::dbgs().indent(2) << "processing:\n");
1482 if (failed(processModulePorts(moduleOp)))
1483 return failure();
1484 if (failed(processModuleBody(moduleOp)))
1485 return failure();
1486 return success();
1487}
1488
1489ExportTable ModuleState::initializeExportTable(FModuleOp moduleOp) {
1490 ExportTable exports;
1491 size_t numPorts = moduleOp.getNumPorts();
1492 for (size_t i = 0; i < numPorts; ++i) {
1493 auto port = dyn_cast<DomainValue>(moduleOp.getArgument(i));
1494 if (!port)
1495 continue;
1496 auto value = getOptUnderlyingDomain(port);
1497 if (value)
1498 exports[value].push_back(port);
1499 }
1500
1501 LLVM_DEBUG({
1502 llvm::dbgs().indent(2) << "domain exports:\n";
1503 for (auto entry : exports) {
1504 llvm::dbgs().indent(4) << render(entry.first) << " exported as ";
1505 llvm::interleaveComma(entry.second, llvm::dbgs(),
1506 [&](auto e) { llvm::dbgs() << render(e); });
1507 llvm::dbgs() << "\n";
1508 }
1509 });
1510
1511 return exports;
1512}
1513
1514void ModuleState::ensureSolved(Namespace &ns, DomainTypeID typeID, size_t ip,
1515 LocationAttr loc, VariableTerm *var,
1516 PendingUpdates &pending) {
1517 if (pending.solutions.contains(var))
1518 return;
1519
1520 auto *context = loc.getContext();
1521 auto domainDecl = getDomain(typeID);
1522 auto domainName = domainDecl.getNameAttr();
1523
1524 auto portName = StringAttr::get(context, ns.newName(domainName.getValue()));
1525 auto portType = DomainType::getFromDomainOp(domainDecl);
1526 auto portDirection = Direction::In;
1527 auto portSym = StringAttr();
1528 auto portLoc = loc;
1529 auto portAnnos = std::nullopt;
1530 // Domain type ports have no associations (domain info is in the type).
1531 auto portDomainInfo = ArrayAttr::get(context, {});
1532 PortInfo portInfo(portName, portType, portDirection, portSym, portLoc,
1533 portAnnos, portDomainInfo);
1534
1535 pending.solutions[var] = pending.insertions.size() + ip;
1536 pending.insertions.push_back({ip, portInfo});
1537}
1538
1539void ModuleState::ensureExported(Namespace &ns, const ExportTable &exports,
1540 DomainTypeID typeID, size_t ip,
1541 LocationAttr loc, ValueTerm *val,
1542 PendingUpdates &pending) {
1543 auto value = val->value;
1544 assert(isa<DomainType>(value.getType()));
1545 if (isPort(value) || exports.contains(value) ||
1546 pending.exports.contains(value))
1547 return;
1548
1549 auto *context = loc.getContext();
1550
1551 auto domainDecl = getDomain(typeID);
1552 auto domainName = domainDecl.getNameAttr();
1553
1554 auto portName = StringAttr::get(context, ns.newName(domainName.getValue()));
1555 auto portType = DomainType::getFromDomainOp(domainDecl);
1556 auto portDirection = Direction::Out;
1557 auto portSym = StringAttr();
1558 auto portAnnos = std::nullopt;
1559 // Domain type ports have no associations (domain info is in the type).
1560 auto portDomainInfo = ArrayAttr::get(context, {});
1561 PortInfo portInfo(portName, portType, portDirection, portSym, loc, portAnnos,
1562 portDomainInfo);
1563 pending.exports[value] = pending.insertions.size() + ip;
1564 pending.insertions.push_back({ip, portInfo});
1565}
1566
1567void ModuleState::getUpdatesForDomainAssociationOfPort(
1568 Namespace &ns, PendingUpdates &pending, DomainTypeID typeID, size_t ip,
1569 LocationAttr loc, Term *term, const ExportTable &exports) {
1570 if (auto *var = dyn_cast<VariableTerm>(term)) {
1571 ensureSolved(ns, typeID, ip, loc, var, pending);
1572 return;
1573 }
1574 if (auto *val = dyn_cast<ValueTerm>(term)) {
1575 ensureExported(ns, exports, typeID, ip, loc, val, pending);
1576 return;
1577 }
1578 llvm_unreachable("invalid domain association");
1579}
1580
1581void ModuleState::getUpdatesForDomainAssociationOfPort(
1582 Namespace &ns, const ExportTable &exports, size_t ip, LocationAttr loc,
1583 RowTerm *row, PendingUpdates &pending) {
1584 for (auto [index, term] : llvm::enumerate(row->elements))
1585 getUpdatesForDomainAssociationOfPort(ns, pending, DomainTypeID{index}, ip,
1586 loc, find(term), exports);
1587}
1588
1589void ModuleState::getUpdatesForModulePorts(FModuleOp moduleOp,
1590 const ExportTable &exports,
1591 Namespace &ns,
1592 PendingUpdates &pending) {
1593 for (size_t i = 0, e = moduleOp.getNumPorts(); i < e; ++i) {
1594 auto port = moduleOp.getArgument(i);
1595 if (!isHardware(port))
1596 continue;
1597
1598 getUpdatesForDomainAssociationOfPort(
1599 ns, exports, i, moduleOp.getPortLocation(i),
1600 getDomainAssociationAsRow(port), pending);
1601 }
1602}
1603
1604void ModuleState::getUpdatesForModule(FModuleOp moduleOp,
1605 const ExportTable &exports,
1606 PendingUpdates &pending) {
1607 Namespace ns;
1608 auto names = moduleOp.getPortNamesAttr();
1609 for (auto name : names.getAsRange<StringAttr>())
1610 ns.add(name);
1611 getUpdatesForModulePorts(moduleOp, exports, ns, pending);
1612}
1613
1614void ModuleState::applyUpdatesToModule(FModuleOp moduleOp, ExportTable &exports,
1615 const PendingUpdates &pending) {
1616 LLVM_DEBUG(llvm::dbgs().indent(2) << "applying updates:\n");
1617 // Put the domain ports in place.
1618 moduleOp.insertPorts(pending.insertions);
1619 dirty();
1620
1621 // Solve any variables and record them as "self-exporting".
1622 for (auto [var, portIndex] : pending.solutions) {
1623 auto portValue = cast<DomainValue>(moduleOp.getArgument(portIndex));
1624 auto *solution = allocVal(portValue);
1625 LLVM_DEBUG(llvm::dbgs().indent(4)
1626 << "new-input " << render(portValue) << "\n");
1627 solve(var, solution);
1628 exports[portValue].push_back(portValue);
1629 globals.inserted.insert(portValue);
1630 }
1631
1632 // Drive the output ports, and record the export.
1633 auto builder = OpBuilder::atBlockEnd(moduleOp.getBodyBlock());
1634 for (auto [domainValue, portIndex] : pending.exports) {
1635 auto portValue = cast<DomainValue>(moduleOp.getArgument(portIndex));
1636 builder.setInsertionPointAfterValue(domainValue);
1637 DomainDefineOp::create(builder, portValue.getLoc(), portValue, domainValue);
1638 LLVM_DEBUG(llvm::dbgs().indent(4) << "new-output " << render(portValue)
1639 << " := " << render(domainValue) << "\n");
1640 exports[domainValue].push_back(portValue);
1641 globals.inserted.insert(portValue);
1642 setTermForDomain(portValue, allocVal(domainValue));
1643 }
1644}
1645
1646SmallVector<Attribute> ModuleState::copyPortDomainAssociations(
1647 FModuleOp moduleOp, ArrayAttr moduleDomainInfo, size_t portIndex) {
1648 SmallVector<Attribute> result(getNumDomains());
1649 auto oldAssociations = getPortDomainAssociation(moduleDomainInfo, portIndex);
1650 for (auto domainPortIndexAttr : oldAssociations) {
1651 auto domainPortIndex = domainPortIndexAttr.getUInt();
1652 auto domainTypeID = getDomainTypeID(moduleOp, domainPortIndex);
1653 result[domainTypeID.index] = domainPortIndexAttr;
1654 };
1655 return result;
1656}
1657
1658LogicalResult ModuleState::driveModuleOutputDomainPorts(FModuleOp moduleOp) {
1659 auto builder = OpBuilder::atBlockEnd(moduleOp.getBodyBlock());
1660 for (size_t i = 0, e = moduleOp.getNumPorts(); i < e; ++i) {
1661 auto port = dyn_cast<DomainValue>(moduleOp.getArgument(i));
1662 if (!port || moduleOp.getPortDirection(i) == Direction::In ||
1663 isDriven(port))
1664 continue;
1665
1666 auto *term = getOptTermForDomain(port);
1667 auto *val = llvm::dyn_cast_if_present<ValueTerm>(term);
1668 if (!val) {
1669 emitDomainPortInferenceError(moduleOp, i);
1670 return failure();
1671 }
1672
1673 auto loc = port.getLoc();
1674 auto value = val->value;
1675 LLVM_DEBUG(llvm::dbgs().indent(4) << "connect " << render(port)
1676 << " := " << render(value) << "\n");
1677 DomainDefineOp::create(builder, loc, port, value);
1678 }
1679
1680 return success();
1681}
1682
1683LogicalResult ModuleState::updateModuleDomainInfo(
1684 FModuleOp moduleOp, const ExportTable &exportTable, ArrayAttr &result) {
1685 // At this point, all domain variables mentioned in ports have been
1686 // solved by generalizing the moduleOp (adding input domain ports). Now, we
1687 // have to form the new port domain information for the moduleOp by examining
1688 // the the associated domains of each port.
1689 auto *context = moduleOp.getContext();
1690 auto numDomains = getNumDomains();
1691 auto oldModuleDomainInfo = moduleOp.getDomainInfoAttr();
1692 auto numPorts = moduleOp.getNumPorts();
1693 SmallVector<Attribute> newModuleDomainInfo(numPorts);
1694
1695 for (size_t i = 0; i < numPorts; ++i) {
1696 auto port = moduleOp.getArgument(i);
1697 auto type = port.getType();
1698
1699 if (isa<DomainType>(type)) {
1700 // Domain type ports have no associations (domain info is in the type).
1701 newModuleDomainInfo[i] = ArrayAttr::get(context, {});
1702 continue;
1703 }
1704
1705 if (!isHardware(port)) {
1706 newModuleDomainInfo[i] = ArrayAttr::get(context, {});
1707 continue;
1708 }
1709
1710 auto associations =
1711 copyPortDomainAssociations(moduleOp, oldModuleDomainInfo, i);
1712 auto *row = cast<RowTerm>(getDomainAssociation(port));
1713 for (size_t domainIndex = 0; domainIndex < numDomains; ++domainIndex) {
1714 auto domainTypeID = DomainTypeID{domainIndex};
1715 if (associations[domainIndex])
1716 continue;
1717
1718 auto domain = cast<ValueTerm>(find(row->elements[domainIndex]))->value;
1719 auto &exports = exportTable.at(domain);
1720 if (exports.empty()) {
1721 auto portName = moduleOp.getPortNameAttr(i);
1722 auto portLoc = moduleOp.getPortLocation(i);
1723 auto domainDecl = getDomain(domainTypeID);
1724 auto domainName = domainDecl.getNameAttr();
1725 auto diag = emitError(portLoc) << "private " << domainName
1726 << " association for port " << portName;
1727 diag.attachNote(domain.getLoc()) << "associated domain: " << domain;
1728 noteLocation(diag, moduleOp);
1729 return failure();
1730 }
1731
1732 if (exports.size() > 1) {
1733 emitAmbiguousPortDomainAssociation(moduleOp, exports, domainTypeID, i);
1734 return failure();
1735 }
1736
1737 auto argument = cast<BlockArgument>(exports[0]);
1738 auto domainPortIndex = argument.getArgNumber();
1739 associations[domainTypeID.index] =
1740 IntegerAttr::get(IntegerType::get(context, 32, IntegerType::Unsigned),
1741 domainPortIndex);
1742 }
1743
1744 newModuleDomainInfo[i] = ArrayAttr::get(context, associations);
1745 }
1746
1747 result = ArrayAttr::get(moduleOp.getContext(), newModuleDomainInfo);
1748 moduleOp.setDomainInfoAttr(result);
1749 return success();
1750}
1751
1752DomainValue ModuleState::solveVarWithAnonDomain(
1753 OpBuilder &builder, DenseMap<DomainValue, DomainValue> &domainsInScope,
1754 Operation *user, DomainType type, VariableTerm *var) {
1755 auto name = type.getName().getAttr();
1756 DomainValue anon =
1757 DomainCreateAnonOp::create(builder, user->getLoc(), type, name);
1758 dirty();
1759 LLVM_DEBUG(llvm::dbgs().indent(6) << "create anon " << render(anon) << "\n");
1760 solve(var, allocVal(anon));
1761 domainsInScope[anon] = anon;
1762 globals.inserted.insert(anon);
1763 return anon;
1764}
1765
1766DomainValue ModuleState::getDomainInScope(
1767 OpBuilder &builder, DenseMap<DomainValue, DomainValue> &domainsInScope,
1768 DomainValue domain) {
1769 auto &domainInScope = domainsInScope[domain];
1770 if (domainInScope)
1771 return domainInScope;
1772
1773 domainInScope = cast<DomainValue>(
1774 WireOp::create(builder, domain.getLoc(), domain.getType(),
1775 domain.getType().getName().getAttr())
1776 .getResult());
1777
1778 OpBuilder::InsertionGuard guard(builder);
1779 builder.setInsertionPointAfterValue(domain);
1780 DomainDefineOp::create(builder, domain.getLoc(), domainInScope, domain);
1781 dirty();
1782 LLVM_DEBUG(llvm::dbgs().indent(6) << "bounce wire " << render(domainInScope)
1783 << " := " << render(domain) << "\n");
1784 return domainInScope;
1785}
1786
1787LogicalResult
1788ModuleState::updateInstance(DenseMap<DomainValue, DomainValue> &domainsInScope,
1789 FInstanceLike op) {
1790 LLVM_DEBUG(llvm::dbgs().indent(4) << "update " << render(op) << "\n");
1791 OpBuilder builder(op.getContext());
1792 builder.setInsertionPointAfter(op);
1793 auto numPorts = op->getNumResults();
1794
1795 for (size_t i = 0; i < numPorts; ++i)
1796 if (auto port = dyn_cast<DomainValue>(op->getResult(i)))
1797 if (op.getPortDirection(i) == Direction::Out)
1798 domainsInScope[port] = port;
1799
1800 for (size_t i = 0; i < numPorts; ++i) {
1801 auto port = dyn_cast<DomainValue>(op->getResult(i));
1802 auto direction = op.getPortDirection(i);
1803 // If the port is an input domain, we may need to drive the input with
1804 // a value. If we don't know what value to drive to the port, drive an
1805 // anonymous domain.
1806 if (port && direction == Direction::In && !isDriven(port)) {
1807 auto loc = port.getLoc();
1808 auto *term = getTermForDomain(port);
1809 if (auto *var = dyn_cast<VariableTerm>(term)) {
1810 auto domain = solveVarWithAnonDomain(builder, domainsInScope, op,
1811 port.getType(), var);
1812 LLVM_DEBUG(llvm::dbgs().indent(6) << "connect " << render(port)
1813 << " := " << render(domain) << "\n");
1814 DomainDefineOp::create(builder, loc, port, domain);
1815 continue;
1816 }
1817 if (auto *val = dyn_cast<ValueTerm>(term)) {
1818 auto domain = getDomainInScope(builder, domainsInScope, val->value);
1819 LLVM_DEBUG(llvm::dbgs().indent(6) << "connect " << render(port)
1820 << " := " << render(domain) << "\n");
1821 DomainDefineOp::create(builder, loc, port, domain);
1822 continue;
1823 }
1824 llvm_unreachable("unhandled domain term type");
1825 }
1826 }
1827
1828 return success();
1829}
1830
1831LogicalResult
1832ModuleState::updateWire(DenseMap<DomainValue, DomainValue> &domainsInScope,
1833 WireOp wireOp) {
1834 auto result = wireOp.getResult();
1835
1836 if (auto tgt = dyn_cast<DomainValue>(result)) {
1837 if (isDriven(tgt))
1838 return success();
1839
1840 LLVM_DEBUG(llvm::dbgs().indent(4) << "update " << render(wireOp) << "\n");
1841 OpBuilder builder(wireOp);
1842 builder.setInsertionPointAfter(wireOp);
1843 auto *term = getTermForDomain(tgt);
1844 if (auto *var = dyn_cast<VariableTerm>(term)) {
1845 auto src = solveVarWithAnonDomain(builder, domainsInScope, wireOp,
1846 tgt.getType(), var);
1847 LLVM_DEBUG(llvm::dbgs().indent(6)
1848 << "connect " << render(tgt) << " := " << render(src) << "\n");
1849 DomainDefineOp::create(builder, wireOp.getLoc(), tgt, src);
1850 return success();
1851 }
1852 if (auto *val = dyn_cast<ValueTerm>(term)) {
1853 auto src = getDomainInScope(builder, domainsInScope, val->value);
1854 LLVM_DEBUG(llvm::dbgs().indent(6)
1855 << "connect " << render(tgt) << " := " << render(src) << "\n");
1856 DomainDefineOp::create(builder, wireOp.getLoc(), tgt, src);
1857 return success();
1858 }
1859 llvm_unreachable("unhandled domain term type");
1860 }
1861
1862 if (!isHardware(result) || isColorless(result))
1863 return success();
1864
1865 LLVM_DEBUG(llvm::dbgs().indent(4) << "update " << render(wireOp) << "\n");
1866 OpBuilder builder(wireOp);
1867 auto *row = getDomainAssociationAsRow(wireOp.getResult());
1868
1869 SmallVector<Value> domainOperands;
1870 for (auto [i, element] : llvm::enumerate(
1871 llvm::map_range(row->elements, [&](auto e) { return find(e); }))) {
1872 if (auto *val = dyn_cast<ValueTerm>(element)) {
1873 domainOperands.push_back(
1874 getDomainInScope(builder, domainsInScope, val->value));
1875 continue;
1876 }
1877 if (auto *var = dyn_cast<VariableTerm>(element)) {
1878 auto type = DomainType::getFromDomainOp(getDomain(DomainTypeID{i}));
1879 auto domain =
1880 solveVarWithAnonDomain(builder, domainsInScope, wireOp, type, var);
1881 domainOperands.push_back(domain);
1882 continue;
1883 }
1884 assert(0 && "unhandled domain type");
1885 }
1886 wireOp.getDomainsMutable().assign(domainOperands);
1887 return success();
1888}
1889
1890LogicalResult ModuleState::updateModuleBody(FModuleOp moduleOp) {
1891 DenseMap<DomainValue, DomainValue> domainsInScope;
1892
1893 for (size_t i = 0, e = moduleOp.getNumPorts(); i < e; ++i)
1894 if (auto port = dyn_cast<DomainValue>(moduleOp.getArgument(i)))
1895 if (moduleOp.getPortDirection(i) == Direction::In)
1896 domainsInScope[port] = port;
1897
1898 auto result = moduleOp.getBodyBlock()->walk([&](Operation *op) -> WalkResult {
1899 return TypeSwitch<Operation *, WalkResult>(op)
1900 .Case<WireOp>(
1901 [&](auto wire) { return updateWire(domainsInScope, wire); })
1902 .Case<FInstanceLike>([&](auto instance) {
1903 return updateInstance(domainsInScope, instance);
1904 })
1905 .Case<DomainCreateOp, DomainCreateAnonOp>([&](auto domain) {
1906 domainsInScope[domain] = domain;
1907 return success();
1908 })
1909 .Default([&](auto op) { return success(); });
1910 });
1911 return failure(result.wasInterrupted());
1912}
1913
1914LogicalResult ModuleState::updateModule(FModuleOp moduleOp) {
1915 auto exports = initializeExportTable(moduleOp);
1916 PendingUpdates pending;
1917 getUpdatesForModule(moduleOp, exports, pending);
1918 applyUpdatesToModule(moduleOp, exports, pending);
1919
1920 ArrayAttr portDomainInfo;
1921 if (failed(updateModuleDomainInfo(moduleOp, exports, portDomainInfo)))
1922 return failure();
1923
1924 if (failed(driveModuleOutputDomainPorts(moduleOp)))
1925 return failure();
1926
1927 // Record the updated interface change in the update
1928 auto &entry = getModuleUpdateTable()[moduleOp.getModuleNameAttr()];
1929 entry.portDomainInfo = portDomainInfo;
1930 entry.portInsertions = std::move(pending.insertions);
1931
1932 if (failed(updateModuleBody(moduleOp)))
1933 return failure();
1934
1935 LLVM_DEBUG({
1936 llvm::dbgs().indent(2) << "port summary:\n";
1937 for (auto port : moduleOp.getBodyBlock()->getArguments()) {
1938 llvm::dbgs().indent(4) << render(port);
1939 auto info = cast<ArrayAttr>(
1940 moduleOp.getDomainInfoAttrForPort(port.getArgNumber()));
1941 if (info.size()) {
1942 llvm::dbgs() << " domains [";
1943 llvm::interleaveComma(
1944 info.getAsRange<IntegerAttr>(), llvm::dbgs(), [&](auto i) {
1945 llvm::dbgs() << render(moduleOp.getArgument(i.getUInt()));
1946 });
1947 llvm::dbgs() << "]";
1948 }
1949 llvm::dbgs() << "\n";
1950 }
1951 });
1952
1953 return success();
1954}
1955
1956LogicalResult ModuleState::checkModulePorts(FModuleLike moduleOp) {
1957 auto numDomains = getNumDomains();
1958 auto domainInfo = moduleOp.getDomainInfoAttr();
1959 auto numPorts = moduleOp.getNumPorts();
1960
1961 DenseMap<unsigned, DomainTypeID> domainTypeIDTable;
1962 for (size_t i = 0; i < numPorts; ++i) {
1963 if (isa<DomainType>(moduleOp.getPortType(i)))
1964 domainTypeIDTable[i] = getDomainTypeID(moduleOp, i);
1965 }
1966
1967 for (size_t i = 0; i < numPorts; ++i) {
1968 if (!isHardware(moduleOp.getPortType(i)))
1969 continue;
1970
1971 // Record the domain associations of this port.
1972 SmallVector<IntegerAttr> associations(numDomains);
1973 for (auto domainPortIndex : getPortDomainAssociation(domainInfo, i)) {
1974 auto domainTypeID = domainTypeIDTable.at(domainPortIndex.getUInt());
1975 auto prevDomainPortIndex = associations[domainTypeID.index];
1976 if (prevDomainPortIndex) {
1977 emitDuplicatePortDomainError(moduleOp, i, domainTypeID,
1978 prevDomainPortIndex, domainPortIndex);
1979 return failure();
1980 }
1981 associations[domainTypeID.index] = domainPortIndex;
1982 }
1983
1984 // Check the associations for completeness.
1985 for (size_t domainIndex = 0; domainIndex < numDomains; ++domainIndex) {
1986 auto typeID = DomainTypeID{domainIndex};
1987 if (!associations[domainIndex]) {
1988 emitMissingPortDomainAssociationError(moduleOp, typeID, i);
1989 return failure();
1990 }
1991 }
1992 }
1993
1994 return success();
1995}
1996
1997LogicalResult ModuleState::checkModuleDomainPortDrivers(FModuleOp moduleOp) {
1998 for (size_t i = 0, e = moduleOp.getNumPorts(); i < e; ++i) {
1999 auto port = dyn_cast<DomainValue>(moduleOp.getArgument(i));
2000 if (!port || moduleOp.getPortDirection(i) != Direction::Out ||
2001 isDriven(port))
2002 continue;
2003
2004 auto name = moduleOp.getPortNameAttr(i);
2005 auto diag = emitError(moduleOp.getPortLocation(i))
2006 << "undriven domain port " << name;
2007 noteLocation(diag, moduleOp);
2008 return failure();
2009 }
2010
2011 return success();
2012}
2013
2014LogicalResult ModuleState::checkInstanceDomainPortDrivers(FInstanceLike op) {
2015 for (size_t i = 0, e = op->getNumResults(); i < e; ++i) {
2016 auto port = dyn_cast<DomainValue>(op->getResult(i));
2017 if (!port || op.getPortDirection(i) != Direction::In || isDriven(port))
2018 continue;
2019
2020 auto name = op.getPortNameAttr(i);
2021 auto diag = emitError(op.getPortLocation(i))
2022 << "undriven domain port " << name;
2023 noteLocation(diag, op);
2024 return failure();
2025 }
2026
2027 return success();
2028}
2029
2030LogicalResult ModuleState::checkModuleBody(FModuleOp moduleOp) {
2031 auto result = moduleOp.getBody().walk([&](FInstanceLike op) -> WalkResult {
2032 return checkInstanceDomainPortDrivers(op);
2033 });
2034 return failure(result.wasInterrupted());
2035}
2036
2037LogicalResult ModuleState::inferModule(FModuleOp moduleOp) {
2038 LLVM_DEBUG(llvm::dbgs() << "infer: " << moduleOp.getModuleName() << "\n");
2039 if (failed(processModule(moduleOp)))
2040 return failure();
2041
2042 return updateModule(moduleOp);
2043}
2044
2045LogicalResult ModuleState::checkModule(FModuleOp moduleOp) {
2046 LLVM_DEBUG(llvm::dbgs() << "check: " << moduleOp.getModuleName() << "\n");
2047 if (failed(checkModulePorts(moduleOp)))
2048 return failure();
2049
2050 if (failed(checkModuleDomainPortDrivers(moduleOp)))
2051 return failure();
2052
2053 if (failed(checkModuleBody(moduleOp)))
2054 return failure();
2055
2056 return processModule(moduleOp);
2057}
2058
2059LogicalResult ModuleState::checkModule(FExtModuleOp extModuleOp) {
2060 LLVM_DEBUG(llvm::dbgs() << "check: " << extModuleOp.getModuleName() << "\n");
2061 return checkModulePorts(extModuleOp);
2062}
2063
2064LogicalResult ModuleState::checkAndInferModule(FModuleOp moduleOp) {
2065 LLVM_DEBUG(llvm::dbgs() << "check/infer: " << moduleOp.getModuleName()
2066 << "\n");
2067
2068 if (failed(checkModulePorts(moduleOp)))
2069 return failure();
2070
2071 if (failed(processModule(moduleOp)))
2072 return failure();
2073
2074 if (failed(driveModuleOutputDomainPorts(moduleOp)))
2075 return failure();
2076
2077 return updateModuleBody(moduleOp);
2078}
2079
2080//===---------------------------------------------------------------------------
2081// InferDomainsPass: Top-level pass implementation.
2082//===---------------------------------------------------------------------------
2083
2084LogicalResult CircuitState::runOnModule(Operation *op) {
2085 ModuleState state(*this);
2086 if (auto moduleOp = dyn_cast<FModuleOp>(op)) {
2087 if (mode == InferDomainsMode::Check)
2088 return state.checkModule(moduleOp);
2089
2090 if (mode == InferDomainsMode::InferAll || moduleOp.isPrivate())
2091 return state.inferModule(moduleOp);
2092
2093 return state.checkAndInferModule(moduleOp);
2094 }
2095
2096 if (auto extModuleOp = dyn_cast<FExtModuleOp>(op))
2097 return state.checkModule(extModuleOp);
2098
2099 return success();
2100}
2101
2102LogicalResult CircuitState::run() {
2103 DenseSet<Operation *> errored;
2104 instanceGraph.walkPostOrder([&](auto &node) {
2105 auto moduleOp = node.getModule();
2106 for (auto *inst : node) {
2107 if (errored.contains(inst->getTarget()->getModule())) {
2108 errored.insert(moduleOp);
2109 return;
2110 }
2111 }
2112 if (failed(runOnModule(node.getModule())))
2113 errored.insert(moduleOp);
2114 });
2115 return success(errored.empty());
2116}
2117
2118namespace {
2119struct InferDomainsPass
2120 : public circt::firrtl::impl::InferDomainsBase<InferDomainsPass> {
2121 using Base::Base;
2122 void runOnOperation() override {
2124 auto circuit = getOperation();
2125
2126 // Strip skipped domains in a prepass before checking/inference
2127 if (!skippedDomains.empty()) {
2128 DenseSet<StringAttr> skippedNames;
2129 auto *context = &getContext();
2130 for (const auto &name : skippedDomains)
2131 skippedNames.insert(StringAttr::get(context, name));
2132
2133 if (failed(stripDomainsFromCircuit(circuit, [&](StringAttr name) {
2134 return skippedNames.contains(name);
2135 })))
2136 return signalPassFailure();
2137 }
2138
2139 auto &instanceGraph = getAnalysis<InstanceGraph>();
2140 auto &symbolTable = getAnalysis<SymbolTable>();
2141 auto &innerSymbolTableCollection =
2142 getAnalysis<InnerSymbolTableCollection>();
2143 circt::hw::InnerRefNamespace innerRefNamespace{symbolTable,
2144 innerSymbolTableCollection};
2145 CircuitState state(circuit, instanceGraph, innerRefNamespace, mode);
2146 if (failed(state.run()))
2147 signalPassFailure();
2148 }
2149};
2150} // namespace
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
SmallVector< std::pair< unsigned, PortInfo > > PortInsertions
mlir::TypedValue< DomainType > DomainValue
DenseMap< VariableTerm *, unsigned > PendingSolutions
A map from unsolved variables to a port index, where that port has not yet been created.
static bool isHardware(Type type)
True if a value of the given type could be associated with a domain.
static bool isPort(BlockArgument arg)
Return true if the value is a port on the module.
DenseMap< DomainValue, TinyPtrVector< DomainValue > > ExportTable
A map from domain IR values defined internal to the moduleOp, to ports that alias that domain.
static auto getPortDomainAssociation(ArrayAttr info, size_t i)
From a domain info attribute, get the row of associated domains for a hardware value at index i.
static bool isDriven(DomainValue port)
Returns true if the value is driven by a connect op.
static Block * getBodyBlock(FModuleLike mod)
static FailureOr< Domain > getDomain(Operation *op)
The domain of an AXI4 op, or failure for one this pass does not know.
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:86
This graph tracks modules and where they are instantiated.
This class represents a collection of InnerSymbolTable's.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:56
static StringRef toLongString(Direction direction)
Definition FIRRTLEnums.h:48
LogicalResult stripDomainsFromCircuit(CircuitOp circuit, llvm::function_ref< bool(StringAttr)> shouldStripDomain)
Strip domains whose names match the predicate from a circuit.
InferDomainsMode
The mode for the InferDomains pass.
Definition Passes.h:79
llvm::raw_ostream & operator<<(llvm::raw_ostream &os, const InstanceInfo::LatticeValue &value)
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
bool isExpression(Operation *op)
Return true if the specified operation is a firrtl expression.
void info(Twine message)
Definition LSPUtils.cpp:20
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
This holds the name and type that describes the module's ports.
This class represents the namespace in which InnerRef's can be resolved.