Use Existing Operation as new

Hi,

Problem Statement is to write an operation which does exactly what scf ForOp do. I created an operation custom::CForOp which has the same .td defintion as scf::ForOp, with only getters written in the custom.cpp file.

def Custom_CForOp : Custom_Op<"for",
      [AutomaticAllocationScope, DeclareOpInterfaceMethods<LoopLikeOpInterface,
       ["getSingleInductionVar", "getSingleLowerBound", "getSingleStep",
        "getSingleUpperBound"]>,
       AllTypesMatch<["lowerBound", "upperBound", "step"]>,
       ConditionallySpeculatable,
       DeclareOpInterfaceMethods<RegionBranchOpInterface>,
       SingleBlockImplicitTerminator<"custom::YieldOp">,
       RecursiveMemoryEffects]> {
  let summary = "for operation";

  let arguments = (ins AnySignlessIntegerOrIndex:$lowerBound,
                       AnySignlessIntegerOrIndex:$upperBound,
                       AnySignlessIntegerOrIndex:$step,
                       Variadic<AnyType>:$initArgs);
  let results = (outs Variadic<AnyType>:$results);
  let regions = (region SizedRegion<1>:$region);

  let skipDefaultBuilders = 1;
  let builders = [
    OpBuilder<(ins "Value":$lowerBound, "Value":$upperBound, "Value":$step,
      CArg<"ValueRange", "std::nullopt">:$iterArgs,
      CArg<"function_ref<void(OpBuilder &, Location, Value, ValueRange)>",
           "nullptr">)>
  ];
   let extraClassDeclaration = [{ /* ..... SAME AS OF SCF FOROP.... */
  }];

}

def Custom_YieldOp : Custom_Op<"yield", [Pure, ReturnLike, Terminator,
    ParentOneOf<["CForOp"]>]> {
  let summary = "loop yield and termination operation";


  let arguments = (ins Variadic<AnyType>:$results);
  let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>];

  let assemblyFormat =
      [{  attr-dict ($results^ `:` type($results))? }];
}

Then i write a pass for it’s conversion.

template <class OpClass, class StdOpClass>
struct CustomForOpLowering : public OpConversionPattern<OpClass> {
public:
  using OpConversionPattern<OpClass>::OpConversionPattern;
  LogicalResult
  matchAndRewrite(OpClass customForOp,
                  typename OpConversionPattern<OpClass>::OpAdaptor adaptor,
                  ConversionPatternRewriter &rewriter) const override {
      //auto type = getBaseType(binOp.left().getType());
      rewriter.template replaceOpWithNewOp<StdOpClass>(
          customForOp, adaptor.getLowerBound() , adaptor.getUpperBound(), adaptor.getStep());
      return success();

  }
};

using CustomForOpLoweringToSCF = CustomForOpLowering<custom::CForOp, scf::ForOp>;

I am facing ld unrefenrced error :

