# RUN: mlir-linalg-ods-yaml-gen %s --o-ods-decl=- | FileCheck %s --check-prefix=ODS
# RUN: mlir-linalg-ods-yaml-gen %s --o-impl=- | FileCheck %s --check-prefix=IMPL

# @linalg_structured_op
# def test1(O=TensorDef(T, S.M, S.N, output=True),
#           cast=TypeFnAttrDef(default=TypeFn.cast_signed)):
#   """Title.

#   Detailed description.
#   """
#   O[D.m, D.n] = cast(T, const(42)) + cast(T, index(D.n))

--- !LinalgOpConfig
metadata: !LinalgOpMetadata
  name: test1
  cpp_class_name: Test1Op
  doc: |-
    Title.

    Detailed description.
structured_op: !LinalgStructuredOpConfig
  args:
  - !LinalgOperandDefConfig
    name: O
    kind: output_tensor
    type_var: T
    shape_map: affine_map<()[s0, s1] -> (s0, s1)>
  - !LinalgOperandDefConfig
    name: cast
    kind: type_fn_attr
    default_fn: cast_signed
  indexing_maps: !LinalgIndexingMapsConfig
    static_indexing_maps:
    - affine_map<(d0, d1)[s0, s1] -> (d0, d1)>
  iterator_types:
  - parallel
  - parallel
  assignments:
  - !ScalarAssign
    arg: O
    value: !ScalarExpression
      scalar_fn:
        kind: binary
        fn_name: add
        operands:
        - !ScalarExpression
          scalar_fn:
            kind: type
            attr_name: cast
            type_var: T
            operands:
            - !ScalarExpression
              scalar_const: '42 : i64'
        - !ScalarExpression
          scalar_fn:
            kind: type
            attr_name: cast
            type_var: T
            operands:
            - !ScalarExpression
              scalar_index: 1

# ODS-LABEL:  def Test1Op : LinalgStructuredBase_Op<"test1"

#       ODS:  let summary = [{Title.}];
#  ODS-NEXT:  let description = [{Detailed description.}];

#       ODS:  let arguments =
#  ODS-NEXT:    Variadic<AnyType>:$inputs,
#  ODS-NEXT:    Variadic<AnyShaped>:$outputs,
#  ODS-NEXT:    DefaultValuedOptionalAttr<TypeFnAttr, "TypeFn::cast_signed">:$cast

#       ODS:  let builders =
#       ODS:  (ins "TypeRange":$resultTensorTypes, "ValueRange":$inputs,
#  ODS-NEXT:       "ValueRange":$outputs,
#  ODS-NEXT:       CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes),

#       ODS:  (ins "TypeRange":$resultTensorTypes, "ValueRange":$inputs,
#  ODS-NEXT:       "ValueRange":$outputs, "Attribute":$cast,
#  ODS-NEXT:       CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes),

#       ODS:    buildStructuredOp($_builder, $_state, resultTensorTypes,
#  ODS-NEXT:      attributes, Test1Op::getRegionBuilder())

#       ODS:    MutableOperandRange getDpsInitsMutable() {
#  ODS-NEXT:      return getOutputsMutable()
#  ODS-NEXT:    }

# IMPL-LABEL:  void Test1Op::regionBuilder(ImplicitLocOpBuilder &b,
#  IMPL-NEXT:    Block &block, ArrayRef<NamedAttribute> attrs)
#       IMPL:  TypeFn castVal = TypeFn::cast_signed;
#  IMPL-NEXT:  auto castIter = llvm::find_if(attrs, [&](const NamedAttribute &attr) {
#  IMPL-NEXT:                                return attr.getName() == "cast"; });
#  IMPL-NEXT:  if (castIter != attrs.end()) {
#  IMPL-NEXT:    if (auto attr = llvm::dyn_cast<TypeFnAttr>(castIter->getValue()))
#  IMPL-NEXT:      castVal = attr.getValue();
#  IMPL-NEXT:  }

#       IMPL:  Value [[VAL0:[a-z0-9]+]] = helper.constant("42 : i64");
#   IMPL-DAG:  Value [[VAL1:[a-z0-9]+]] = helper.buildTypeFn(castVal, block.getArgument(0).getType(), [[VAL0]]);
#   IMPL-DAG:  Value [[VAL2:[a-z0-9]+]] = helper.index(1);
#   IMPL-DAG:  Value [[VAL3:[a-z0-9]+]] = helper.buildTypeFn(castVal, block.getArgument(0).getType(), [[VAL2]]);
#   IMPL-DAG:  Value [[VAL4:[a-z0-9]+]] = helper.buildBinaryFn(BinaryFn::add, [[VAL1]], [[VAL3]]);


# @linalg_structured_op
# def test2(I=TensorDef(T, S.M, S.N),
#           O=TensorDef(T, S.M, S.N, output=True),
#           strides=IndexAttrDef(S.SM, S.SN, default=[1, 2])):
#   """Title.

#   Detailed description.
#   """
#   O[D.m, D.n] = I[D.n * S.SM, D.m * S.SN]

