-
Notifications
You must be signed in to change notification settings - Fork 302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SCFToCalyx] If op with sequential condition #7687
base: main
Are you sure you want to change the base?
Conversation
cb4f02b
to
b42c4a0
Compare
b42c4a0
to
4d83a5c
Compare
@@ -240,13 +255,36 @@ class ForLoopLoweringStateInterface | |||
} | |||
}; | |||
|
|||
class PipeOpLoweringStateInterface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not very satisfied with this approach though, having to introduce another interface to "remember" the register set for the sequential/pipelined library operator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, we'll definitely need some way to recall this mapping, since it isn't explicitly stated in the IR. If this is only necessary for lowering conditionals in if
statements, then I think it would make more sense in the IfLoweringStateInterface
.
With that said, I don't think this is the only Calyx control flow that will require this special treatment, i.e., while
should be similar. What other approaches are you thinking about?
In any case, it would be nice to add documentation on why this interface exists.
700af35
to
7fc6c93
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to check my understanding, this PR is ensuring that if the condition of a SCF if
op depends on some sequential operation, we create a separate group and save its value in a register to be used in the Calyx if
op. Is that correct?
Operation *operation = op.getOperation(); | ||
assert(condReg.count(operation) == 0 && | ||
"A condition register was already set for this scf::IfOp!\n"); | ||
condReg[operation] = regOp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid the double lookup here (once for count
and once for the []
operator, we can do:
auto [it, succeeded] = condReg.insert(std::make_pair(operation, regOp));
assert(succeeded && "...");
@@ -172,6 +186,7 @@ class IfLoweringStateInterface { | |||
} | |||
|
|||
private: | |||
DenseMap<Operation *, calyx::RegisterOp> condReg; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this hasn't been the norm, but it would sure be nice to describe why this data structure exists for future readers :-)
class PipeOpLoweringStateInterface { | ||
public: | ||
void setPipeResReg(Operation *op, calyx::RegisterOp reg) { | ||
assert(isa<calyx::MultPipeLibOp>(op) || isa<calyx::DivUPipeLibOp>(op) || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prefer: isa<calyx::MultPipeLibOp, calyx::DivUPipelibOp, ...>(op)
assert(isa<calyx::MultPipeLibOp>(op) || isa<calyx::DivUPipeLibOp>(op) || | ||
isa<calyx::DivSPipeLibOp>(op) || isa<calyx::RemUPipeLibOp>(op) || | ||
isa<calyx::RemSPipeLibOp>(op)); | ||
assert(resultRegs.count(op) == 0 && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar comment as above.
if (isSequential) | ||
rewriter.create<calyx::AssignOp>(op.getLoc(), dstOp.value(), | ||
srcReg.getOut()); | ||
else | ||
rewriter.create<calyx::AssignOp>(op.getLoc(), dstOp.value(), | ||
op->getOperand(dstOp.index())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (isSequential) | |
rewriter.create<calyx::AssignOp>(op.getLoc(), dstOp.value(), | |
srcReg.getOut()); | |
else | |
rewriter.create<calyx::AssignOp>(op.getLoc(), dstOp.value(), | |
op->getOperand(dstOp.index())); | |
auto srcOp = isSequential ? srcReg.getOut() : op->getOperand(dstOp.index()); | |
rewriter.create<calyx::AssignOp>(op.getLoc(), dstOp.value(), srcOp); |
Maybe this doesn't work, but I would try to limit the scope of your conditional.
} | ||
|
||
private: | ||
DenseMap<Operation *, calyx::RegisterOp> resultRegs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment would be nice here!
resultRegs[op] = reg; | ||
} | ||
// Get the register for a specific pipe operation | ||
calyx::RegisterOp getPipeResReg(Operation *op) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly for this function, it is used twice but only because you condition on an entire function call rather than the one element that is different: the function argument. I think we should avoid introducing this unless you think it improves readability.
@@ -240,13 +255,36 @@ class ForLoopLoweringStateInterface | |||
} | |||
}; | |||
|
|||
class PipeOpLoweringStateInterface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, we'll definitely need some way to recall this mapping, since it isn't explicitly stated in the IR. If this is only necessary for lowering conditionals in if
statements, then I think it would make more sense in the IfLoweringStateInterface
.
With that said, I don't think this is the only Calyx control flow that will require this special treatment, i.e., while
should be similar. What other approaches are you thinking about?
In any case, it would be nice to add documentation on why this interface exists.
@@ -339,7 +377,12 @@ class BuildOpGroups : public calyx::FuncOpPartialLoweringPattern { | |||
/// source operation TSrcOp. | |||
template <typename TGroupOp, typename TCalyxLibOp, typename TSrcOp> | |||
LogicalResult buildLibraryOp(PatternRewriter &rewriter, TSrcOp op, | |||
TypeRange srcTypes, TypeRange dstTypes) const { | |||
TypeRange srcTypes, TypeRange dstTypes, | |||
calyx::RegisterOp srcReg = nullptr, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add comments about these.
%rem = arith.remui %arg0, %two : i32 | ||
%cond = arith.cmpi eq, %arg0, %rem : i32 | ||
|
||
%res = scf.if %cond -> i32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: could we minimize what you're trying to test even further, e.g., do we need the else
case, mem alloc, ...? It seems the critical piece is an if statement that uses the output of a sequential operation.
That's exactly right! :-) And to reply the following comment as a whole:
Let me shamelessly copy your wording to describe the feature I'm trying to support in general:
So you are right, both if and while need it.
Currently, my implementation has the following lowered result: calyx.group @bb0_0 {
calyx.assign %std_remu_pipe_0.left = %in0 : i32
calyx.assign %std_remu_pipe_0.right = %c2_i32 : i32
calyx.assign %remui_0_reg.in = %std_remu_pipe_0.out_remainder : i32
calyx.assign %remui_0_reg.write_en = %std_remu_pipe_0.done : i1
%0 = comb.xor %std_remu_pipe_0.done, %true : i1
calyx.assign %std_remu_pipe_0.go = %0 ? %true : i1
calyx.group_done %remui_0_reg.done : i1
}
calyx.group @bb0_1 {
calyx.assign %std_eq_0.left = %remui_0_reg.out : i32
calyx.assign %std_eq_0.right = %remui_0_reg.out : i32
calyx.assign %cmpi_0_reg.in = %std_eq_0.out : i1
calyx.assign %cmpi_0_reg.write_en = %true : i1
calyx.group_done %cmpi_0_reg.done : i1
}
calyx.control {
calyx.seq {
calyx.enable @bb0_0
calyx.enable @bb0_1
calyx.if %cmpi_0_reg.out {
some operation I wonder, is it possible to do: calyx.group @bb0_0 {
calyx.assign %std_remu_pipe_0.left = %in0 : i32
calyx.assign %std_remu_pipe_0.right = %c2_i32 : i32
calyx.assign %remui_0_reg.in = %std_remu_pipe_0.out_remainder : i32
calyx.assign %remui_0_reg.write_en = %std_remu_pipe_0.done : i1
%0 = comb.xor %std_remu_pipe_0.done, %true : i1
calyx.assign %std_remu_pipe_0.go = %0 ? %true : i1
calyx.group_done %remui_0_reg.done : i1
}
calyx.comb_group @bb0_1 {
calyx.assign %std_eq_0.left = %remui_0_reg.out : i32
calyx.assign %std_eq_0.right = %remui_0_reg.out : i32
}
calyx.control {
calyx.seq {
calyx.enable @bb0_0
calyx.if %std_eq_0.out with @bb0_1 {
some operation
So that we can save a cycle, but I'm not sure what needs to be check with respect to the stability of @rachitnigam recommends the first approach though, as he mentioned in the issue:
|
My first thought is this additional step could be enabled as a peephole optimization starting from the calyx.enable @bb0_1
calyx.if %cmpi_0_reg.out { --> calyx.enable @bb0_1
calyx.if %std_eq_0.out with @bb0_1 { where we get rid of an intermediate register. |
op->getOperand(dstOp.index())); | ||
|
||
for (auto dstOp : enumerate(opInputPorts)) { | ||
if (isSequential) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug, should be:
if (isSequential) | |
if (isPipeLibOp(dstOp.value())) | |
rewriter.create<calyx::AssignOp>(op.getLoc(), dstOp.value(), | |
srcReg.getOut()); |
Then seems like we are checking isPipeLibOp
twice, once in a caller function, another in a callee..
This patch addresses #7682. It supports IfOp whose condition check involves sequential operations by enabling
buildLibraryOp
to createGroupOp
.