undefined reference to `mlir::custom::CForOp::build(mlir::OpBuilder&, mlir::OperationState&, mlir::Value, mlir::Value, mlir::Value, mlir::ValueRange, llvm::function_ref<void (mlir::OpBuilder&, mlir::Location, mlir::Value, mlir::ValueRange)>)'
collect2: error: ld returned 1 exit status

please help as possible. my idea is to just have an operation which i can translate directly to scf::ForOp. Do i have to write the whole ForOp classes, definitions and logic again ?
It might be a trivial query as i am learning the codebase as a beginner.

I would advise reading the Toy tutorial. You have to include the generated .inc files:

Notice several includes of .inc files. The .inc files are generated by tblegen.

1 Like

That means your header structure is correct but either you’re missing including the .cpp.inc file in your implementation, or you’re not linking to your implementation.

1 Like

Hi,
I went through the suggested tutorials. Thanks so much, it helped in better understanding.
The inc files seem to be included fine as i am able to convert custom print operation into vector’s.

Checked the inc files. The linking seems to work for custom operation of constant op creation converting to arith’s constantop and custom print operation conversion to vector’s.

Though, for the operations i didnt have to implement any .cpp files other than the Conversions.
I guess, for ForOp i have to implement the logic as SCF’s. Going through the tutorials i realised this.

I deleted the getters. The code i have now is as following :

Ops.td

def Custom_ForOp : Custom_Op<"for",[AllTypesMatch<["lowerBound", "upperBound", "step"]>,SingleBlockImplicitTerminator<"custom::YieldOp">,
       RecursiveMemoryEffects]>{
        let summary = "for operation";
        let arguments = (ins AnySignlessIntegerOrIndex:$lowerBound,
                       AnySignlessIntegerOrIndex:$upperBound,
                       AnySignlessIntegerOrIndex:$step,
                       Variadic<AnyType>:$initArgs);
        let results = (outs Variadic<AnyType>:$results);
        let regions = (region SizedRegion<1>:$region);
        let builders = [
        OpBuilder<(ins "Value":$lowerBound, "Value":$upperBound, "Value":$step,
        CArg<"ValueRange", "std::nullopt">:$iterArgs,
        CArg<"function_ref<void(OpBuilder &, Location, Value, ValueRange)>",
           "nullptr">)>
        ];

       }
def Custom_YieldOp : Custom_Op<"yield", [Pure, ReturnLike, Terminator,
    ParentOneOf<["ForOp"]>]> {
        let summary = "loop yield and termination operation";
      let arguments = (ins Variadic<AnyType>:$results);
      let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>];

      let assemblyFormat = [{  attr-dict ($results^ `:` type($results))? }];
}

When i build it , i get the following build() in .h.inc file :

  static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, Value lowerBound, Value upperBound, Value step, ValueRange iterArgs = std::nullopt, function_ref<void(OpBuilder &, Location, Value, ValueRange)> odsArg4 = nullptr);
  static void build(::mlir::OpBuilder &odsBuilder, ::mlir::OperationState &odsState, ::mlir::TypeRange results, ::mlir::Value lowerBound, ::mlir::Value upperBound, ::mlir::Value step, ::mlir::ValueRange initArgs);
  static void build(::mlir::OpBuilder &, ::mlir::OperationState &odsState, ::mlir::TypeRange resultTypes, ::mlir::ValueRange operands, ::llvm::ArrayRef<::mlir::NamedAttribute> attributes = {});
  

The way i access :

auto res = builder.create<custom::ForOp>(loc, lb.getResult(), ub.getResult(), step.getResult(), ValueRange{}, [&](OpBuilder &b, Location loc, Value iv, ValueRange args){
        builder.create<custom::PrintFOp>(loc, qnty_filter_cond);
        b.create<custom::YieldOp>(loc, ValueRange{});
    });

Error :

/usr/bin/ld: lib/libMLIRCodeGenIR.a(TestDialect.cpp.o): in function `mlir::custom::ForOp mlir::OpBuilder::create<mlir::custom::ForOp, mlir::Value, mlir::Value, mlir::Value, mlir::ValueRange, generateMLIRForOps(mlir::MLIRContext&, mlir::OwningOpRef<mlir::ModuleOp>&, mlir::Location)::{lambda(mlir::OpBuilder&, mlir::Location, mlir::Value, mlir::ValueRange)#1}>(mlir::Location, mlir::Value&&, mlir::Value&&, mlir::Value&&, mlir::ValueRange&&, generateMLIRForOps(mlir::MLIRContext&, mlir::OwningOpRef<mlir::ModuleOp>&, mlir::Location)::{lambda(mlir::OpBuilder&, mlir::Location, mlir::Value, mlir::ValueRange)#1}&&)':
TestDialect.cpp:(.text._ZN4mlir9OpBuilder6createINS_6custom5ForOpEJNS_5ValueES4_S4_NS_10ValueRangeEZ18generateMLIRForOpsRNS_11MLIRContextERNS_11OwningOpRefINS_8ModuleOpEEENS_8LocationEEUlRS0_SC_S4_S5_E_EEET_SC_DpOT0_+0x142): undefined reference to `mlir::custom::ForOp::build(mlir::OpBuilder&, mlir::OperationState&, mlir::Value, mlir::Value, mlir::Value, mlir::ValueRange, llvm::function_ref<void (mlir::OpBuilder&, mlir::Location, mlir::Value, mlir::ValueRange)>)'
collect2: error: ld returned 1 exit status

You declared the builder in ODS but you didn’t provide any implementation for it.

Understood. I added the build method. then it worked fine. Thank you :slight_smile:

Although, when i convert it to scf loop, the statements inside the loop is lost.

mlir :

module {
  func.func @main() {
    %0 = custom.constant 2.400000e+01 : f64
    %c0 = arith.constant 0 : index
    %c1 = arith.constant 1 : index
    %c10 = arith.constant 10 : index
    "custom.for"(%c0, %c10, %c1) ({
    ^bb0(%arg0: index):
      custom.printf %0 : f64
      custom.yield
    }) : (index, index, index) -> ()
    return
  }
}

converted :

module {
  func.func @main() {
    %cst = arith.constant 2.400000e+01 : f64
    %c0 = arith.constant 0 : index
    %c1 = arith.constant 1 : index
    %c10 = arith.constant 10 : index
    scf.for %arg0 = %c0 to %c10 step %c1 {
    }
    return
  }
}

build defn :

void casair::ForOp::build(OpBuilder &builder, OperationState &result, Value lb,
                  Value ub, Value step, ValueRange iterArgs,
                  BodyBuilderFn bodyBuilder) {
  result.addOperands({lb, ub, step});
  result.addOperands(iterArgs);
  for (Value v : iterArgs)
    result.addTypes(v.getType());
  Type t = lb.getType();
  Region *bodyRegion = result.addRegion();
  bodyRegion->push_back(new Block);
  Block &bodyBlock = bodyRegion->front();
  bodyBlock.addArgument(t, result.location);
  for (Value v : iterArgs)
    bodyBlock.addArgument(v.getType(), v.getLoc());

  // Create the default terminator if the builder is not provided and if the
  // iteration arguments are not provided. Otherwise, leave this to the caller
  // because we don't know which values to return from the loop.
  if (iterArgs.empty() && !bodyBuilder) {
    ForOp::ensureTerminator(*bodyRegion, builder, result.location);
  } else if (bodyBuilder) {
    OpBuilder::InsertionGuard guard(builder);
    builder.setInsertionPointToStart(&bodyBlock);
    bodyBuilder(builder, result.location, bodyBlock.getArgument(0),
                bodyBlock.getArguments().drop_front());
  }
}

Conversion :

 matchAndRewrite(OpClass customForOp,
                  typename OpConversionPattern<OpClass>::OpAdaptor adaptor,
                  ConversionPatternRewriter &rewriter) const override {
      rewriter.template replaceOpWithNewOp<StdOpClass>(
          customForOp, adaptor.getLowerBound() , adaptor.getUpperBound(), adaptor.getStep(), adaptor.getInitArgs());
      return success();
.......
using CustomForOpLoweringToSCF = CustomForOpLowering<custom::ForOp, scf::ForOp>;

I don’t see any reason casair::ForOp::build would be ever used in this test right?

      rewriter.template replaceOpWithNewOp<StdOpClass>(
          customForOp, adaptor.getLowerBound() , adaptor.getUpperBound(), adaptor.getStep(), adaptor.getInitArgs());

The code is incomplete, so hard to tell, but it this the implementation of your CustomForOpLowering?
And StdOpClass would be scf::ForOp?
It’s not clear to me in the conversion process that you’re handling the body…

It seems my understanding is wrong. If i need to transform a new operation to an existing one, where the former is exactly what i wish it to do as the later one, i could just write the conversion for it to happen. In the above mentioned code for conversion, i am passing the arguments that scf::forop takes. However, the logic has to be written which i naively missed.
But i understand now. Thank you!