--- !LinalgOpConfig
metadata: !LinalgOpMetadata
  name: test2
  cpp_class_name: Test2Op
  doc: |-
    Title.

    Detailed description.
structured_op: !LinalgStructuredOpConfig
  args:
  - !LinalgOperandDefConfig
    name: I
    kind: input_tensor
    type_var: T
    shape_map: affine_map<()[s0, s1, s2, s3] -> (s0, s1)>
  - !LinalgOperandDefConfig
    name: O
    kind: output_tensor
    type_var: T
    shape_map: affine_map<()[s0, s1, s2, s3] -> (s0, s1)>
  - !LinalgOperandDefConfig
    name: strides
    kind: index_attr
    index_attr_map: affine_map<()[s0, s1, s2, s3] -> (s2, s3)>
    default_indices:
    - 1
    - 2
  indexing_maps: !LinalgIndexingMapsConfig
    static_indexing_maps:
    - affine_map<(d0, d1)[s0, s1, s2, s3] -> (d1 * s2, d0 * s3)>
    - affine_map<(d0, d1)[s0, s1, s2, s3] -> (d0, d1)>
  iterator_types:
  - parallel
  - parallel
  assignments:
  - !ScalarAssign
    arg: O
    value: !ScalarExpression
      scalar_arg: I

# ODS-LABEL:  def Test2Op : LinalgStructuredBase_Op<"test2"

#       ODS:  let arguments =
#  ODS-NEXT:    Variadic<AnyType>:$inputs,
#  ODS-NEXT:    Variadic<AnyShaped>:$outputs,
#  ODS-NEXT:    DefaultValuedOptionalAttr<RankedI64ElementsAttr<[2]>
#  ODS-SAME:    "{ static_cast<int64_t>(1), static_cast<int64_t>(2) }">:$strides

#       ODS:  "Attribute":$strides
#       ODS:  $_state.addAttribute("strides", strides);

#       ODS:  bool hasDynamicIndexingMaps();
#  ODS-NEXT:  LogicalResult verifyIndexingMapRequiredAttributes();

#       IMPL:  getSymbolBindings(Test2Op self)
#       IMPL:  cst2 = self.getStrides().getValues<int64_t>()[0];
#  IMPL-NEXT:  getAffineConstantExpr(cst2, context)
#       IMPL:  cst3 = self.getStrides().getValues<int64_t>()[1];
#  IMPL-NEXT:  getAffineConstantExpr(cst3, context)

#       IMPL:  Test2Op::getIndexingMaps()
#       IMPL:  = getSymbolBindings(*this);
#       IMPL:  "affine_map<(d0, d1)[s0, s1, s2, s3] -> (d1 * s2, d0 * s3)>"
#       IMPL:  "affine_map<(d0, d1)[s0, s1, s2, s3] -> (d0, d1)>"

#       IMPL:  Test2Op::getNumRegionArgs() { return 2; }

#       IMPL:  Test2Op::hasDynamicIndexingMaps() { return true; }
#       IMPL:  Test2Op::verifyIndexingMapRequiredAttributes()
#       IMPL:  auto attr = op->getAttrOfType<DenseElementsAttr>("strides")
#       IMPL:  "incorrect element type for index attribute 'strides'"
#       IMPL:  "incorrect shape for index attribute 'strides'"
#       IMPL:  void Test2Op::regionBuilder(ImplicitLocOpBuilder &b,
#  IMPL-NEXT:    Block &block, ArrayRef<NamedAttribute> attrs)
#  IMPL-NEXT:    assert(2 > 0 && block.getNumArguments() == 2 &&

#       IMPL:   yields.push_back(block.getArgument(0));

# @linalg_structured_op
# def test3(value=ScalarDef(T1),
#           O=TensorDef(U, output=True)):
#   """Title.

#   Detailed description.
#   """
#   O[None] = TypeFn.cast_signed(U, value)

--- !LinalgOpConfig
metadata: !LinalgOpMetadata
  name: test3
  cpp_class_name: Test3Op
  doc: |-
    Title.

    Detailed description.
structured_op: !LinalgStructuredOpConfig
  args:
  - !LinalgOperandDefConfig
    name: value
    kind: scalar
    type_var: T1
  - !LinalgOperandDefConfig
    name: O
    kind: output_tensor
    type_var: U
    shape_map: affine_map<() -> ()>
  indexing_maps: !LinalgIndexingMapsConfig
    static_indexing_maps:
    - affine_map<() -> ()>
    - affine_map<() -> ()>
  iterator_types: []
  assignments:
  - !ScalarAssign
    arg: O
    value: !ScalarExpression
      scalar_fn:
        kind: type
        fn_name: cast_signed
        type_var: U
        operands:
        - !ScalarExpression
          scalar_arg: value

#       IMPL:  Test3Op::getIteratorTypesArray() {
#  IMPL-NEXT:    int64_t rank = getRank(getDpsInitOperand(0));

