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