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