[RTL] Adding a constant value to a mlir::OperandRange

Hi all. I am looking to implement constant folding for the and operation.
given that the type of inputs is a mlir::OperandRange (as deduced by my error messages),
I want to add a value to the back. I know there exists drop_back, but what about add_back, or similar?

void AndOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
                                        MLIRContext *context) {
 struct Folder final : public OpRewritePattern<AndOp> {
    using OpRewritePattern::OpRewritePattern;
    LogicalResult matchAndRewrite(AndOp op,
                                  PatternRewriter &rewriter) const override {
      auto inputs = op.inputs();

      // ...

      APInt value1, value2;
      // and(..., c1, c2) -> and(..., c3) -- constant folding
      if (matchPattern(inputs[size - 1], m_RConstant(value1)) &&
          matchPattern(inputs[size - 2], m_RConstant(value2))) {
        value1 &= value2;
        inputs.add_back(value1); // :-(
        rewriter.replaceOpWithNewOp<AndOp>(op, op.getType(), inputs);
        return success();
      // ...

I tried finding other places that do similar optimizations, to no avail.

Hey Chris, good question!

I’d recommend taking a look at this section of the LLVM Programmer’s Manual if you haven’t seen it, it defines some essential concepts you’ll see across the code base.

mlir::OperandRange is a computed/projected view of the underlying operands of an operation - you can mutate elements, but you can’t add or delete operands to the Operation through this interface (note that drop_back just shrinks the view, it doesn’t change the operation AFAIK).

The reason for this is that you can’t change the number of operands in operation in MLIR. This has to do with some low level storage optimizations that are going on.

The solution in this case is to make a temporary vector to hold the new things, more specifically a SmallVector. Something like this should work:

  SmallVector<Value, 4> newOperands(inputs.drop_back(/*n=*/2));
  rewriter.replaceOpWithNewOp<AndOp>(op, op.getType(), newOperands);


Random thought: we should also canonicalize and(x) --> x similarly for single operand versions of the other variadic operators.

edit: oh nevermind, it looks like AndOp::fold already gets this, great!

Fly-by comment, you actually can change the number of operands by calling Opreation::insertOpreands or Operation::eraseOperand. Such inplace mutations don’t play well with the pattern infrastructure though.

Actually, they do. (If they aren’t, we can fix it.) An operation can be updated in place (either via its folding hook or a pattern registered on it), and the rewriter driver internally will add all its operands’ producers (defining ops) back into the worklist. So, not only can you change operands, but also add/remove operands. It’s pretty common in all the simplification and canonicalization that we do for ops with map + operands. The number of operands could increase or decrease typically via its folding hook and the greedy rewrite driver should do the right thing. For rewrite patterns, start/finalize root update should do the notification.

You can freely change the number of operands for an Operation (both increase or decrease). This is pretty common for all operations that take maps + operands including affine.for where bound operands get added/removed/reset (via setOperands) without having to reallocate. A number of folding hooks also change the number of operands during canonicalization and simplification in place (for eg. replacing symbolic upper bounds with constants). These mutations can also happen in rewrite patterns, and the rewriting driver is internally aware of them.

In fact, I have not seen this. Thanks Chris.

@bondhugula Thanks for this. Do you have any relevant documentation / code samples of this that I can follow? I tried to find something along these lines, but, as you can tell, failed :slight_smile:

If your goal is to implement constant folding, you should actually be adding a AndOp::fold. For reference, please see SplatOp::fold from the standard dialect. Independently of this, if you need to update the operands on an op, please use setOperands() after having constructed the vector of new operands.

Playing with your example, this doesn’t quite work since value1 has type APInt, and the SmallVector (as well as inputs) accepts type mlir::Value. Again, tried to sniff some things up about converting between the two, but can’t find much. Any documentation about these things as well is greatly accepted.

Oh, I forgot about that, thank you both!!

Oh good point, didn’t notice that. The APInt class is a simple “arbitrary precision integer” where you can set the number of bits and do math - this is a concept independent of compilers. The only thing to be aware of here is that it is “arbitrary” but “defined” precision - you can have something that is 1017 bits, but adding two 1017 bit things together will give you a 1017 bit result.

A Value is an IR object - something that refers to the result of an operation or a bb argument in the code. To bridge the two, you need to create an rtl.constant node, with the APInt as its value. Something like this should work:

  auto cst = rewriter.create<ConstantOp>(value1);  // might need to be rtl::ConstantOp


Thanks. I used a slight variation of this, that followed what I saw in nearby files:
auto cst = rewriter.create<ConstantOp>(op.getLoc(), value1);

oh yep, good catch thanks!