Skip to content
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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

jiahanxie353
Copy link
Contributor

This patch addresses #7682. It supports IfOp whose condition check involves sequential operations by enabling buildLibraryOp to create GroupOp.

@jiahanxie353 jiahanxie353 added the Calyx The Calyx dialect label Oct 9, 2024
@@ -240,13 +255,36 @@ class ForLoopLoweringStateInterface
}
};

class PipeOpLoweringStateInterface {
Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

@cgyurgyik cgyurgyik left a 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;
Copy link
Member

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;
Copy link
Member

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) ||
Copy link
Member

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 &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment as above.

Comment on lines +421 to +426
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()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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;
Copy link
Member

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) {
Copy link
Member

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 {
Copy link
Member

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,
Copy link
Member

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 {
Copy link
Member

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.

@jiahanxie353
Copy link
Contributor Author

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?

That's exactly right! :-)

And to reply the following comment as a whole:

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.

Let me shamelessly copy your wording to describe the feature I'm trying to support in general:
This (and future) PR is ensuring that if the condition of a SCF if/while 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/while op.

I don't think this is the only Calyx control flow that will require this special treatment

So you are right, both if and while need it.

What other approaches are you thinking about?

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 %std_eq_0.out with @bb0_1

@rachitnigam recommends the first approach though, as he mentioned in the issue:

compute_cond; // computes rem and store in reg
if reg.out { ... } else { ... }

@cgyurgyik
Copy link
Member

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 %std_eq_0.out with @bb0_1

My first thought is this additional step could be enabled as a peephole optimization starting from the calyx.if, i.e.,

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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug, should be:

Suggested change
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..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Calyx The Calyx dialect
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants