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