CIRCT  20.0.0git
ProbesToSignals.cpp
Go to the documentation of this file.
1 //===- ProbesToSignals.cpp - Probes to Signals ----------------------------===//
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 file defines the ProbesToSignals pass. This pass replaces probes with
10 // signals of the same type. This is not considered a lowering but a
11 // behavior-changing transformation that may break ABI compatibility anywhere
12 // probes are used relevant to ABI.
13 //
14 // Pre-requisites for complete conversion:
15 // * LowerOpenAggs
16 // - Simplifies this pass, Probes are always separate.
17 // * ExpandWhens
18 // - ref.define is "static single connect", and FIRRTL does not have
19 // an equivalent for hardware connections. As a result, probes sent out
20 // from under a "when" cannot be represented currently.
21 //
22 // Suggested:
23 // * Inference passes, especially width inference. Probes infer slightly
24 // differently than non-probes do (must have same width along the chain).
25 //
26 // Colored probes are not supported.
27 // Specialize layers on or off to remove colored probes first.
28 //
29 // Debug ports on FIRRTL memories are not currently supported,
30 // but CHIRRTL debug ports are handled.
31 //
32 //===----------------------------------------------------------------------===//
33 
40 #include "circt/Support/Debug.h"
41 #include "mlir/IR/Threading.h"
42 #include "mlir/Transforms/DialectConversion.h"
43 #include "llvm/ADT/STLExtras.h"
44 
45 #define DEBUG_TYPE "firrtl-probes-to-signals"
46 
47 namespace circt {
48 namespace firrtl {
49 #define GEN_PASS_DEF_PROBESTOSIGNALS
50 #include "circt/Dialect/FIRRTL/Passes.h.inc"
51 } // namespace firrtl
52 } // namespace circt
53 
54 using namespace circt;
55 using namespace firrtl;
56 
57 //===----------------------------------------------------------------------===//
58 // Probes to Signals
59 //===----------------------------------------------------------------------===//
60 
61 namespace {
62 
63 class ProbeVisitor : public FIRRTLVisitor<ProbeVisitor, LogicalResult> {
64 public:
65  ProbeVisitor(hw::InnerRefNamespace &irn) : irn(irn) {}
66 
67  /// Entrypoint.
68  LogicalResult visit(FModuleLike mod);
69 
73 
74  //===--------------------------------------------------------------------===//
75  // Type conversion
76  //===--------------------------------------------------------------------===//
77 
78  /// Return the converted type, null if same, failure on error.
79  static FailureOr<Type> convertType(Type type, Location loc) {
80  auto err = [type, loc](const Twine &message) {
81  return mlir::emitError(loc, message) << ", cannot convert type " << type;
82  };
83  if (isa<OpenBundleType, OpenVectorType>(type))
84  return err("open aggregates not supported");
85 
86  auto refType = dyn_cast<RefType>(type);
87  if (!refType)
88  return Type();
89 
90  if (refType.getLayer())
91  return err("layer-colored probes not supported");
92 
93  // Otherwise, this maps to the probed type.
94  return refType.getType();
95  }
96 
97  /// Return "target" type, or failure on error.
98  static FailureOr<Type> mapType(Type type, Location loc) {
99  auto newType = convertType(type, loc);
100  if (failed(newType))
101  return failure();
102  return *newType ? *newType : type;
103  }
104 
105  /// Map a range of types, return if changes needed.
106  template <typename R>
107  static FailureOr<bool> mapRange(R &&range, Location loc,
108  SmallVectorImpl<Type> &newTypes) {
109  newTypes.reserve(llvm::size(range));
110 
111  bool anyConverted = false;
112  for (auto type : range) {
113  auto conv = mapType(type, loc);
114  if (failed(conv))
115  return failure();
116  newTypes.emplace_back(*conv);
117  anyConverted |= *conv != type;
118  }
119  return anyConverted;
120  }
121 
122  // CHIRRTL
123  LogicalResult visitMemoryDebugPortOp(chirrtl::MemoryDebugPortOp op);
124 
125  // Visitors
126 
127  LogicalResult visitInvalidOp(Operation *op) {
128  if (auto dbgPortOp = dyn_cast<chirrtl::MemoryDebugPortOp>(op))
129  return visitMemoryDebugPortOp(dbgPortOp);
130 
131  return visitUnhandledOp(op);
132  }
133  LogicalResult visitUnhandledOp(Operation *op);
134 
135  /// Check declarations specifically before forwarding to unhandled.
136  LogicalResult visitUnhandledDecl(Operation *op) {
137  // Check for and handle active forceable declarations.
138  if (auto fop = dyn_cast<Forceable>(op); fop && fop.isForceable())
139  return visitActiveForceableDecl(fop);
140  return visitUnhandledOp(op);
141  }
142 
143  // Declarations
144 
145  LogicalResult visitDecl(MemOp op);
146  LogicalResult visitDecl(WireOp op);
147  LogicalResult visitActiveForceableDecl(Forceable fop);
148 
149  LogicalResult visitInstanceLike(Operation *op);
150  LogicalResult visitDecl(InstanceOp op) { return visitInstanceLike(op); }
151  LogicalResult visitDecl(InstanceChoiceOp op) { return visitInstanceLike(op); }
152 
153  // Probe operations.
154 
155  LogicalResult visitExpr(RWProbeOp op);
156  LogicalResult visitExpr(RefCastOp op);
157  LogicalResult visitExpr(RefResolveOp op);
158  LogicalResult visitExpr(RefSendOp op);
159  LogicalResult visitExpr(RefSubOp op);
160 
161  LogicalResult visitStmt(RefDefineOp op);
162 
163  // Force and release operations: reject as unsupported.
164  LogicalResult visitStmt(RefForceOp op) {
165  return op.emitError("force not supported");
166  }
167  LogicalResult visitStmt(RefForceInitialOp op) {
168  return op.emitError("force_initial not supported");
169  }
170  LogicalResult visitStmt(RefReleaseOp op) {
171  return op.emitError("release not supported");
172  }
173  LogicalResult visitStmt(RefReleaseInitialOp op) {
174  return op.emitError("release_initial not supported");
175  }
176 
177 private:
178  /// Map from probe-typed Value's to their non-probe equivalent.
179  DenseMap<Value, Value> probeToHWMap;
180 
181  /// Forceable operations to demote.
182  SmallVector<Forceable> forceables;
183 
184  /// Operations to delete.
185  SmallVector<Operation *> toDelete;
186 
187  /// Read-only copy of inner-ref namespace for resolving inner refs.
188  hw::InnerRefNamespace &irn;
189 };
190 
191 } // end namespace
192 
193 //===----------------------------------------------------------------------===//
194 // Visitor: FModuleLike
195 //===----------------------------------------------------------------------===//
196 
197 static Block *getBodyBlock(FModuleLike mod) {
198  // Safety check for below, presently all modules have a region.
199  assert(mod->getNumRegions() == 1);
200  auto &blocks = mod->getRegion(0).getBlocks();
201  return !blocks.empty() ? &blocks.front() : nullptr;
202 }
203 
204 /// Visit a module, converting its ports and internals to use hardware signals
205 /// instead of probes.
206 LogicalResult ProbeVisitor::visit(FModuleLike mod) {
207  // If module has strings describing XMR suffixes for its ports, reject.
208  if (auto internalPaths = mod->getAttrOfType<ArrayAttr>("internalPaths"))
209  return mod.emitError("cannot convert module with internal path");
210 
211  // Ports -> new ports without probe-ness.
212  // For all probe ports, insert non-probe duplex values to use
213  // as their replacement while rewriting. Only if has body.
214  SmallVector<std::pair<size_t, WireOp>> wires;
215 
216  auto portTypes = mod.getPortTypes();
217  auto portLocs = mod.getPortLocationsAttr().getAsRange<Location>();
218  SmallVector<Attribute> newPortTypes;
219 
220  wires.reserve(portTypes.size());
221  newPortTypes.reserve(portTypes.size());
222  auto *block = getBodyBlock(mod);
223  bool portsToChange = false;
224  for (auto [idx, typeAttr, loc] : llvm::enumerate(portTypes, portLocs)) {
225  auto type = cast<TypeAttr>(typeAttr);
226  auto conv = convertType(type.getValue(), loc);
227  if (failed(conv))
228  return failure();
229  auto newType = *conv;
230 
231  if (newType) {
232  portsToChange = true;
233  newPortTypes.push_back(TypeAttr::get(newType));
234  if (block) {
235  auto builder = OpBuilder::atBlockBegin(block);
236  wires.emplace_back(idx, builder.create<WireOp>(loc, newType));
237  probeToHWMap[block->getArgument(idx)] = wires.back().second.getData();
238  }
239  } else
240  newPortTypes.push_back(type);
241  }
242 
243  // Update body, if present.
244  if (block &&
245  block
246  ->walk<mlir::WalkOrder::PreOrder>(
247  [&](Operation *op) -> WalkResult { return dispatchVisitor(op); })
248  .wasInterrupted())
249  return failure();
250 
251  // Update signature and argument types.
252  if (portsToChange) {
253  mod.setPortTypesAttr(ArrayAttr::get(mod->getContext(), newPortTypes));
254 
255  if (block) {
256  // We may also need to update the types on the block arguments.
257  for (auto [arg, typeAttr] :
258  llvm::zip_equal(block->getArguments(), newPortTypes))
259  arg.setType(cast<TypeAttr>(typeAttr).getValue());
260 
261  // Drop the port stand-ins and RAUW to the block arguments.
262  for (auto [idx, wire] : wires) {
263  auto arg = block->getArgument(idx);
264  wire.getData().replaceAllUsesWith(arg);
265  wire.erase();
266  }
267  }
268  }
269 
270  // Delete operations that were converted.
271  for (auto *op : llvm::reverse(toDelete))
272  op->erase();
273 
274  // Demote forceable's.
275  for (auto fop : forceables)
277 
278  return success();
279 }
280 
281 //===----------------------------------------------------------------------===//
282 // Visitor: Unhandled
283 //===----------------------------------------------------------------------===//
284 
285 LogicalResult ProbeVisitor::visitUnhandledOp(Operation *op) {
286  auto checkType = [&](auto type) -> bool {
287  // Return if conversion needed (or if error).
288  auto newType = convertType(type, op->getLoc());
289  if (failed(newType))
290  return true;
291  if (!*newType)
292  return false;
293 
294  // Type found that needs to be converted, diagnose.
295  op->emitError("unhandled operation needs conversion of type ")
296  << type << " to " << *newType;
297  return true;
298  };
299 
300  return success(llvm::none_of(op->getOperandTypes(), checkType) &&
301  llvm::none_of(op->getResultTypes(), checkType));
302 }
303 
304 //===----------------------------------------------------------------------===//
305 // Visitor: CHIRRTL
306 //===----------------------------------------------------------------------===//
307 LogicalResult
308 ProbeVisitor::visitMemoryDebugPortOp(chirrtl::MemoryDebugPortOp op) {
309  auto conv = convertType(op.getResult().getType(), op.getLoc());
310  if (failed(conv))
311  return failure();
312  auto type = *conv;
313  assert(type);
314 
315  auto vectype = type_cast<FVectorType>(type);
316 
317  // Just assert the chirrtl memory IR has the expected structure,
318  // if it didn't many things break.
319  // Must be defined in same module, tapped memory must be comb mem.
320  auto mem = op.getMemory().getDefiningOp<chirrtl::CombMemOp>();
321  assert(mem);
322 
323  // The following is adapted from LowerAnnotations.
324  Value clock;
325  for (auto *portOp : mem.getResult().getUsers()) {
326  for (auto result : portOp->getResults()) {
327  for (auto *user : result.getUsers()) {
328  auto accessOp = dyn_cast<chirrtl::MemoryPortAccessOp>(user);
329  if (!accessOp)
330  continue;
331  auto newClock = accessOp.getClock();
332  if (clock && clock != newClock)
333  return mem.emitOpError(
334  "has different clocks on different ports (this is ambiguous "
335  "when compiling without reference types)");
336  clock = newClock;
337  }
338  }
339  }
340  if (!clock)
341  return mem->emitOpError(
342  "does not have an access port to determine a clock connection (this "
343  "is necessary when compiling without reference types)");
344 
345  // Add one port per memory address.
346  SmallVector<Value> data;
347  ImplicitLocOpBuilder builder(op.getLoc(), op);
348 
349  // Insert new ports as late as possible (end of block containing the memory).
350  // This is necessary to preserve ordering of existing ports.
351  builder.setInsertionPointToEnd(mem->getBlock());
352  Type uintType = builder.getType<UIntType>();
353  for (uint64_t i = 0, e = mem.getType().getNumElements(); i != e; ++i) {
354  auto port = builder.create<chirrtl::MemoryPortOp>(
355  mem.getType().getElementType(),
356  chirrtl::CMemoryPortType::get(builder.getContext()), mem.getResult(),
357  MemDirAttr::Read, builder.getStringAttr("memTap_" + Twine(i)),
358  builder.getArrayAttr({}));
359  builder.create<chirrtl::MemoryPortAccessOp>(
360  port.getPort(),
361  builder.create<ConstantOp>(uintType, APSInt::getUnsigned(i)), clock);
362  data.push_back(port.getData());
363  }
364 
365  // Package up all the reads into a vector.
366  assert(vectype == FVectorType::get(mem.getType().getElementType(),
367  mem.getType().getNumElements()));
368  auto vecData = builder.create<VectorCreateOp>(vectype, data);
369 
370  // While the new ports are added as late as possible, the debug port
371  // operation we're replacing likely has users and those are before
372  // the new ports. Add a wire at a point we know dominates this operation
373  // and the new port access operations added above. This will be used for
374  // the existing users of the debug port.
375  builder.setInsertionPoint(mem);
376  auto wire = builder.create<WireOp>(vectype);
377  builder.setInsertionPointToEnd(mem->getBlock());
378  emitConnect(builder, wire.getData(), vecData);
379  probeToHWMap[op.getResult()] = wire.getData();
380  toDelete.push_back(op);
381  return success();
382 }
383 
384 //===----------------------------------------------------------------------===//
385 // Visitor: Declarations
386 //===----------------------------------------------------------------------===//
387 
388 LogicalResult ProbeVisitor::visitDecl(MemOp op) {
389  // Scan for debug ports. These are not supported presently, diagnose.
390  SmallVector<Type> newTypes;
391  auto needsConv = mapRange(op->getResultTypes(), op->getLoc(), newTypes);
392  if (failed(needsConv))
393  return failure();
394  if (!*needsConv)
395  return success();
396 
397  return op.emitError("memory has unsupported debug port (memtap)");
398 }
399 
400 LogicalResult ProbeVisitor::visitDecl(WireOp op) {
401  if (op.isForceable())
402  return visitActiveForceableDecl(op);
403 
404  auto conv = convertType(op.getDataRaw().getType(), op.getLoc());
405  if (failed(conv))
406  return failure();
407  auto type = *conv;
408  if (!type) // No conversion needed.
409  return success();
410 
411  // New Wire of converted type.
412  ImplicitLocOpBuilder builder(op.getLoc(), op);
413  auto cloned = cast<WireOp>(builder.clone(*op));
414  cloned->getOpResults().front().setType(type);
415  probeToHWMap[op.getDataRaw()] = cloned.getData();
416  toDelete.push_back(op);
417  return success();
418 }
419 
420 LogicalResult ProbeVisitor::visitActiveForceableDecl(Forceable fop) {
421  assert(fop.isForceable() && "must be called on active forceables");
422  // Map rw ref result to normal result.
423  auto data = fop.getData();
424  auto conv = mapType(fop.getDataRef().getType(), fop.getLoc());
425  if (failed(conv))
426  return failure();
427  auto newType = *conv;
428  forceables.push_back(fop);
429 
430  assert(newType == data.getType().getPassiveType());
431  if (newType != data.getType()) {
432  ImplicitLocOpBuilder builder(fop.getLoc(), fop);
433  builder.setInsertionPointAfterValue(data);
434  auto wire = builder.create<WireOp>(newType);
435  emitConnect(builder, wire.getData(), data);
436  data = wire.getData();
437  }
438  probeToHWMap[fop.getDataRef()] = data;
439  return success();
440 }
441 
442 LogicalResult ProbeVisitor::visitInstanceLike(Operation *op) {
443  SmallVector<Type> newTypes;
444  auto needsConv = mapRange(op->getResultTypes(), op->getLoc(), newTypes);
445  if (failed(needsConv))
446  return failure();
447  if (!*needsConv)
448  return success();
449 
450  // New instance with converted types.
451  // Move users of unconverted results to the new operation.
452  ImplicitLocOpBuilder builder(op->getLoc(), op);
453  auto *newInst = builder.clone(*op);
454  for (auto [oldResult, newResult, newType] :
455  llvm::zip_equal(op->getOpResults(), newInst->getOpResults(), newTypes)) {
456  if (newType == oldResult.getType()) {
457  oldResult.replaceAllUsesWith(newResult);
458  continue;
459  }
460 
461  newResult.setType(newType);
462  probeToHWMap[oldResult] = newResult;
463  }
464 
465  toDelete.push_back(op);
466  return success();
467 }
468 
469 //===----------------------------------------------------------------------===//
470 // Visitor: Probe operations
471 //===----------------------------------------------------------------------===//
472 
473 LogicalResult ProbeVisitor::visitStmt(RefDefineOp op) {
474  // ref.define x, y -> connect map(x), map(y)
475  // Be mindful of connect semantics when considering
476  // placement.
477 
478  auto newDest = probeToHWMap.at(op.getDest());
479  auto newSrc = probeToHWMap.at(op.getSrc());
480 
481  // Source must be ancestor of destination block for a connect
482  // to behave the same (generally).
483  assert(!isa<BlockArgument>(newDest));
484  auto *destDefiningOp = newDest.getDefiningOp();
485  assert(destDefiningOp);
486  if (!newSrc.getParentBlock()->findAncestorOpInBlock(*destDefiningOp)) {
487  // Conditional or sending out of a layer...
488  auto diag = op.emitError("unable to convert to equivalent connect");
489  diag.attachNote(op.getDest().getLoc()) << "destination here";
490  diag.attachNote(op.getSrc().getLoc()) << "source here";
491  return diag;
492  }
493 
494  auto *destBlock = newDest.getParentBlock();
495  auto builder = ImplicitLocOpBuilder::atBlockEnd(op.getLoc(), destBlock);
496  emitConnect(builder, newDest, newSrc);
497  toDelete.push_back(op);
498  return success();
499 }
500 
501 LogicalResult ProbeVisitor::visitExpr(RWProbeOp op) {
502  // Handle similar to ref.send but lookup the target
503  // and materialize a value for it (indexing).
504  auto conv = mapType(op.getType(), op.getLoc());
505  if (failed(conv))
506  return failure();
507  auto newType = *conv;
508  toDelete.push_back(op);
509 
510  auto ist = irn.lookup(op.getTarget());
511  assert(ist);
512  auto ref = getFieldRefForTarget(ist);
513 
514  ImplicitLocOpBuilder builder(op.getLoc(), op);
515  builder.setInsertionPointAfterValue(ref.getValue());
516  auto data = getValueByFieldID(builder, ref.getValue(), ref.getFieldID());
517  assert(cast<FIRRTLBaseType>(data.getType()).getPassiveType() ==
518  op.getType().getType());
519  if (newType != data.getType()) {
520  auto wire = builder.create<WireOp>(newType);
521  emitConnect(builder, wire.getData(), data);
522  data = wire.getData();
523  }
524  probeToHWMap[op.getResult()] = data;
525  return success();
526 }
527 
528 LogicalResult ProbeVisitor::visitExpr(RefCastOp op) {
529  auto input = probeToHWMap.at(op.getInput());
530  // Insert wire of the new type, and connect to it.
531 
532  // y = ref.cast x : probe<t1> -> probe<t2>
533  // ->
534  // w = firrtl.wire : t2
535  // emitConnect(w : t2, map(x): t1)
536 
537  auto conv = mapType(op.getResult().getType(), op.getLoc());
538  if (failed(conv))
539  return failure();
540  auto newType = *conv;
541 
542  ImplicitLocOpBuilder builder(op.getLoc(), op);
543  builder.setInsertionPointAfterValue(input);
544  auto wire = builder.create<WireOp>(newType);
545  emitConnect(builder, wire.getData(), input);
546  probeToHWMap[op.getResult()] = wire.getData();
547  toDelete.push_back(op);
548  return success();
549 }
550 
551 LogicalResult ProbeVisitor::visitExpr(RefSendOp op) {
552  auto conv = mapType(op.getResult().getType(), op.getLoc());
553  if (failed(conv))
554  return failure();
555  auto newType = *conv;
556  toDelete.push_back(op);
557 
558  // If the mapped type is same as input, just use that.
559  if (newType == op.getBase().getType()) {
560  probeToHWMap[op.getResult()] = op.getBase();
561  return success();
562  }
563 
564  // Otherwise, need to make this the probed type (passive).
565  // Insert wire of the new type, and connect to it.
566  assert(newType == op.getBase().getType().getPassiveType());
567  ImplicitLocOpBuilder builder(op.getLoc(), op);
568  builder.setInsertionPointAfterValue(op.getBase());
569  auto wire = builder.create<WireOp>(newType);
570  emitConnect(builder, wire.getData(), op.getBase());
571  probeToHWMap[op.getResult()] = wire.getData();
572  return success();
573 }
574 
575 LogicalResult ProbeVisitor::visitExpr(RefResolveOp op) {
576  // ref.resolve x -> map(x)
577  auto val = probeToHWMap.at(op.getRef());
578  op.replaceAllUsesWith(val);
579  toDelete.push_back(op);
580  return success();
581 }
582 
583 LogicalResult ProbeVisitor::visitExpr(RefSubOp op) {
584  // ref.sub x, fieldid -> index(map(x), fieldid)
585  auto val = probeToHWMap.at(op.getInput());
586  assert(val);
587  ImplicitLocOpBuilder builder(op.getLoc(), op);
588  builder.setInsertionPointAfterValue(op.getInput());
589  auto newVal =
590  getValueByFieldID(builder, val, op.getAccessedField().getFieldID());
591  probeToHWMap[op.getResult()] = newVal;
592  toDelete.push_back(op);
593  return success();
594 }
595 
596 //===----------------------------------------------------------------------===//
597 // Pass Infrastructure
598 //===----------------------------------------------------------------------===//
599 
600 namespace {
601 struct ProbesToSignalsPass
602  : public circt::firrtl::impl::ProbesToSignalsBase<ProbesToSignalsPass> {
603  ProbesToSignalsPass() = default;
604  void runOnOperation() override;
605 };
606 } // end anonymous namespace
607 
608 void ProbesToSignalsPass::runOnOperation() {
609  LLVM_DEBUG(debugPassHeader(this) << "\n");
610 
611  SmallVector<Operation *, 0> ops(getOperation().getOps<FModuleLike>());
612 
613  hw::InnerRefNamespace irn{getAnalysis<SymbolTable>(),
614  getAnalysis<hw::InnerSymbolTableCollection>()};
615 
616  auto result = failableParallelForEach(&getContext(), ops, [&](Operation *op) {
617  ProbeVisitor visitor(irn);
618  return visitor.visit(cast<FModuleLike>(op));
619  });
620 
621  if (result.failed())
622  signalPassFailure();
623 }
624 
625 /// This is the pass constructor.
626 std::unique_ptr<mlir::Pass> circt::firrtl::createProbesToSignalsPass() {
627  return std::make_unique<ProbesToSignalsPass>();
628 }
assert(baseType &&"element must be base type")
static FIRRTLBaseType convertType(FIRRTLBaseType type)
Returns null type if no conversion is needed.
Definition: DropConst.cpp:32
static Block * getBodyBlock(FModuleLike mod)
FIRRTLVisitor allows you to visit all of the expr/stmt/decls with one class declaration.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
Forceable replaceWithNewForceability(Forceable op, bool forceable, ::mlir::PatternRewriter *rewriter=nullptr)
Replace a Forceable op with equivalent, changing whether forceable.
FieldRef getFieldRefForTarget(const hw::InnerSymTarget &ist)
Get FieldRef pointing to the specified inner symbol target, which must be valid.
Value getValueByFieldID(ImplicitLocOpBuilder builder, Value value, unsigned fieldID)
This gets the value targeted by a field id.
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
Definition: FIRRTLUtils.cpp:25
std::unique_ptr< mlir::Pass > createProbesToSignalsPass()
This is the pass constructor.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
llvm::raw_ostream & debugPassHeader(const mlir::Pass *pass, int width=80)
Write a boilerplate header for a pass to the debug stream.
Definition: Debug.cpp:31