CIRCT 23.0.0git
Loading...
Searching...
No Matches
LinkCircuits.cpp
Go to the documentation of this file.
1//===- LinkCircuits.cpp - Merge FIRRTL circuits --------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This pass links multiple circuits into a single one.
10//
11//===----------------------------------------------------------------------===//
12
18#include "mlir/IR/Attributes.h"
19#include "mlir/IR/Builders.h"
20#include "mlir/IR/BuiltinAttributes.h"
21#include "mlir/IR/Diagnostics.h"
22#include "mlir/IR/Operation.h"
23#include "mlir/IR/SymbolTable.h"
24#include "mlir/Support/LLVM.h"
25#include "llvm/ADT/MapVector.h"
26#include "llvm/ADT/STLExtras.h"
27#include <iterator>
28
29namespace circt {
30namespace firrtl {
31#define GEN_PASS_DEF_LINKCIRCUITS
32#include "circt/Dialect/FIRRTL/Passes.h.inc"
33} // namespace firrtl
34} // namespace circt
35
36using namespace mlir;
37using namespace circt;
38using namespace firrtl;
39
40namespace {
41struct LinkCircuitsPass
42 : public circt::firrtl::impl::LinkCircuitsBase<LinkCircuitsPass> {
43 using Base::Base;
44
45 void runOnOperation() override;
46 LogicalResult mergeCircuits();
47 LinkCircuitsPass(StringRef baseCircuitNameOption, bool noMangleOption) {
48 baseCircuitName = std::string(baseCircuitNameOption);
49 noMangle = noMangleOption;
50 }
51};
52} // namespace
53
54template <typename CallableT>
55static DictionaryAttr transformAnnotationTarget(DictionaryAttr anno,
56 CallableT transformTokensFn) {
57 return DictionaryAttr::getWithSorted(
58 anno.getContext(),
59 to_vector(map_range(
60 anno.getValue(), [&](NamedAttribute namedAttr) -> NamedAttribute {
61 if (namedAttr.getName() == "target")
62 if (auto target = dyn_cast<StringAttr>(namedAttr.getValue()))
63 if (auto tokens = tokenizePath(target.getValue()))
64 return {
65 namedAttr.getName(),
66 StringAttr::get(target.getContext(),
67 transformTokensFn(tokens.value()).str())};
68 return namedAttr;
69 })));
70}
71
72static LogicalResult mangleCircuitSymbols(CircuitOp circuit) {
73 auto circuitName = circuit.getNameAttr();
74
75 llvm::MapVector<StringRef, Operation *> renameTable;
76 auto symbolTable = SymbolTable(circuit.getOperation());
77 auto manglePrivateSymbol = [&](SymbolOpInterface symbolOp) {
78 auto symbolName = symbolOp.getNameAttr();
79 auto newSymbolName =
80 StringAttr::get(symbolOp->getContext(),
81 circuitName.getValue() + "_" + symbolName.getValue());
82 renameTable.insert(std::pair(symbolName.getValue(), symbolOp));
83 return symbolTable.rename(symbolOp, newSymbolName);
84 };
85
86 for (auto &op : circuit.getOps()) {
87 auto symbolOp = dyn_cast<SymbolOpInterface>(op);
88 if (!symbolOp)
89 continue;
90
91 if (symbolOp.isPrivate())
92 if (failed(manglePrivateSymbol(symbolOp)))
93 return failure();
94 }
95
96 circuit.walk([&](Operation *op) {
97 auto updateType = [&](Type type) -> Type {
98 if (auto cls = dyn_cast<ClassType>(type))
99 if (auto *newOp = renameTable.lookup(cls.getName()))
100 return ClassType::get(FlatSymbolRefAttr::get(newOp),
101 cls.getElements());
102 return type;
103 };
104 auto updateTypeAttr = [&](Attribute attr) -> Attribute {
105 if (auto typeAttr = dyn_cast<TypeAttr>(attr)) {
106 auto newType = updateType(typeAttr.getValue());
107 if (newType != typeAttr.getValue())
108 return TypeAttr::get(newType);
109 }
110 return attr;
111 };
112 auto updateResults = [&](auto &&results) {
113 for (auto result : results)
114 if (auto newType = updateType(result.getType());
115 newType != result.getType())
116 result.setType(newType);
117 };
118
119 TypeSwitch<Operation *>(op)
120 .Case<CircuitOp>([&](CircuitOp circuit) {
121 SmallVector<Attribute> newAnnotations;
122 llvm::transform(
123 circuit.getAnnotationsAttr(), std::back_inserter(newAnnotations),
124 [&](Attribute attr) {
125 return transformAnnotationTarget(
126 cast<DictionaryAttr>(attr), [&](TokenAnnoTarget &tokens) {
127 if (auto *newModule = renameTable.lookup(tokens.module))
128 tokens.module =
129 cast<SymbolOpInterface>(newModule).getName();
130 return tokens;
131 });
132 });
133 circuit.setAnnotationsAttr(
134 ArrayAttr::get(circuit.getContext(), newAnnotations));
135 })
136 .Case<ObjectOp>([&](ObjectOp obj) {
137 auto resultTypeName = obj.getResult().getType().getName();
138 if (auto *newOp = renameTable.lookup(resultTypeName))
139 obj.getResult().setType(dyn_cast<ClassOp>(newOp).getInstanceType());
140 })
141 .Case<FModuleOp>([&](FModuleOp module) {
142 SmallVector<Attribute> newPortTypes;
143 llvm::transform(module.getPortTypesAttr().getValue(),
144 std::back_inserter(newPortTypes), updateTypeAttr);
145 module.setPortTypesAttr(
146 ArrayAttr::get(module->getContext(), newPortTypes));
147 updateResults(module.getBodyBlock()->getArguments());
148 })
149 .Case<InstanceOp>(
150 [&](InstanceOp instance) { updateResults(instance->getResults()); })
151 .Case<WireOp>([&](WireOp wire) { updateResults(wire->getResults()); })
152 .Default([](Operation *op) {});
153 });
154 return success();
155}
156
157static void collectLayerSymbols(LayerOp layer,
158 SmallVectorImpl<FlatSymbolRefAttr> &stack,
159 llvm::DenseSet<Attribute> &layers) {
160 stack.push_back(FlatSymbolRefAttr::get(layer.getSymNameAttr()));
161 layers.insert(SymbolRefAttr::get(stack.front().getAttr(),
162 ArrayRef(stack).drop_front()));
163 for (auto child : layer.getOps<LayerOp>())
164 collectLayerSymbols(child, stack, layers);
165 stack.pop_back();
166}
167
168static void collectLayerSymbols(CircuitOp circuit,
169 llvm::DenseSet<Attribute> &layers) {
170 SmallVector<FlatSymbolRefAttr> stack;
171 for (auto layer : circuit.getOps<LayerOp>())
172 collectLayerSymbols(layer, stack, layers);
173}
174
175static LogicalResult
176verifyKnownLayers(FExtModuleOp extModule,
177 const llvm::DenseSet<Attribute> &availableLayers) {
178 auto knownLayersAttr = extModule.getKnownLayersAttr();
179 if (!knownLayersAttr)
180 return success();
181
182 SmallVector<Attribute> missingLayers;
183 for (auto attr : knownLayersAttr)
184 if (!availableLayers.contains(attr))
185 missingLayers.push_back(attr);
186
187 if (missingLayers.empty())
188 return success();
189
190 auto diag = extModule.emitOpError()
191 << "declares known layers that are not defined in the linked "
192 "circuit: ";
193 llvm::interleaveComma(missingLayers, diag,
194 [&](Attribute attr) { diag << attr; });
195 return failure();
196}
197
198static LogicalResult mergeLayer(LayerOp dst, LayerOp src) {
199 if (dst.getConvention() != src.getConvention())
200 return src.emitOpError("layer convention mismatch with existing layer");
201
202 SymbolTable dstSymbolTable(dst);
203
204 for (auto &op : llvm::make_early_inc_range(src.getBody().front())) {
205 if (auto srcChildLayer = dyn_cast<LayerOp>(op))
206 if (auto dstChildLayer = cast_if_present<LayerOp>(
207 dstSymbolTable.lookup(srcChildLayer.getNameAttr()))) {
208 if (failed(mergeLayer(dstChildLayer, srcChildLayer)))
209 return failure();
210 continue;
211 }
212 op.moveBefore(&dst.getBody().front(), dst.getBody().front().end());
213 }
214 return success();
215}
216
217/// Handles colliding symbols when merging circuits.
218///
219/// This function resolves symbol collisions between operations in different
220/// circuits during the linking process. It handles three specific cases:
221///
222/// 1. Identical extmodules: When two extmodules have identical attributes, the
223/// incoming one is removed as they are duplicates.
224///
225/// 2. Extmodule declaration + module definition: When an extmodule
226/// (declaration) collides with a module (definition), the declaration is
227/// removed in favor of the definition if their attributes match.
228///
229/// 3. Extmodule with empty parameters (Zaozi workaround): When two extmodules
230/// collide and one has empty parameters, the one without parameters
231/// (placeholder declaration) is removed. This handles a limitation in
232/// Zaozi's module generation where placeholder extmodule declarations are
233/// created from instance ops without knowing the actual parameters or
234/// defname.
235///
236/// \param collidingOp The operation already present in the merged circuit
237/// \param incomingOp The operation being added from another circuit
238/// \return FailureOr<bool> Returns success with true if incomingOp was erased,
239/// success with false if collidingOp was erased, or failure if the
240/// collision cannot be resolved
241///
242/// \note This workaround for empty parameters should ultimately be
243/// removed once ODS is updated to properly support placeholder
244/// declarations.
245static FailureOr<bool>
246handleCollidingOps(SymbolOpInterface collidingOp, SymbolOpInterface incomingOp,
247 const llvm::DenseSet<Attribute> &mergedLayers,
248 const llvm::DenseSet<Attribute> &incomingLayers) {
249 if (!collidingOp.isPublic())
250 return collidingOp->emitOpError("should be a public symbol");
251 if (!incomingOp.isPublic())
252 return incomingOp->emitOpError("should be a public symbol");
253
254 if ((isa<FExtModuleOp>(collidingOp) && isa<FModuleOp>(incomingOp)) ||
255 (isa<FExtModuleOp>(incomingOp) && isa<FModuleOp>(collidingOp))) {
256 auto definition = collidingOp;
257 auto declaration = incomingOp;
258 if (!isa<FModuleOp>(collidingOp)) {
259 definition = incomingOp;
260 declaration = collidingOp;
261 }
262
263 auto extModule = cast<FExtModuleOp>(declaration);
264 const auto &layersToCheck =
265 (definition == incomingOp) ? incomingLayers : mergedLayers;
266 if (failed(verifyKnownLayers(extModule, layersToCheck)))
267 return failure();
268
269 constexpr const StringRef attrsToCompare[] = {
270 "portDirections", "portSymbols", "portNames", "portTypes", "layers"};
271 if (!all_of(attrsToCompare, [&](StringRef attr) {
272 return definition->getAttr(attr) == declaration->getAttr(attr);
273 }))
274 return failure();
275
276 declaration->erase();
277 return declaration == incomingOp;
278 }
279
280 if (isa<FExtModuleOp>(collidingOp) && isa<FExtModuleOp>(incomingOp)) {
281 constexpr const StringRef attrsToCompare[] = {
282 "portDirections", "portSymbols", "portNames",
283 "portTypes", "knownLayers", "layers",
284 };
285 if (!all_of(attrsToCompare, [&](StringRef attr) {
286 return collidingOp->getAttr(attr) == incomingOp->getAttr(attr);
287 }))
288 return failure();
289
290 auto collidingParams = collidingOp->getAttrOfType<ArrayAttr>("parameters");
291 auto incomingParams = incomingOp->getAttrOfType<ArrayAttr>("parameters");
292 if (collidingParams == incomingParams) {
293 if (collidingOp->getAttr("defname") != incomingOp->getAttr("defname"))
294 return failure();
295 incomingOp->erase();
296 return true;
297 }
298
299 // FIXME: definition and declaration may have different defname and
300 // decalration has no parameters
301 if (collidingParams.empty() || incomingParams.empty()) {
302 auto declaration = collidingParams.empty() ? collidingOp : incomingOp;
303 declaration->erase();
304 return declaration == incomingOp;
305 }
306 }
307
308 if (isa<LayerOp>(collidingOp) && isa<LayerOp>(incomingOp)) {
309 if (failed(
310 mergeLayer(cast<LayerOp>(collidingOp), cast<LayerOp>(incomingOp))))
311 return failure();
312 incomingOp->erase();
313 return true;
314 }
315
316 return failure();
317}
318
319LogicalResult LinkCircuitsPass::mergeCircuits() {
320 auto module = getOperation();
321
322 SmallVector<CircuitOp> circuits;
323 for (CircuitOp circuitOp : module.getOps<CircuitOp>())
324 circuits.push_back(circuitOp);
325
326 auto builder = OpBuilder(module);
327 builder.setInsertionPointToEnd(module.getBody());
328 auto mergedCircuit =
329 CircuitOp::create(builder, module.getLoc(),
330 StringAttr::get(&getContext(), baseCircuitName));
331 SmallVector<Attribute> mergedAnnotations;
332
333 llvm::DenseSet<Attribute> mergedLayers;
334
335 for (auto circuit : circuits) {
336 if (!noMangle)
337 if (failed(mangleCircuitSymbols(circuit)))
338 return circuit->emitError("failed to mangle private symbol");
339
340 llvm::DenseSet<Attribute> incomingLayers;
341 collectLayerSymbols(circuit, incomingLayers);
342
343 // TODO: other circuit attributes (such as enable_layers...)
344 llvm::transform(circuit.getAnnotations().getValue(),
345 std::back_inserter(mergedAnnotations), [&](Attribute attr) {
346 return transformAnnotationTarget(
347 cast<DictionaryAttr>(attr),
348 [&](TokenAnnoTarget &tokens) {
349 tokens.circuit = mergedCircuit.getName();
350 return tokens;
351 });
352 });
353
354 // reconstruct symbol table after each merge
355 auto mergedSymbolTable = SymbolTable(mergedCircuit.getOperation());
356
357 SmallVector<Operation *> opsToMove;
358 for (auto &op : circuit.getOps())
359 opsToMove.push_back(&op);
360 for (auto *op : opsToMove) {
361 if (auto symbolOp = dyn_cast<SymbolOpInterface>(op))
362 if (auto collidingOp = cast_if_present<SymbolOpInterface>(
363 mergedSymbolTable.lookup(symbolOp.getNameAttr()))) {
364 auto opErased = handleCollidingOps(collidingOp, symbolOp,
365 mergedLayers, incomingLayers);
366 if (failed(opErased))
367 return mergedCircuit->emitError("has colliding symbol " +
368 symbolOp.getName() +
369 " which cannot be merged");
370 if (opErased.value())
371 continue;
372 }
373
374 op->moveBefore(mergedCircuit.getBodyBlock(),
375 mergedCircuit.getBodyBlock()->end());
376 }
377
378 mergedLayers.insert(incomingLayers.begin(), incomingLayers.end());
379 circuit->erase();
380 }
381
382 mergedCircuit.setAnnotationsAttr(
383 ArrayAttr::get(mergedCircuit.getContext(), mergedAnnotations));
384
385 return mlir::detail::verifySymbolTable(mergedCircuit);
386}
387
388void LinkCircuitsPass::runOnOperation() {
389 if (failed(mergeCircuits()))
390 signalPassFailure();
391}
static FIRRTLBaseType updateType(FIRRTLBaseType oldType, unsigned fieldID, FIRRTLBaseType fieldType)
Update the type of a single field within a type.
static LogicalResult verifyKnownLayers(FExtModuleOp extModule, const llvm::DenseSet< Attribute > &availableLayers)
static DictionaryAttr transformAnnotationTarget(DictionaryAttr anno, CallableT transformTokensFn)
static LogicalResult mergeLayer(LayerOp dst, LayerOp src)
static void collectLayerSymbols(LayerOp layer, SmallVectorImpl< FlatSymbolRefAttr > &stack, llvm::DenseSet< Attribute > &layers)
static LogicalResult mangleCircuitSymbols(CircuitOp circuit)
static FailureOr< bool > handleCollidingOps(SymbolOpInterface collidingOp, SymbolOpInterface incomingOp, const llvm::DenseSet< Attribute > &mergedLayers, const llvm::DenseSet< Attribute > &incomingLayers)
Handles colliding symbols when merging circuits.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.