CIRCT  20.0.0git
CalyxLoweringUtils.h
Go to the documentation of this file.
1 //===- CalyxLoweringUtils.h - Calyx lowering utility methods ----*- C++ -*-===//
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 header file defines various lowering utility methods for converting to
10 // and from Calyx programs.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #ifndef CIRCT_DIALECT_CALYX_CALYXLOWERINGUTILS_H
15 #define CIRCT_DIALECT_CALYX_CALYXLOWERINGUTILS_H
16 
20 #include "circt/Support/LLVM.h"
21 #include "mlir/Dialect/Arith/IR/Arith.h"
22 #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
23 #include "mlir/Dialect/Func/IR/FuncOps.h"
24 #include "mlir/Dialect/MemRef/IR/MemRef.h"
25 #include "mlir/Dialect/SCF/IR/SCF.h"
26 #include "mlir/IR/AsmState.h"
27 #include "mlir/IR/PatternMatch.h"
28 #include "llvm/ADT/SmallPtrSet.h"
29 #include "llvm/ADT/TypeSwitch.h"
30 
31 #include <variant>
32 
33 namespace circt {
34 namespace calyx {
35 
36 void appendPortsForExternalMemref(PatternRewriter &rewriter, StringRef memName,
37  Value memref, unsigned memoryID,
38  SmallVectorImpl<calyx::PortInfo> &inPorts,
39  SmallVectorImpl<calyx::PortInfo> &outPorts);
40 
41 // Walks the control of this component, and appends source information for leaf
42 // nodes. It also appends a position attribute that connects the source location
43 // metadata to the corresponding control operation.
44 WalkResult
45 getCiderSourceLocationMetadata(calyx::ComponentOp component,
46  SmallVectorImpl<Attribute> &sourceLocations);
47 
48 // Tries to match a constant value defined by op. If the match was
49 // successful, returns true and binds the constant to 'value'. If unsuccessful,
50 // the value is unmodified.
51 bool matchConstantOp(Operation *op, APInt &value);
52 
53 // Returns true if there exists only a single memref::LoadOp which loads from
54 // the memory referenced by loadOp.
55 bool singleLoadFromMemory(Value memoryReference);
56 
57 // Returns true if there are no memref::StoreOp uses with the referenced
58 // memory.
59 bool noStoresToMemory(Value memoryReference);
60 
61 // Get the index'th output port of compOp.
62 Value getComponentOutput(calyx::ComponentOp compOp, unsigned outPortIdx);
63 
64 // If the provided type is an index type, converts it to i32, else, returns the
65 // unmodified type.
66 Type convIndexType(OpBuilder &builder, Type type);
67 
68 // Creates a new calyx::CombGroupOp or calyx::GroupOp group within compOp.
69 template <typename TGroup>
70 TGroup createGroup(OpBuilder &builder, calyx::ComponentOp compOp, Location loc,
71  Twine uniqueName) {
72  mlir::IRRewriter::InsertionGuard guard(builder);
73  builder.setInsertionPointToEnd(compOp.getWiresOp().getBodyBlock());
74  return builder.create<TGroup>(loc, uniqueName.str());
75 }
76 
77 /// Creates register assignment operations within the provided groupOp.
78 /// The component operation will house the constants.
79 void buildAssignmentsForRegisterWrite(OpBuilder &builder,
80  calyx::GroupOp groupOp,
81  calyx::ComponentOp componentOp,
82  calyx::RegisterOp &reg, Value inputValue);
83 
84 // A structure representing a set of ports which act as a memory interface for
85 // external memories.
87  std::string memName;
88  std::optional<Value> readData;
89  std::optional<Value> readOrContentEn;
90  std::optional<Value> writeData;
91  std::optional<Value> writeEn;
92  std::optional<Value> done;
93  SmallVector<Value> addrPorts;
94  std::optional<bool> isContentEn;
95 };
96 
97 // Represents the interface of memory in Calyx. The various lowering passes
98 // are agnostic wrt. whether working with a calyx::MemoryOp (internally
99 // allocated memory) or MemoryPortsImpl (external memory).
102  explicit MemoryInterface(const MemoryPortsImpl &ports);
103  explicit MemoryInterface(calyx::MemoryOp memOp);
104  explicit MemoryInterface(calyx::SeqMemoryOp memOp);
105 
106  // Getter methods for each memory interface port.
107  std::string memName();
108  Value readData();
109  Value readEn();
110  Value contentEn();
111  Value writeData();
112  Value writeEn();
113  Value done();
114  std::optional<Value> readDataOpt();
115  std::optional<Value> readEnOpt();
116  std::optional<Value> contentEnOpt();
117  std::optional<Value> writeDataOpt();
118  std::optional<Value> writeEnOpt();
119  std::optional<Value> doneOpt();
120  ValueRange addrPorts();
121 
122 private:
123  std::variant<calyx::MemoryOp, calyx::SeqMemoryOp, MemoryPortsImpl> impl;
124 };
125 
126 // A common interface for any loop operation that needs to be lowered to Calyx.
128 public:
130 
131  // Returns the arguments to this loop operation.
132  virtual Block::BlockArgListType getBodyArgs() = 0;
133 
134  // Returns body of this loop operation.
135  virtual Block *getBodyBlock() = 0;
136 
137  // Returns the location of the loop interface.
138  virtual Location getLoc() = 0;
139 
140  // Returns the number of iterations the loop will conduct if known.
141  virtual std::optional<int64_t> getBound() = 0;
142 };
143 
144 // A common interface for loop operations that have conditionals (e.g., while
145 // loops) that need to be lowered to Calyx.
147 public:
148  // Returns the Block in which the condition exists.
149  virtual Block *getConditionBlock() = 0;
150 
151  // Returns the condition as a Value.
152  virtual Value getConditionValue() = 0;
153 };
154 
155 // Provides an interface for the control flow `while` operation across different
156 // dialects.
157 template <typename T>
159  static_assert(std::is_convertible_v<T, Operation *>);
160 
161 public:
162  explicit WhileOpInterface(T op) : impl(op) {}
163  explicit WhileOpInterface(Operation *op) : impl(dyn_cast_or_null<T>(op)) {}
164 
165  // Returns the operation.
166  T getOperation() { return impl; }
167 
168  // Returns the source location of the operation.
169  Location getLoc() override { return impl->getLoc(); }
170 
171 private:
172  T impl;
173 };
174 
175 // Provides an interface for the control flow `forOp` operation across different
176 // dialects.
177 template <typename T>
179  static_assert(std::is_convertible_v<T, Operation *>);
180 
181 public:
182  explicit RepeatOpInterface(T op) : impl(op) {}
183  explicit RepeatOpInterface(Operation *op) : impl(dyn_cast_or_null<T>(op)) {}
184 
185  // Returns the operation.
186  T getOperation() { return impl; }
187 
188  // Returns the source location of the operation.
189  Location getLoc() override { return impl->getLoc(); }
190 
191 private:
192  T impl;
193 };
194 
195 /// Holds common utilities used for scheduling when lowering to Calyx.
196 template <typename T>
198 public:
199  /// Register 'scheduleable' as being generated through lowering 'block'.
200  ///
201  /// TODO(mortbopet): Add a post-insertion check to ensure that the use-def
202  /// ordering invariant holds for the groups. When the control schedule is
203  /// generated, scheduleables within a block are emitted sequentially based on
204  /// the order that this function was called during conversion.
205  ///
206  /// Currently, we assume this to always be true. Walking the FuncOp IR implies
207  /// sequential iteration over operations within basic blocks.
208  void addBlockScheduleable(mlir::Block *block, const T &scheduleable) {
209  blockScheduleables[block].push_back(scheduleable);
210  }
211 
212  /// Returns an ordered list of schedulables which registered themselves to be
213  /// a result of lowering the block in the source program. The list order
214  /// follows def-use chains between the scheduleables in the block.
215  SmallVector<T> getBlockScheduleables(mlir::Block *block) {
216  if (auto it = blockScheduleables.find(block);
217  it != blockScheduleables.end())
218  return it->second;
219  /// In cases of a block resulting in purely combinational logic, no
220  /// scheduleables registered themselves with the block.
221  return {};
222  }
223 
224 private:
225  /// BlockScheduleables is a list of scheduleables that should be
226  /// sequentially executed when executing the associated basic block.
227  DenseMap<mlir::Block *, SmallVector<T>> blockScheduleables;
228 };
229 
230 //===----------------------------------------------------------------------===//
231 // Lowering state classes
232 //===----------------------------------------------------------------------===//
233 
234 // Handles state during the lowering of a loop. It will be used for
235 // several lowering patterns.
236 template <typename Loop>
238  static_assert(std::is_base_of_v<BasicLoopInterface, Loop>);
239 
240 public:
242 
243  /// Register reg as being the idx'th iter_args register for 'op'.
244  void addLoopIterReg(Loop op, calyx::RegisterOp reg, unsigned idx) {
245  assert(loopIterRegs[op.getOperation()].count(idx) == 0 &&
246  "A register was already registered for the given loop iter_arg "
247  "index");
248  assert(idx < op.getBodyArgs().size());
249  loopIterRegs[op.getOperation()][idx] = reg;
250  }
251 
252  /// Return a mapping of block argument indices to block argument.
253  calyx::RegisterOp getLoopIterReg(Loop op, unsigned idx) {
254  auto iterRegs = getLoopIterRegs(op);
255  auto it = iterRegs.find(idx);
256  assert(it != iterRegs.end() &&
257  "No iter arg register set for the provided index");
258  return it->second;
259  }
260 
261  /// Return a mapping of block argument indices to block argument.
262  const DenseMap<unsigned, calyx::RegisterOp> &getLoopIterRegs(Loop op) {
263  return loopIterRegs[op.getOperation()];
264  }
265 
266  /// Registers grp to be the loop latch group of `op`.
267  void setLoopLatchGroup(Loop op, calyx::GroupOp group) {
268  Operation *operation = op.getOperation();
269  assert(loopLatchGroups.count(operation) == 0 &&
270  "A latch group was already set for this loopOp");
271  loopLatchGroups[operation] = group;
272  }
273 
274  /// Retrieve the loop latch group registered for `op`.
275  calyx::GroupOp getLoopLatchGroup(Loop op) {
276  auto it = loopLatchGroups.find(op.getOperation());
277  assert(it != loopLatchGroups.end() &&
278  "No loop latch group was set for this loopOp");
279  return it->second;
280  }
281 
282  /// Registers groups to be the loop init groups of `op`.
283  void setLoopInitGroups(Loop op, SmallVector<calyx::GroupOp> groups) {
284  Operation *operation = op.getOperation();
285  assert(loopInitGroups.count(operation) == 0 &&
286  "Init group(s) was already set for this loopOp");
287  loopInitGroups[operation] = std::move(groups);
288  }
289 
290  /// Retrieve the loop init groups registered for `op`.
291  SmallVector<calyx::GroupOp> getLoopInitGroups(Loop op) {
292  auto it = loopInitGroups.find(op.getOperation());
293  assert(it != loopInitGroups.end() &&
294  "No init group(s) was set for this loopOp");
295  return it->second;
296  }
297 
298  /// Creates a new group that assigns the 'ops' values to the iter arg
299  /// registers of the loop operation.
300  calyx::GroupOp buildLoopIterArgAssignments(OpBuilder &builder, Loop op,
301  calyx::ComponentOp componentOp,
302  Twine uniqueSuffix,
303  MutableArrayRef<OpOperand> ops) {
304  /// Pass iteration arguments through registers. This follows closely
305  /// to what is done for branch ops.
306  std::string groupName = "assign_" + uniqueSuffix.str();
307  auto groupOp = calyx::createGroup<calyx::GroupOp>(builder, componentOp,
308  op.getLoc(), groupName);
309  /// Create register assignment for each iter_arg. a calyx::GroupDone signal
310  /// is created for each register. These will be &'ed together in
311  /// MultipleGroupDonePattern.
312  for (OpOperand &arg : ops) {
313  auto reg = getLoopIterReg(op, arg.getOperandNumber());
314  buildAssignmentsForRegisterWrite(builder, groupOp, componentOp, reg,
315  arg.get());
316  }
317  return groupOp;
318  }
319 
320 private:
321  /// A mapping from loop ops to iteration argument registers.
322  DenseMap<Operation *, DenseMap<unsigned, calyx::RegisterOp>> loopIterRegs;
323 
324  /// A loop latch group is a group that should be sequentially executed when
325  /// finishing a loop body. The execution of this group will write the
326  /// yield'ed loop body values to the iteration argument registers.
327  DenseMap<Operation *, calyx::GroupOp> loopLatchGroups;
328 
329  /// Loop init groups are to be scheduled before the while operation. These
330  /// groups should set the initial value(s) of the loop init_args register(s).
331  DenseMap<Operation *, SmallVector<calyx::GroupOp>> loopInitGroups;
332 };
333 
334 // Handles state during the lowering of a Calyx component. This provides common
335 // tools for converting to the Calyx ComponentOp.
337 public:
338  ComponentLoweringStateInterface(calyx::ComponentOp component);
339 
341 
342  /// Returns the calyx::ComponentOp associated with this lowering state.
343  calyx::ComponentOp getComponentOp();
344 
345  /// Register reg as being the idx'th argument register for block. This is
346  /// necessary for the `BuildBBReg` pass.
347  void addBlockArgReg(Block *block, calyx::RegisterOp reg, unsigned idx);
348 
349  /// Return a mapping of block argument indices to block argument registers.
350  /// This is necessary for the `BuildBBReg` pass.
351  const DenseMap<unsigned, calyx::RegisterOp> &getBlockArgRegs(Block *block);
352 
353  /// Register 'grp' as a group which performs block argument
354  /// register transfer when transitioning from basic block 'from' to 'to'.
355  void addBlockArgGroup(Block *from, Block *to, calyx::GroupOp grp);
356 
357  /// Returns a list of groups to be evaluated to perform the block argument
358  /// register assignments when transitioning from basic block 'from' to 'to'.
359  ArrayRef<calyx::GroupOp> getBlockArgGroups(Block *from, Block *to);
360 
361  /// Returns a unique name within compOp with the provided prefix.
362  std::string getUniqueName(StringRef prefix);
363 
364  /// Returns a unique name associated with a specific operation.
365  StringRef getUniqueName(Operation *op);
366 
367  /// Registers a unique name for a given operation using a provided prefix.
368  void setUniqueName(Operation *op, StringRef prefix);
369 
370  /// Register value v as being evaluated when scheduling group.
371  void registerEvaluatingGroup(Value v, calyx::GroupInterface group);
372 
373  /// Register reg as being the idx'th return value register.
374  void addReturnReg(calyx::RegisterOp reg, unsigned idx);
375 
376  /// Returns the idx'th return value register.
377  calyx::RegisterOp getReturnReg(unsigned idx);
378 
379  /// Registers a memory interface as being associated with a memory identified
380  /// by 'memref'.
381  void registerMemoryInterface(Value memref,
382  const calyx::MemoryInterface &memoryInterface);
383 
384  /// Returns the memory interface registered for the given memref.
386 
387  /// If v is an input to any memory registered within this component, returns
388  /// the memory. If not, returns null.
389  std::optional<calyx::MemoryInterface> isInputPortOfMemory(Value v);
390 
391  /// Assign a mapping between the source funcOp result indices and the
392  /// corresponding output port indices of this componentOp.
393  void setFuncOpResultMapping(const DenseMap<unsigned, unsigned> &mapping);
394 
395  /// Get the output port index of this component for which the funcReturnIdx of
396  /// the original function maps to.
397  unsigned getFuncOpResultMapping(unsigned funcReturnIdx);
398 
399  /// The instance is obtained from the name of the callee.
400  InstanceOp getInstance(StringRef calleeName);
401 
402  /// Put the name of the callee and the instance of the call into map.
403  void addInstance(StringRef calleeName, InstanceOp instanceOp);
404 
405  /// Return the group which evaluates the value v. Optionally, caller may
406  /// specify the expected type of the group.
407  template <typename TGroupOp = calyx::GroupInterface>
408  TGroupOp getEvaluatingGroup(Value v) {
409  auto it = valueGroupAssigns.find(v);
410  assert(it != valueGroupAssigns.end() && "No group evaluating value!");
411  if constexpr (std::is_same_v<TGroupOp, calyx::GroupInterface>)
412  return it->second;
413  else {
414  auto group = dyn_cast<TGroupOp>(it->second.getOperation());
415  assert(group && "Actual group type differed from expected group type");
416  return group;
417  }
418  }
419 
420  template <typename T, typename = void>
421  struct IsFloatingPoint : std::false_type {};
422 
423  template <typename T>
425  T, std::void_t<decltype(std::declval<T>().getFloatingPointStandard())>>
426  : std::is_same<decltype(std::declval<T>().getFloatingPointStandard()),
427  FloatingPointStandard> {};
428 
429  template <typename TLibraryOp>
430  TLibraryOp getNewLibraryOpInstance(OpBuilder &builder, Location loc,
431  TypeRange resTypes) {
432  mlir::IRRewriter::InsertionGuard guard(builder);
433  Block *body = component.getBodyBlock();
434  builder.setInsertionPoint(body, body->begin());
435  std::string name = TLibraryOp::getOperationName().split(".").second.str();
436  if constexpr (IsFloatingPoint<TLibraryOp>::value) {
437  switch (TLibraryOp::getFloatingPointStandard()) {
439  constexpr char prefix[] = "ieee754.";
440  assert(name.find(prefix) == 0 &&
441  ("IEEE754 type operation's name must begin with '" +
442  std::string(prefix) + "'")
443  .c_str());
444  name.erase(0, sizeof(prefix) - 1);
445  name = llvm::join_items(/*separator=*/"", "std_", name, "FN");
446  break;
447  }
448  }
449  }
450  return builder.create<TLibraryOp>(loc, getUniqueName(name), resTypes);
451  }
452 
453 private:
454  /// The component which this lowering state is associated to.
455  calyx::ComponentOp component;
456 
457  /// A mapping from blocks to block argument registers.
458  DenseMap<Block *, DenseMap<unsigned, calyx::RegisterOp>> blockArgRegs;
459 
460  /// Block arg groups is a list of groups that should be sequentially
461  /// executed when passing control from the source to destination block.
462  /// Block arg groups are executed before blockScheduleables (akin to a
463  /// phi-node).
464  DenseMap<Block *, DenseMap<Block *, SmallVector<calyx::GroupOp>>>
466 
467  /// A mapping of string prefixes and the current uniqueness counter for that
468  /// prefix. Used to generate unique names.
469  std::map<std::string, unsigned> prefixIdMap;
470 
471  /// A mapping from Operations and previously assigned unique name of the op.
472  std::map<Operation *, std::string> opNames;
473 
474  /// A mapping between SSA values and the groups which assign them.
475  DenseMap<Value, calyx::GroupInterface> valueGroupAssigns;
476 
477  /// A mapping from return value indexes to return value registers.
478  DenseMap<unsigned, calyx::RegisterOp> returnRegs;
479 
480  /// A mapping from memref's to their corresponding Calyx memory interface.
481  DenseMap<Value, calyx::MemoryInterface> memories;
482 
483  /// A mapping between the source funcOp result indices and the corresponding
484  /// output port indices of this componentOp.
485  DenseMap<unsigned, unsigned> funcOpResultMapping;
486 
487  /// A mapping between the callee and the instance.
488  llvm::StringMap<calyx::InstanceOp> instanceMap;
489 };
490 
491 /// An interface for conversion passes that lower Calyx programs. This handles
492 /// state during the lowering of a Calyx program.
494 public:
495  explicit CalyxLoweringState(mlir::ModuleOp module,
496  StringRef topLevelFunction);
497 
498  /// Returns the current program.
499  mlir::ModuleOp getModule();
500 
501  /// Returns the name of the top-level function in the source program.
502  StringRef getTopLevelFunction() const;
503 
504  /// Returns a meaningful name for a block within the program scope (removes
505  /// the ^ prefix from block names).
506  std::string blockName(Block *b);
507 
508  /// Returns the component lowering state associated with `op`. If not found
509  /// already found, a new mapping is added for this ComponentOp. Different
510  /// conversions may have different derived classes of the interface, so we
511  /// provided a template.
512  template <typename T = calyx::ComponentLoweringStateInterface>
513  T *getState(calyx::ComponentOp op) {
514  static_assert(std::is_convertible_v<T, ComponentLoweringStateInterface>);
515  auto it = componentStates.find(op);
516  if (it == componentStates.end()) {
517  // Create a new ComponentLoweringState for the compOp.
518  bool success;
519  std::tie(it, success) =
520  componentStates.try_emplace(op, std::make_unique<T>(op));
521  }
522 
523  return static_cast<T *>(it->second.get());
524  }
525 
526  /// Returns a meaningful name for a value within the program scope.
527  template <typename ValueOrBlock>
528  std::string irName(ValueOrBlock &v) {
529  std::string s;
530  llvm::raw_string_ostream os(s);
531  mlir::AsmState asmState(module);
532  v.printAsOperand(os, asmState);
533  return s;
534  }
535 
536 private:
537  /// The name of this top-level function.
538  StringRef topLevelFunction;
539  /// The program associated with this state.
540  mlir::ModuleOp module;
541  /// Mapping from ComponentOp to component lowering state.
542  DenseMap<Operation *, std::unique_ptr<ComponentLoweringStateInterface>>
544 };
545 
546 /// Extra state that is passed to all PartialLoweringPatterns so they can record
547 /// when they have run on an Operation, and only run once.
549  DenseMap<const mlir::RewritePattern *, SmallPtrSet<Operation *, 16>>;
550 
551 /// Base class for partial lowering passes. A partial lowering pass
552 /// modifies the root operation in place, but does not replace the root
553 /// operation.
554 /// The RewritePatternType template parameter allows for using both
555 /// OpRewritePattern (default) or OpInterfaceRewritePattern.
556 template <class OpType,
557  template <class> class RewritePatternType = OpRewritePattern>
558 class PartialLoweringPattern : public RewritePatternType<OpType> {
559 public:
560  using RewritePatternType<OpType>::RewritePatternType;
561  PartialLoweringPattern(MLIRContext *ctx, LogicalResult &resRef,
563  : RewritePatternType<OpType>(ctx), partialPatternRes(resRef),
565 
566  LogicalResult matchAndRewrite(OpType op,
567  PatternRewriter &rewriter) const override {
568  // If this pattern has been applied to this op, it should now fail to match.
569  if (patternState[this].contains(op))
570  return failure();
571 
572  // Do the actual rewrite, marking this op as updated. Because the op is
573  // marked as updated, the pattern driver will re-enqueue the op again.
574  rewriter.modifyOpInPlace(
575  op, [&] { partialPatternRes = partiallyLower(op, rewriter); });
576 
577  // Mark that this pattern has been applied to this op.
578  patternState[this].insert(op);
579 
580  return partialPatternRes;
581  }
582 
583  // Hook for subclasses to lower the op using the rewriter.
584  //
585  // Note that this call is wrapped in `modifyOpInPlace`, so any direct IR
586  // mutations that are legal to apply during a root update of op are allowed.
587  //
588  // Also note that this means the op will be re-enqueued to the greedy
589  // rewriter's worklist. A safeguard is in place to prevent patterns from
590  // running multiple times, but if the op is erased or otherwise becomes dead
591  // after the call to `partiallyLower`, there will likely be use-after-free
592  // violations. If you will erase the op, override `matchAndRewrite` directly.
593  virtual LogicalResult partiallyLower(OpType op,
594  PatternRewriter &rewriter) const = 0;
595 
596 private:
597  LogicalResult &partialPatternRes;
599 };
600 
601 /// Helper to update the top-level ModuleOp to set the entrypoing function.
602 LogicalResult applyModuleOpConversion(mlir::ModuleOp,
603  StringRef topLevelFunction);
604 
605 /// FuncOpPartialLoweringPatterns are patterns which intend to match on FuncOps
606 /// and then perform their own walking of the IR.
608  : public calyx::PartialLoweringPattern<mlir::func::FuncOp> {
609 
610 public:
612  MLIRContext *context, LogicalResult &resRef,
614  DenseMap<mlir::func::FuncOp, calyx::ComponentOp> &map,
616 
617  /// Entry point to initialize the state of this class and conduct the partial
618  /// lowering.
619  LogicalResult partiallyLower(mlir::func::FuncOp funcOp,
620  PatternRewriter &rewriter) const override final;
621 
622  /// Returns the component operation associated with the currently executing
623  /// partial lowering.
624  calyx::ComponentOp getComponent() const;
625 
626  // Returns the component state associated with the currently executing
627  // partial lowering.
628  template <typename T = ComponentLoweringStateInterface>
629  T &getState() const {
630  static_assert(
631  std::is_convertible_v<T, calyx::ComponentLoweringStateInterface>);
632  assert(
633  componentLoweringState != nullptr &&
634  "Component lowering state should be set during pattern construction");
635  return *static_cast<T *>(componentLoweringState);
636  }
637 
638  /// Return the calyx lowering state for this pattern.
640 
641  // Hook for subclasses to lower the op using the rewriter.
642  //
643  // Note that this call is wrapped in `modifyOpInPlace`, so any direct IR
644  // mutations that are legal to apply during a root update of op are allowed.
645  //
646  // Also note that this means the op will be re-enqueued to the greedy
647  // rewriter's worklist. A safeguard is in place to prevent patterns from
648  // running multiple times, but if the op is erased or otherwise becomes dead
649  // after the call to `partiallyLower`, there will likely be use-after-free
650  // violations. If you will erase the op, override `matchAndRewrite` directly.
651  virtual LogicalResult
652  partiallyLowerFuncToComp(mlir::func::FuncOp funcOp,
653  PatternRewriter &rewriter) const = 0;
654 
655 protected:
656  // A map from FuncOp to it's respective ComponentOp lowering.
657  DenseMap<mlir::func::FuncOp, calyx::ComponentOp> &functionMapping;
658 
659 private:
660  mutable ComponentOp componentOp;
663 };
664 
665 /// Converts all index-typed operations and values to i32 values.
668 
669  LogicalResult
670  partiallyLowerFuncToComp(mlir::func::FuncOp funcOp,
671  PatternRewriter &rewriter) const override;
672 };
673 
674 /// GroupDoneOp's are terminator operations and should therefore be the last
675 /// operator in a group. During group construction, we always append assignments
676 /// to the end of a group, resulting in group_done ops migrating away from the
677 /// terminator position. This pattern moves such ops to the end of their group.
679  : mlir::OpRewritePattern<calyx::GroupDoneOp> {
681 
682  LogicalResult matchAndRewrite(calyx::GroupDoneOp groupDoneOp,
683  PatternRewriter &) const override;
684 };
685 
686 /// When building groups which contain accesses to multiple sequential
687 /// components, a group_done op is created for each of these. This pattern
688 /// and's each of the group_done values into a single group_done.
691 
692  LogicalResult matchAndRewrite(calyx::GroupOp groupOp,
693  PatternRewriter &rewriter) const override;
694 };
695 
696 /// Removes calyx::CombGroupOps which are unused. These correspond to
697 /// combinational groups created during op building that, after conversion,
698 /// have either been inlined into calyx::GroupOps or are referenced by an
699 /// if/while with statement.
700 /// We do not eliminate unused calyx::GroupOps; this should never happen, and is
701 /// considered an error. In these cases, the program will be invalidated when
702 /// the Calyx verifiers execute.
703 struct EliminateUnusedCombGroups : mlir::OpRewritePattern<calyx::CombGroupOp> {
705 
706  LogicalResult matchAndRewrite(calyx::CombGroupOp combGroupOp,
707  PatternRewriter &rewriter) const override;
708 };
709 
710 /// This pass recursively inlines use-def chains of combinational logic (from
711 /// non-stateful groups) into groups referenced in the control schedule.
713  : public calyx::PartialLoweringPattern<calyx::GroupInterface,
714  mlir::OpInterfaceRewritePattern> {
715 public:
716  InlineCombGroups(MLIRContext *context, LogicalResult &resRef,
719 
720  LogicalResult partiallyLower(calyx::GroupInterface originGroup,
721  PatternRewriter &rewriter) const override;
722 
723 private:
724  void
725  recurseInlineCombGroups(PatternRewriter &rewriter,
727  llvm::SmallSetVector<Operation *, 8> &inlinedGroups,
728  calyx::GroupInterface originGroup,
729  calyx::GroupInterface recGroup, bool doInline) const;
730 
732 };
733 
734 /// This pass rewrites memory accesses that have a width mismatch. Such
735 /// mismatches are due to index types being assumed 32-bit wide due to the lack
736 /// of a width inference pass.
738  : public calyx::PartialLoweringPattern<calyx::AssignOp> {
739 public:
740  RewriteMemoryAccesses(MLIRContext *context, LogicalResult &resRef,
743  : PartialLoweringPattern(context, resRef, patternState), cls(cls) {}
744 
745  LogicalResult partiallyLower(calyx::AssignOp assignOp,
746  PatternRewriter &rewriter) const override;
747 
748 private:
750 };
751 
752 /// Builds registers for each block argument in the program.
755 
756  LogicalResult
757  partiallyLowerFuncToComp(mlir::func::FuncOp funcOp,
758  PatternRewriter &rewriter) const override;
759 };
760 
761 /// Builds registers for the return statement of the program and constant
762 /// assignments to the component return value.
765 
766  LogicalResult
767  partiallyLowerFuncToComp(mlir::func::FuncOp funcOp,
768  PatternRewriter &rewriter) const override;
769 };
770 
771 /// Builds instance for the calyx.invoke and calyx.group in order to initialize
772 /// the instance.
775 
776  LogicalResult
777  partiallyLowerFuncToComp(mlir::func::FuncOp funcOp,
778  PatternRewriter &rewriter) const override;
779  ComponentOp getCallComponent(mlir::func::CallOp callOp) const;
780 };
781 
782 } // namespace calyx
783 } // namespace circt
784 
785 #endif // CIRCT_DIALECT_CALYX_CALYXLOWERINGUTILS_H
assert(baseType &&"element must be base type")
virtual std::optional< int64_t > getBound()=0
virtual Location getLoc()=0
virtual Block::BlockArgListType getBodyArgs()=0
virtual Block * getBodyBlock()=0
Builds registers for each block argument in the program.
LogicalResult partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const override
Builds instance for the calyx.invoke and calyx.group in order to initialize the instance.
LogicalResult partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const override
ComponentOp getCallComponent(mlir::func::CallOp callOp) const
Builds registers for the return statement of the program and constant assignments to the component re...
LogicalResult partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const override
An interface for conversion passes that lower Calyx programs.
std::string irName(ValueOrBlock &v)
Returns a meaningful name for a value within the program scope.
mlir::ModuleOp getModule()
Returns the current program.
CalyxLoweringState(mlir::ModuleOp module, StringRef topLevelFunction)
DenseMap< Operation *, std::unique_ptr< ComponentLoweringStateInterface > > componentStates
Mapping from ComponentOp to component lowering state.
T * getState(calyx::ComponentOp op)
Returns the component lowering state associated with op.
std::string blockName(Block *b)
Returns a meaningful name for a block within the program scope (removes the ^ prefix from block names...
StringRef getTopLevelFunction() const
Returns the name of the top-level function in the source program.
StringRef topLevelFunction
The name of this top-level function.
mlir::ModuleOp module
The program associated with this state.
const DenseMap< unsigned, calyx::RegisterOp > & getBlockArgRegs(Block *block)
Return a mapping of block argument indices to block argument registers.
void setFuncOpResultMapping(const DenseMap< unsigned, unsigned > &mapping)
Assign a mapping between the source funcOp result indices and the corresponding output port indices o...
DenseMap< Value, calyx::MemoryInterface > memories
A mapping from memref's to their corresponding Calyx memory interface.
TGroupOp getEvaluatingGroup(Value v)
Return the group which evaluates the value v.
void addReturnReg(calyx::RegisterOp reg, unsigned idx)
Register reg as being the idx'th return value register.
calyx::MemoryInterface getMemoryInterface(Value memref)
Returns the memory interface registered for the given memref.
llvm::StringMap< calyx::InstanceOp > instanceMap
A mapping between the callee and the instance.
DenseMap< Block *, DenseMap< Block *, SmallVector< calyx::GroupOp > > > blockArgGroups
Block arg groups is a list of groups that should be sequentially executed when passing control from t...
void setUniqueName(Operation *op, StringRef prefix)
Registers a unique name for a given operation using a provided prefix.
calyx::RegisterOp getReturnReg(unsigned idx)
Returns the idx'th return value register.
std::string getUniqueName(StringRef prefix)
Returns a unique name within compOp with the provided prefix.
calyx::ComponentOp component
The component which this lowering state is associated to.
void registerMemoryInterface(Value memref, const calyx::MemoryInterface &memoryInterface)
Registers a memory interface as being associated with a memory identified by 'memref'.
calyx::ComponentOp getComponentOp()
Returns the calyx::ComponentOp associated with this lowering state.
void addBlockArgReg(Block *block, calyx::RegisterOp reg, unsigned idx)
Register reg as being the idx'th argument register for block.
InstanceOp getInstance(StringRef calleeName)
The instance is obtained from the name of the callee.
unsigned getFuncOpResultMapping(unsigned funcReturnIdx)
Get the output port index of this component for which the funcReturnIdx of the original function maps...
DenseMap< unsigned, unsigned > funcOpResultMapping
A mapping between the source funcOp result indices and the corresponding output port indices of this ...
void addInstance(StringRef calleeName, InstanceOp instanceOp)
Put the name of the callee and the instance of the call into map.
DenseMap< Value, calyx::GroupInterface > valueGroupAssigns
A mapping between SSA values and the groups which assign them.
std::map< std::string, unsigned > prefixIdMap
A mapping of string prefixes and the current uniqueness counter for that prefix.
ArrayRef< calyx::GroupOp > getBlockArgGroups(Block *from, Block *to)
Returns a list of groups to be evaluated to perform the block argument register assignments when tran...
ComponentLoweringStateInterface(calyx::ComponentOp component)
void registerEvaluatingGroup(Value v, calyx::GroupInterface group)
Register value v as being evaluated when scheduling group.
std::optional< calyx::MemoryInterface > isInputPortOfMemory(Value v)
If v is an input to any memory registered within this component, returns the memory.
void addBlockArgGroup(Block *from, Block *to, calyx::GroupOp grp)
Register 'grp' as a group which performs block argument register transfer when transitioning from bas...
TLibraryOp getNewLibraryOpInstance(OpBuilder &builder, Location loc, TypeRange resTypes)
DenseMap< Block *, DenseMap< unsigned, calyx::RegisterOp > > blockArgRegs
A mapping from blocks to block argument registers.
DenseMap< unsigned, calyx::RegisterOp > returnRegs
A mapping from return value indexes to return value registers.
std::map< Operation *, std::string > opNames
A mapping from Operations and previously assigned unique name of the op.
Converts all index-typed operations and values to i32 values.
LogicalResult partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const override
FuncOpPartialLoweringPatterns are patterns which intend to match on FuncOps and then perform their ow...
calyx::ComponentOp getComponent() const
Returns the component operation associated with the currently executing partial lowering.
ComponentLoweringStateInterface * componentLoweringState
DenseMap< mlir::func::FuncOp, calyx::ComponentOp > & functionMapping
virtual LogicalResult partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const =0
CalyxLoweringState & loweringState() const
Return the calyx lowering state for this pattern.
FuncOpPartialLoweringPattern(MLIRContext *context, LogicalResult &resRef, PatternApplicationState &patternState, DenseMap< mlir::func::FuncOp, calyx::ComponentOp > &map, calyx::CalyxLoweringState &state)
LogicalResult partiallyLower(mlir::func::FuncOp funcOp, PatternRewriter &rewriter) const override final
Entry point to initialize the state of this class and conduct the partial lowering.
This pass recursively inlines use-def chains of combinational logic (from non-stateful groups) into g...
LogicalResult partiallyLower(calyx::GroupInterface originGroup, PatternRewriter &rewriter) const override
InlineCombGroups(MLIRContext *context, LogicalResult &resRef, PatternApplicationState &patternState, calyx::CalyxLoweringState &pls)
void recurseInlineCombGroups(PatternRewriter &rewriter, ComponentLoweringStateInterface &state, llvm::SmallSetVector< Operation *, 8 > &inlinedGroups, calyx::GroupInterface originGroup, calyx::GroupInterface recGroup, bool doInline) const
calyx::CalyxLoweringState & cls
virtual Value getConditionValue()=0
virtual Block * getConditionBlock()=0
calyx::GroupOp getLoopLatchGroup(Loop op)
Retrieve the loop latch group registered for op.
const DenseMap< unsigned, calyx::RegisterOp > & getLoopIterRegs(Loop op)
Return a mapping of block argument indices to block argument.
void setLoopLatchGroup(Loop op, calyx::GroupOp group)
Registers grp to be the loop latch group of op.
calyx::RegisterOp getLoopIterReg(Loop op, unsigned idx)
Return a mapping of block argument indices to block argument.
void addLoopIterReg(Loop op, calyx::RegisterOp reg, unsigned idx)
Register reg as being the idx'th iter_args register for 'op'.
void setLoopInitGroups(Loop op, SmallVector< calyx::GroupOp > groups)
Registers groups to be the loop init groups of op.
DenseMap< Operation *, DenseMap< unsigned, calyx::RegisterOp > > loopIterRegs
A mapping from loop ops to iteration argument registers.
DenseMap< Operation *, calyx::GroupOp > loopLatchGroups
A loop latch group is a group that should be sequentially executed when finishing a loop body.
calyx::GroupOp buildLoopIterArgAssignments(OpBuilder &builder, Loop op, calyx::ComponentOp componentOp, Twine uniqueSuffix, MutableArrayRef< OpOperand > ops)
Creates a new group that assigns the 'ops' values to the iter arg registers of the loop operation.
DenseMap< Operation *, SmallVector< calyx::GroupOp > > loopInitGroups
Loop init groups are to be scheduled before the while operation.
SmallVector< calyx::GroupOp > getLoopInitGroups(Loop op)
Retrieve the loop init groups registered for op.
Base class for partial lowering passes.
virtual LogicalResult partiallyLower(OpType op, PatternRewriter &rewriter) const =0
PatternApplicationState & patternState
PartialLoweringPattern(MLIRContext *ctx, LogicalResult &resRef, PatternApplicationState &patternState)
LogicalResult matchAndRewrite(OpType op, PatternRewriter &rewriter) const override
This pass rewrites memory accesses that have a width mismatch.
calyx::CalyxLoweringState & cls
LogicalResult partiallyLower(calyx::AssignOp assignOp, PatternRewriter &rewriter) const override
RewriteMemoryAccesses(MLIRContext *context, LogicalResult &resRef, PatternApplicationState &patternState, calyx::CalyxLoweringState &cls)
Holds common utilities used for scheduling when lowering to Calyx.
SmallVector< T > getBlockScheduleables(mlir::Block *block)
Returns an ordered list of schedulables which registered themselves to be a result of lowering the bl...
void addBlockScheduleable(mlir::Block *block, const T &scheduleable)
Register 'scheduleable' as being generated through lowering 'block'.
DenseMap< mlir::Block *, SmallVector< T > > blockScheduleables
BlockScheduleables is a list of scheduleables that should be sequentially executed when executing the...
TGroup createGroup(OpBuilder &builder, calyx::ComponentOp compOp, Location loc, Twine uniqueName)
void appendPortsForExternalMemref(PatternRewriter &rewriter, StringRef memName, Value memref, unsigned memoryID, SmallVectorImpl< calyx::PortInfo > &inPorts, SmallVectorImpl< calyx::PortInfo > &outPorts)
void buildAssignmentsForRegisterWrite(OpBuilder &builder, calyx::GroupOp groupOp, calyx::ComponentOp componentOp, calyx::RegisterOp &reg, Value inputValue)
Creates register assignment operations within the provided groupOp.
DenseMap< const mlir::RewritePattern *, SmallPtrSet< Operation *, 16 > > PatternApplicationState
Extra state that is passed to all PartialLoweringPatterns so they can record when they have run on an...
Type convIndexType(OpBuilder &builder, Type type)
LogicalResult applyModuleOpConversion(mlir::ModuleOp, StringRef topLevelFunction)
Helper to update the top-level ModuleOp to set the entrypoing function.
WalkResult getCiderSourceLocationMetadata(calyx::ComponentOp component, SmallVectorImpl< Attribute > &sourceLocations)
bool matchConstantOp(Operation *op, APInt &value)
bool noStoresToMemory(Value memoryReference)
Value getComponentOutput(calyx::ComponentOp compOp, unsigned outPortIdx)
bool singleLoadFromMemory(Value memoryReference)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None)
Definition: seq.py:21
Removes calyx::CombGroupOps which are unused.
LogicalResult matchAndRewrite(calyx::CombGroupOp combGroupOp, PatternRewriter &rewriter) const override
std::optional< Value > contentEnOpt()
std::variant< calyx::MemoryOp, calyx::SeqMemoryOp, MemoryPortsImpl > impl
std::optional< Value > writeDataOpt()
std::optional< Value > readEnOpt()
std::optional< Value > readDataOpt()
std::optional< Value > doneOpt()
std::optional< Value > writeEnOpt()
std::optional< Value > readOrContentEn
std::optional< Value > done
std::optional< Value > writeEn
std::optional< Value > writeData
std::optional< bool > isContentEn
std::optional< Value > readData
SmallVector< Value > addrPorts
When building groups which contain accesses to multiple sequential components, a group_done op is cre...
LogicalResult matchAndRewrite(calyx::GroupOp groupOp, PatternRewriter &rewriter) const override
GroupDoneOp's are terminator operations and should therefore be the last operator in a group.
LogicalResult matchAndRewrite(calyx::GroupDoneOp groupDoneOp, PatternRewriter &) const override