#       IMPL:  Test3Op::getIndexingMaps() {
#  IMPL-NEXT:    MLIRContext *context = getContext();
#  IMPL-NEXT:    AffineMap scalarMap = AffineMap::get(getNumParallelLoops(), 0, context);
#  IMPL-NEXT:    AffineMap tensorMap = AffineMap::getMultiDimIdentityMap(

# @linalg_structured_op
# def test4(O=TensorDef(T, S.M, S.N, output=True),
#           unary_fun=UnaryFnAttrDef(default=UnaryFn.exp),
#           binary_fun=BinaryFnAttrDef(default=BinaryFn.add)):
#   """Title.

#   Detailed description.
#   """
#   O[D.m, D.n] = binary_fun(unary_fun(O[D.m, D.n]), O[D.m, D.n])

--- !LinalgOpConfig
metadata: !LinalgOpMetadata
  name: test4
  cpp_class_name: Test4Op
  doc: |-
    Title.

    Detailed description.
structured_op: !LinalgStructuredOpConfig
  args:
  - !LinalgOperandDefConfig
    name: O
    kind: output_tensor
    type_var: T
    shape_map: affine_map<()[s0, s1] -> (s0, s1)>
  - !LinalgOperandDefConfig
    name: unary_fun
    kind: unary_fn_attr
    default_fn: exp
  - !LinalgOperandDefConfig
    name: binary_fun
    kind: binary_fn_attr
    default_fn: add
  indexing_maps: !LinalgIndexingMapsConfig
    static_indexing_maps:
    - affine_map<(d0, d1)[s0, s1] -> (d0, d1)>
  iterator_types:
  - parallel
  - parallel
  assignments:
  - !ScalarAssign
    arg: O
    value: !ScalarExpression
      scalar_fn:
        kind: binary
        attr_name: binary_fun
        operands:
        - !ScalarExpression
          scalar_fn:
            kind: unary
            attr_name: unary_fun
            operands:
            - !ScalarExpression
              scalar_arg: O
        - !ScalarExpression
          scalar_arg: O

# ODS-LABEL:  def Test4Op : LinalgStructuredBase_Op<"test4"

#       ODS:  let arguments =
#  ODS-NEXT:    Variadic<AnyType>:$inputs,
#  ODS-NEXT:    Variadic<AnyShaped>:$outputs,
#  ODS-NEXT:    DefaultValuedOptionalAttr<UnaryFnAttr, "UnaryFn::exp">:$unary_fun,
#  ODS-NEXT:    DefaultValuedOptionalAttr<BinaryFnAttr, "BinaryFn::add">:$binary_fun

#       ODS:    "Attribute":$unary_fun, "Attribute":$binary_fun,

#       ODS:    $_state.addAttribute("unary_fun", unary_fun)
#  ODS-NEXT:    $_state.addAttribute("binary_fun", binary_fun)

# IMPL-LABEL:  void Test4Op::regionBuilder(ImplicitLocOpBuilder &b,
#  IMPL-NEXT:    Block &block, ArrayRef<NamedAttribute> attrs)
#       IMPL:  UnaryFn unary_funVal = UnaryFn::exp
#       IMPL:  BinaryFn binary_funVal = BinaryFn::add

#       IMPL:  Value [[VAL0:[a-z0-9]+]] = helper.buildUnaryFn(unary_funVal, block.getArgument(0))
#  IMPL-NEXT:  Value [[VAL1:[a-z0-9]+]] = helper.buildBinaryFn(binary_funVal, [[VAL0]], block.getArgument(0))
#  IMPL-NEXT:  yields.push_back([[VAL1]])

# @linalg_structured_op
# def test5(value=ScalarDef(T1), O=TensorDef(U, output=True)):
#   """Title.

#   Detailed description.
#   """
#   implements(FillOpInterface)
#   defines(Canonicalizer)
#   O[None] = TypeFn.cast(U, value)

--- !LinalgOpConfig
metadata: !LinalgOpMetadata
  name: test5
  cpp_class_name: Test5Op
  doc: |-
    Title.

    Detailed description.
  implements:
  - LinalgFillOpInterface
  defines:
  - hasCanonicalizer
structured_op: !LinalgStructuredOpConfig
  args:
  - !LinalgOperandDefConfig
    name: value
    kind: scalar
    type_var: T1
  - !LinalgOperandDefConfig
    name: O
    kind: output_tensor
    type_var: U
    shape_map: affine_map<() -> ()>
  indexing_maps: !LinalgIndexingMapsConfig
    static_indexing_maps:
    - affine_map<() -> ()>
    - affine_map<() -> ()>
  iterator_types: []
  assignments:
  - !ScalarAssign
    arg: O
    value: !ScalarExpression
      scalar_fn:
        kind: type
        fn_name: cast
        type_var: U
        operands:
        - !ScalarExpression
          scalar_arg: value

# ODS-LABEL:  def Test5Op : LinalgStructuredBase_Op<"test5"
#  ODS-NEXT:  /*extraInterfaces=*/[LinalgFillOpInterface])>

#       ODS:  let hasCanonicalizer = 1;
