# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import logging
import os
import pathlib
import sys

import yaml
from decomp_interface_gen_op_list import decomp_interface_declare_gen_op_list
from op_build_gen import gen_build_func_str, gen_build_func_str_by_invoke
from op_interface_gen import (
    gen_exclusive_interface_str,
    gen_op_infer_meta_str,
    gen_op_vjp_str,
)
from op_kerneltype_gen import gen_kernel_type_for_var_str
from op_member_func_gen import gen_op_get_inputs_outputs_str
from op_verify_gen import gen_verify_func_str
from parse_kernel_key_gen import gen_parse_kernel_key_str
from vjp_interface_black_list import vjp_interface_black_list

# import from paddle/fluid/primitive/code_gen/gen.py
sys.path.append(
    str(pathlib.Path(__file__).resolve().parents[3] / 'primitive/codegen')
)
import gen as vjp_gen

# =====================================
# String Template for h file code gen
# =====================================
NAMESPACE_GARD_TEMPLATE = """namespace {namespace} {{
{input}
}} // namespace {namespace}"""

H_FILE_TEMPLATE = """// This file is generated by "paddle/fluid/pir/dialect/op_generator/op_gen.py"
#pragma once
#include <vector>

#include "paddle/pir/core/builder.h"
#include "paddle/pir/core/operation_utils.h"
#include "paddle/pir/core/op_base.h"
#include "paddle/pir/core/op_trait.h"
#include "paddle/fluid/pir/dialect/operator/utils/utils.h"
#include "paddle/fluid/pir/dialect/operator/utils/op_yaml_info_util.h"
#include "paddle/fluid/pir/dialect/operator/interface/op_yaml_info.h"
#include "paddle/fluid/pir/dialect/operator/interface/infermeta.h"
#include "paddle/fluid/pir/dialect/operator/interface/vjp.h"
#include "paddle/fluid/pir/dialect/operator/interface/parse_kernel_key.h"
#include "paddle/fluid/pir/dialect/operator/interface/decomp.h"
#include "paddle/fluid/pir/dialect/operator/trait/inplace.h"
#include "paddle/fluid/pir/dialect/operator/trait/custom_vjp.h"
#include "paddle/fluid/framework/infershape_utils.h"
#include "paddle/phi/core/infermeta_utils.h"
#include "paddle/fluid/pir/dialect/operator/ir/manual_op.h"
#include "paddle/fluid/ir_adaptor/translator/utils.h"
{only_pd_op_header_files}

{op_to_multi_kernels_map}

{input}

{declare_type_id}
"""

OP_TO_MULTI_KERNELS_MAP_H = """
extern std::unordered_map<std::string, std::vector<PdOpSig>> op_to_multi_kernels_map;
"""

GET_OP_LIST_TEMPALTE = """{}
"""

DECLARE_OP_TYPE_ID = """
IR_DECLARE_EXPLICIT_TYPE_ID({op_name})
"""

OP_DECLARE_TEMPLATE = """
class {op_name} : public pir::Op<{op_name}{interfaces}{traits}> {{
 public:
  using Op::Op;
  static const char *name() {{ return "{dialect_op_name}"; }}
  {attribute_declare}
  static constexpr uint32_t attributes_num = {attribute_num};
  static OpInfoTuple GetOpInfo();
  static void Build({build_args});
  {build_mutable_attr_is_input}
  {build_attr_num_over_1}
  {build_mutable_attr_is_input_attr_num_over_1}
  void VerifySig();
{get_kernel_type_for_var_declare}
{parse_kernel_key_declare}
{get_inputs_and_outputs}
{exclusive_interface}
}};
"""
op_0_attribute_declare_str = (
    "static constexpr const char **attributes_name = nullptr;"
)
op_n_attribute_declare_str = (
    "static const char *attributes_name[{attribute_num}];"
)

get_kernel_type_for_var_declare_template = """
  static phi::DataType GetKernelTypeForVar(
      const std::string& var_name,
        const phi::DataType& tensor_dtype,
        const phi::DataType& expected_kernel_dtype);
"""

parse_kernel_key_template = """
  static std::tuple<phi::DataType, phi::Backend> ParseKernelKey(pir::Operation *op);
"""

# =====================================
# String Template for cc file code gen
# =====================================
CC_FILE_TEMPLATE = """#ifdef GET_OP_LIST
#undef GET_OP_LIST
{op_declare}
#else
// This file is generated by "paddle/fluid/pir/dialect/op_generator/op_gen.py"
#include "{h_file}"
#include "paddle/fluid/pir/dialect/operator/ir/op_type.h"
#include "paddle/fluid/pir/dialect/operator/ir/op_attribute.h"
#include "paddle/fluid/pir/dialect/operator/ir/ir_tensor.h"
#include "paddle/fluid/pir/dialect/operator/ir/ir_meta_tensor.h"
#include "paddle/pir/core/builtin_attribute.h"
#include "paddle/pir/core/builtin_type.h"
#include "paddle/pir/core/builtin_op.h"
#include "paddle/pir/core/ir_context.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/infermeta/binary.h"
#include "paddle/phi/infermeta/multiary.h"
#include "paddle/phi/infermeta/nullary.h"
#include "paddle/phi/infermeta/unary.h"
#include "paddle/phi/infermeta/ternary.h"
#include "paddle/phi/infermeta/backward.h"
#include "paddle/phi/infermeta/fusion.h"
#include "paddle/phi/api/lib/utils/allocator.h"
#include "paddle/fluid/primitive/rule/vjp/vjp.h"
#include "paddle/pir/core/op_base.h"

{op_to_multi_kernels_map}

{input}

{define_type_id}
#endif
"""
# =====================================
# String Template for pd_op_vjp.cc file code gen
# =====================================
VJP_CC_FILE_TEMPLATE = """// This file is generated by "paddle/fluid/pir/dialect/op_generator/op_gen.py"
#include "paddle/fluid/pir/dialect/operator/ir/op_attribute.h"
#include "paddle/fluid/pir/dialect/operator/ir/pd_op.h"
#include "paddle/fluid/primitive/rule/vjp/vjp.h"
#include "paddle/fluid/primitive/type/lazy_tensor.h"
#include "paddle/pir/core/builtin_op.h"
#include "paddle/pir/core/op_base.h"
#include "paddle/phi/common/int_array.h"
#include "paddle/fluid/pir/dialect/operator/utils/utils.h"

namespace paddle {{
namespace dialect {{
{input}
}}  // namespace dialect
}}  // namespace paddle
"""

OP_N_ATTRIBUTE_DEFINED_TEMPLATE = """
const char *{op_name}::attributes_name[{attribute_num}] = {{ {attribute_names} }};
"""

# get op info
OP_INFO_TEMPLATE = """
OpInfoTuple {op_name}::GetOpInfo() {{
  std::vector<paddle::dialect::OpInputInfo> inputs = {{ {inputs} }};
  std::vector<paddle::dialect::OpAttributeInfo> attributes = {{ {attributes} }};
  std::vector<paddle::dialect::OpOutputInfo> outputs = {{ {outputs} }};
  paddle::dialect::OpRunTimeInfo run_time_info = paddle::dialect::OpRunTimeInfo("{infer_meta_func}", {{"{infer_meta_param}"}}, "{kernel_func}", {{"{kernel_param}"}}, {{{kernel_key_dtype}}}, {{{kernel_key_backend}}}, {{{inplace}}}, {{{view}}});
  return std::make_tuple(inputs, attributes, outputs, run_time_info, "{origin_op_name}");
}}
"""
CONSTRUCT_INPUT_INFO_TEMPLATE = """paddle::dialect::OpInputInfo("{name}", "{typename}", {optional}, {no_need_buffer}, {is_mutable_attribute}, {with_grad_semantic})"""
CONSTRUCT_OUTPUT_INFO_TEMPLATE = """paddle::dialect::OpOutputInfo("{name}", "{typename}", {optional}, {intermediate})"""
CONSTRUCT_ATTRIBUTE_INFO_TEMPLATE = """paddle::dialect::OpAttributeInfo("{name}", "{typename}", "{data_type}")"""


DEFINE_OP_TYPE_ID = """
IR_DEFINE_EXPLICIT_TYPE_ID({op_name})
"""

OP_TO_MULTI_KERNELS_MAPS = """
std::unordered_map<std::string, std::vector<PdOpSig>> op_to_multi_kernels_map = {{
{maps}
}};
"""

scalar_type_maps = {
    'int': 'pir::Int32Attribute',
    'int64_t': 'pir::Int64Attribute',
    'float': 'pir::FloatAttribute',
    'dobule': 'pir::DoubleAttribute',
    'bool': 'pir::BoolAttribute',
}

PD_MANUAL_OP_LIST = {
    'add_n',
    'add_n_',
    'add_n_with_kernel',
    'split_grad',
    'expand',
}

attr_types_map = {
    'IntArray': ['paddle::dialect::IntArrayAttribute', 'IntArray'],
    'Scalar': ['paddle::dialect::ScalarAttribute', 'Scalar'],
    'Scalar(int)': ['pir::Int32Attribute', 'int'],
    'Scalar(int64_t)': ['pir::Int64Attribute', 'int64_t'],
    'Scalar(float)': ['pir::FloatAttribute', 'float'],
    'Scalar(double)': ['pir::DoubleAttribute', 'double'],
    'Scalar[]': [
        'pir::ArrayAttribute<paddle::dialect::ScalarAttribute>',
        'const std::vector<Scalar>&',
    ],
    'int': ['pir::Int32Attribute', 'int'],
    'int32_t': ['pir::Int32Attribute', 'int32_t'],
    'int64_t': ['pir::Int64Attribute', 'int64_t'],
    'long': ['pir::LongAttribute', 'long'],
    'size_t': ['pir::Size_tAttribute', 'size_t'],
    'float': ['pir::FloatAttribute', 'float'],
    'float[]': [
        'pir::ArrayAttribute<pir::FloatAttribute>',
        'const std::vector<float>&',
    ],
    'double': ['pir::DoubleAttribute', 'double'],
    'bool': ['pir::BoolAttribute', 'bool'],
    'bool[]': [
        'pir::ArrayAttribute<pir::BoolAttribute>',
        'const std::vector<bool>&',
    ],
    'str': ['pir::StrAttribute', 'const std::string&'],
    'str[]': [
        'pir::ArrayAttribute<pir::StrAttribute>',
        'const std::vector<std::string>&',
    ],
    'Place': ['paddle::dialect::PlaceAttribute', 'const Place&'],
    'DataLayout': [
        'paddle::dialect::DataLayoutAttribute',
        'DataLayout',
    ],
    'DataType': ['paddle::dialect::DataTypeAttribute', 'DataType'],
    'int64_t[]': [
        'pir::ArrayAttribute<pir::Int64Attribute>',
        'const std::vector<int64_t>&',
    ],
    'int[]': [
        'pir::ArrayAttribute<pir::Int32Attribute>',
        'const std::vector<int>&',
    ],
}


def to_phi_and_fluid_op_name(op_item):
    # Templat: - op : phi_name (fluid_name)
    names = op_item.split('(')
    if len(names) == 1:
        phi_fluid_name = names[0].strip()
        return phi_fluid_name, phi_fluid_name
    else:
        phi_name = names[0].strip()
        fluid_name = names[1].split(')')[0].strip()
        return phi_name, fluid_name


def to_phi_and_fluid_grad_op_name(op_item):
    # Templat: sum_grad (reduce_sum_grad), sum_double_grad
    rtn = []
    all_names = op_item.split(', ')
    for name in all_names:
        backward_phi_name, backward_fluid_name = to_phi_and_fluid_op_name(name)
        rtn.append([backward_phi_name, backward_fluid_name])
    return rtn


# =====================================
# Parse Op Compat From Yaml
# =====================================
class OpCompatParser:
    def __init__(self, ops_compat_yaml_file):
        self.ops_compat_yaml_file = ops_compat_yaml_file
        with open(self.ops_compat_yaml_file, "r") as f:
            self.ops_compat = yaml.safe_load(f)

    def get_compat(self, op_name):
        for compat in self.ops_compat:
            forward_phi_name, forward_fluid_name = to_phi_and_fluid_op_name(
                compat['op']
            )
            if op_name == forward_phi_name:
                return compat
            elif 'backward' in compat.keys():
                bkw_names = to_phi_and_fluid_grad_op_name(compat['backward'])
                for name in bkw_names:
                    if op_name == name[0]:
                        return compat
        return None

    def parse_support_tensor(self, op):
        scalar_item = {}
        int_array_item = {}
        for support_tensor_attr in op['support_tensor']:
            for attr in op['attrs']:
                if (
                    attr['typename'] == 'Scalar'
                    and attr['name'] == support_tensor_attr
                ):
                    scalar_item[support_tensor_attr] = {"support_tensor": True}
                if (
                    attr['typename'] == 'IntArray'
                    and attr['name'] == support_tensor_attr
                ):
                    scalar_item[support_tensor_attr] = {"support_tensor": True}
        return scalar_item, int_array_item


# =====================================
# Parse Op Information From Yaml
# =====================================
class OpInfoParser:
    def __init__(self, op_yaml_item, op_compat_item):
        self.op_yaml_item = op_yaml_item
        self.op_compat_item = op_compat_item
        self.op_phi_name = self.parse_op_phi_name()

        self.kernel_map = self.parse_kernel_map()

        # parse inputs
        self.input_name_list = self.parse_input_name_list()
        self.input_type_list = self.parse_input_type_list()
        self.input_type_dict = self.parse_input_type_dict()
        self.input_optional_list = self.parse_input_optional_list()
        self.input_no_need_buffer_list = self.parse_input_no_need_buffer_list()
        self.cross_check(
            self.input_name_list, self.input_type_list, self.input_optional_list
        )

        # parse outputs
        self.output_name_list = self.parse_output_name_list()
        self.output_type_list = self.parse_output_type_list()
        self.output_type_dict = self.parse_output_type_dict()
        self.output_size_list = self.parse_output_size_list()
        self.output_optional_list = self.parse_output_optional_list()
        self.output_intermediate_list = self.parse_output_intermediate_list()
        self.cross_check(
            self.output_name_list,
            self.output_type_list,
            self.output_optional_list,
        )

        # parse attributes
        self.attr_types_map = attr_types_map
        self.attribute_name_list = self.parse_attribute_name_list()
        self.attribute_type_list = self.parse_attribute_type_list()
        self.attribute_build_arg_type_list = (
            self.parse_attribute_build_arg_type_list()
        )
        self.attribute_gen_arg_type_list = (
            self.parse_attribute_gen_arg_type_list()
        )
        self.attribute_data_type_list = self.parse_attribute_data_type_list()
        self.attribute_default_value_list = (
            self.parse_attribute_default_value_list()
        )
        self.cross_check(self.attribute_name_list, self.attribute_type_list)

        # parse mutable attributes (as inputs)
        (
            self.mutable_attribute_name_list,
            self.mutable_attribute_type_list,
        ) = self.parse_mutable_attribute()

        (
            self.non_mutable_attribute_name_list,
            self.non_mutable_attribute_type_list,
            self.non_mutable_attribute_data_type_list,
            self.non_mutable_attribute_build_arg_type_list,
            self.non_mutable_attribute_default_value_list,
        ) = self.parse_non_nutable_attribute()

        # parse infermeta && kernel
        self.infer_meta_map = self.parse_infer_meta_map()
        self.invoke_map = self.parse_invoke_map()
        if 'infer_meta' in self.op_yaml_item:
            self.infer_meta_func = self.op_yaml_item['infer_meta']["func"]
        else:
            self.infer_meta_func = None

        # parse backward name
        self.backward_name = self.parse_backward_name()

        # parse inplace && view
        self.inplace_map = self.parse_op_inplace_info()
        self.view_map = self.parse_op_view_info()

        # parse data_transform
        self.data_transform_map = self.parse_data_transform_info()

        # parse has_custom_verify
        self.custom_verify = self.parse_custom_verify()

        # parse forward input name list and attribute name list
        self.forward_input_name_list = self.parse_forward_input_name()

        # parse forward output name list
        self.forward_output_name_list = self.parse_forward_output_name()

        # parse traits list
        self.traits_list = self.parse_op_traits()

        # parse interfaces list
        self.interfaces_list = self.parse_op_interfaces()

    def parse_op_traits(self):
        if 'traits' in self.op_yaml_item:
            return self.op_yaml_item['traits']
        else:
            return []

    def parse_op_interfaces(self):
        if 'interfaces' in self.op_yaml_item:
            return self.op_yaml_item['interfaces']
        else:
            return []

    def parse_forward_input_name(self):
        if 'forward' in self.op_yaml_item:
            forward_input_name_list = []
            forward_map = self.op_yaml_item['forward']
            if forward_map is not None:
                inputs = forward_map['inputs']
                for input in inputs:
                    forward_input_name_list.append(input['name'])
                return forward_input_name_list
            else:
                return None
        else:
            return None

    def parse_forward_output_name(self):
        if 'forward' in self.op_yaml_item:
            forward_output_name_list = []
            forward_map = self.op_yaml_item['forward']
            if forward_map is not None:
                outputs = forward_map['outputs']
                for output in outputs:
                    forward_output_name_list.append(output['name'])
                return forward_output_name_list
            else:
                return None
        else:
            return None

    def cross_check(self, name_list, type_list, optional_list=None):
        assert len(name_list) == len(
            type_list
        ), "name list size != type list size."
        if optional_list is not None:
            assert len(name_list) == len(
                optional_list
            ), "type list size != optional list size."

    def parse_custom_verify(self):
        if 'custom_verify' in self.op_yaml_item:
            return self.op_yaml_item['custom_verify']
        return False

    def parse_op_phi_name(self):
        if (self.parse_op_inplace_info() is None) and (
            self.parse_op_view_info() is None
        ):
            return [self.op_yaml_item['name']]
        else:
            if self.op_yaml_item['name'][-1] == "_":
                return [self.op_yaml_item['name']]
            else:
                return [
                    self.op_yaml_item['name'],
                    self.op_yaml_item['name'] + "_",
                ]

    def parse_op_inplace_info(self):
        if 'inplace' in self.op_yaml_item:
            return self.op_yaml_item['inplace']
        return None

    def parse_op_view_info(self):
        if 'view' in self.op_yaml_item:
            return self.op_yaml_item['view']
        return None

    def is_mutable_attribute(self, attr_dict):
        if (
            'support_tensor' in attr_dict
            and attr_dict['support_tensor'] is True
        ):
            return True
        elif 'tensor_name' in attr_dict or 'tensors_name' in attr_dict:
            return True
        else:
            return False

    def parse_mutable_attribute(self):
        """
        {'axis': 'paddle::dialect::ScalarAttribute', 'rotl': 'paddle::dialect::IntArrayAttribute'}
        """
        mutable_attribute_name_list = []
        mutable_attribute_type_list = []
        # scalar
        if (self.op_compat_item is not None) and (
            'scalar' in self.op_compat_item
        ):
            for scalar_attr in self.op_compat_item['scalar'].keys():
                if not self.is_mutable_attribute(
                    self.op_compat_item['scalar'][scalar_attr]
                ):
                    continue
                if 'data_type' in self.op_compat_item['scalar'][scalar_attr]:
                    if (
                        scalar_attr == "depth"
                        and self.op_phi_name[0] == "one_hot"
                    ):
                        mutable_attribute_name_list.append("num_classes")
                    else:
                        mutable_attribute_name_list.append(scalar_attr)
                    data_type = self.op_compat_item['scalar'][scalar_attr][
                        'data_type'
                    ]
                    # patch for isclose and allclose
                    if (self.op_compat_item['op'] == "isclose") or (
                        self.op_compat_item['op'] == "allclose"
                    ):
                        data_type = "double"
                    mutable_attribute_type_list.append(
                        [
                            "paddle::dialect::ScalarAttribute",
                            data_type,
                        ]
                    )
                # See eye in op_compat.yaml
                else:
                    mutable_attribute_name_list.append(scalar_attr)
                    mutable_attribute_type_list.append(
                        [
                            "paddle::dialect::ScalarAttribute",
                            self.attribute_data_type_list[
                                self.attribute_name_list.index(scalar_attr)
                            ],
                        ]
                    )
        # int_array
        if (self.op_compat_item is not None) and (
            'int_array' in self.op_compat_item
        ):
            for int_array_attr in self.op_compat_item['int_array']:
                if not self.is_mutable_attribute(
                    self.op_compat_item['int_array'][int_array_attr]
                ):
                    continue
                mutable_attribute_name_list.append(int_array_attr)
                mutable_attribute_type_list.append(
                    [
                        "paddle::dialect::IntArrayAttribute",
                        self.op_compat_item['int_array'][int_array_attr][
                            'data_type'
                        ],
                    ]
                )
        sorted_mutable_attribute_name_list = []
        sorted_mutable_attribute_type_list = []
        for attr_name in self.attribute_name_list:
            if attr_name in mutable_attribute_name_list:
                sorted_mutable_attribute_name_list.append(attr_name)
                sorted_mutable_attribute_type_list.append(
                    mutable_attribute_type_list[
                        mutable_attribute_name_list.index(attr_name)
                    ]
                )

        return (
            sorted_mutable_attribute_name_list,
            sorted_mutable_attribute_type_list,
        )

    def parse_non_nutable_attribute(self):
        op_non_mutable_attribute_name_list = []
        op_non_mutable_attribute_type_list = []
        op_non_mutable_attribute_data_type_list = []
        op_non_mutable_attribute_build_arg_type_list = []
        op_non_mutable_attribute_default_value_list = []
        for idx in range(len(self.attribute_name_list)):
            if (
                self.attribute_name_list[idx]
                not in self.mutable_attribute_name_list
            ):
                op_non_mutable_attribute_name_list.append(
                    self.attribute_name_list[idx]
                )
                op_non_mutable_attribute_type_list.append(
                    self.attribute_type_list[idx]
                )
                op_non_mutable_attribute_data_type_list.append(
                    self.attribute_data_type_list[idx]
                )
                op_non_mutable_attribute_build_arg_type_list.append(
                    self.attribute_build_arg_type_list[idx]
                )
                op_non_mutable_attribute_default_value_list.append(
                    self.attribute_default_value_list[idx]
                )
        return (
            op_non_mutable_attribute_name_list,
            op_non_mutable_attribute_type_list,
            op_non_mutable_attribute_data_type_list,
            op_non_mutable_attribute_build_arg_type_list,
            op_non_mutable_attribute_default_value_list,
        )

    def parse_input_name_list(self):
        name_list = []
        for input_info in self.op_yaml_item['inputs']:
            name_list.append(input_info['name'])
        return name_list

    def parse_input_type_list(self):
        input_types_map = {
            'Tensor': 'paddle::dialect::DenseTensorType',
            'Tensor[]': 'pir::VectorType<paddle::dialect::DenseTensorType>',
        }
        type_list = []
        for input_info in self.op_yaml_item['inputs']:
            assert (
                input_info['typename'] in input_types_map
            ), f"{self.op_phi_name} : Input type error: the input type only support Tensor and Tensor[], but now is {input_info['typename']}."
            type_list.append(input_types_map[input_info['typename']])
        return type_list

    def parse_input_type_dict(self):
        type_dict = {}

        if (
            self.kernel_map is None
            or self.kernel_map['dispatch'][self.kernel_map['func'][0]] is None
        ):
            input_types_map = {
                'Tensor': 'paddle::dialect::DenseTensorType',
                'Tensor[]': 'pir::VectorType<paddle::dialect::DenseTensorType>',
            }
            type_list = []
            for input_info in self.op_yaml_item['inputs']:
                assert (
                    input_info['typename'] in input_types_map
                ), f"{self.op_phi_name} : Input type error: the input type only support Tensor and Tensor[], but now is {input_info['typename']}."
                type_list.append(input_types_map[input_info['typename']])

            if self.kernel_map is None:
                type_dict['default'] = type_list
            else:
                type_dict[self.kernel_map['func'][0]] = type_list

        else:
            input_types_map = {
                'dense': 'paddle::dialect::DenseTensorType',
                'selected_rows': 'paddle::dialect::SelectedRowsType',
            }

            for kernel_func_name in self.kernel_map['func']:
                inputs = self.kernel_map['dispatch'][kernel_func_name][0]
                type_list = []
                for input_info in inputs:
                    assert (
                        input_info in input_types_map
                    ), f"{self.op_phi_name} : Input type error: the input type only support dense and selected_rows, but now is {input_info}."
                    type_list.append(input_types_map[input_info])

                type_dict[kernel_func_name] = type_list

        return type_dict

    def parse_input_optional_list(self):
        optional_list = []
        for input_info in self.op_yaml_item['inputs']:
            if input_info['optional']:
                optional_list.append("true")
            else:
                optional_list.append("false")
        return optional_list

    def parse_input_no_need_buffer_list(self):
        no_need_buffer_list = []
        for input_info in self.op_yaml_item['inputs']:
            if input_info['no_need_buffer']:
                no_need_buffer_list.append("true")
            else:
                no_need_buffer_list.append("false")
        return no_need_buffer_list

    def parse_output_name_list(self):
        name_list = []
        for output_info in self.op_yaml_item['outputs']:
            name_list.append(output_info['name'])
        return name_list

    def parse_output_type_list(self):
        output_type_map = {
            'Tensor': 'paddle::dialect::DenseTensorType',
            'Tensor[]': 'pir::VectorType<paddle::dialect::DenseTensorType>',
            'SelectedRows': 'paddle::dialect::SelectedRowsType',
        }
        type_list = []
        for output_info in self.op_yaml_item['outputs']:
            assert (
                output_info['typename'] in output_type_map
            ), f"{self.op_phi_name} : Output type error: the output type only support Tensor and Tensor[], but now is {output_info['typename']}."
            type_list.append(output_type_map[output_info['typename']])
        return type_list

    def parse_output_type_dict(self):
        type_dict = {}

        if (
            self.kernel_map is None
            or self.kernel_map['dispatch'][self.kernel_map['func'][0]] is None
        ):
            output_type_map = {
                'Tensor': 'paddle::dialect::DenseTensorType',
                'Tensor[]': 'pir::VectorType<paddle::dialect::DenseTensorType>',
                'SelectedRows': 'paddle::dialect::SelectedRowsType',
            }
            type_list = []
            for output_info in self.op_yaml_item['outputs']:
                assert (
                    output_info['typename'] in output_type_map
                ), f"{self.op_phi_name} : Output type error: the output type only support Tensor and Tensor[], but now is {output_info['typename']}."
                type_list.append(output_type_map[output_info['typename']])

            if self.kernel_map is None:
                type_dict['default'] = type_list
            else:
                type_dict[self.kernel_map['func'][0]] = type_list

        else:
            output_type_map = {
                'dense': 'paddle::dialect::DenseTensorType',
                'selected_rows': 'paddle::dialect::SelectedRowsType',
            }

            for kernel_func_name in self.kernel_map['func']:
                outputs = self.kernel_map['dispatch'][kernel_func_name][1]
                type_list = []
                for output_info in outputs:
                    assert (
                        output_info in output_type_map
                    ), f"{self.op_phi_name} : Input type error: the input type only support dense and selected_rows, but now is {output_info}."
                    type_list.append(output_type_map[output_info])

                type_dict[kernel_func_name] = type_list

        return type_dict

    def parse_output_size_list(self):
        size_list = []
        for output_info in self.op_yaml_item['outputs']:
            if 'size' in output_info:
                size_list.append(output_info['size'])
            else:
                size_list.append(None)
        return size_list

    def parse_output_optional_list(self):
        optional_list = []
        for output_info in self.op_yaml_item['outputs']:
            if 'optional' in output_info or 'intermediate' in output_info:
                if output_info['optional'] or output_info['intermediate']:
                    optional_list.append("true")
                else:
                    optional_list.append("false")
            else:
                optional_list.append("false")
        return optional_list

    def parse_output_intermediate_list(self):
        intermediate_list = []
        for output_info in self.op_yaml_item['outputs']:
            if 'intermediate' in output_info:
                if output_info['intermediate']:
                    intermediate_list.append("true")
                else:
                    intermediate_list.append("false")
            else:
                intermediate_list.append("false")
        return intermediate_list

    def parse_attribute_name_list(self):
        name_list = []
        for attribute_info in self.op_yaml_item['attrs']:
            name_list.append(attribute_info['name'])
        return name_list

    def parse_attribute_build_arg_type_list(self):
        type_list = []
        for attribute_info in self.op_yaml_item['attrs']:
            assert (
                attribute_info['typename'] in self.attr_types_map
            ), f"{self.op_phi_name} : Attr type error."

            # Scalar & IntArray has data_type
            temp_type = self.attr_types_map[attribute_info['typename']][1]
            if 'Scalar' in temp_type:
                if 'data_type' in attribute_info:
                    temp_type = attribute_info['data_type']
                op_name = self.op_yaml_item['name']
                attr_name = attribute_info['name']
                if (
                    op_name not in ["isclose", "allclose"]
                    and self.op_compat_item is not None
                    and 'scalar' in self.op_compat_item.keys()
                    and attr_name in self.op_compat_item['scalar'].keys()
                    and 'data_type'
                    in self.op_compat_item['scalar'][attr_name].keys()
                ):
                    temp_type = self.op_compat_item['scalar'][attr_name][
                        'data_type'
                    ]
            if 'IntArray' in temp_type:
                if 'data_type' in attribute_info:
                    temp_type = "const " + attribute_info['data_type'] + "&"
            type_list.append(self.get_phi_dtype_name(temp_type))
        return type_list

    def parse_attribute_gen_arg_type_list(self):
        type_list = []
        for attribute_info in self.op_yaml_item['attrs']:
            assert (
                attribute_info['typename'] in self.attr_types_map
            ), f"{self.op_phi_name} : Attr type error."

            temp_type = self.attr_types_map[attribute_info['typename']][1]
            type_list.append(self.get_phi_dtype_name(temp_type))
        return type_list

    def parse_attribute_type_list(self):
        type_list = []
        for attribute_info in self.op_yaml_item['attrs']:
            assert (
                attribute_info['typename'] in self.attr_types_map
            ), f"{self.op_phi_name} : Attr type error."
            type_list.append(self.attr_types_map[attribute_info['typename']][0])
        return type_list

    def parse_attribute_data_type_list(self):
        data_type_list = []
        for attribute_info in self.op_yaml_item['attrs']:
            if 'data_type' in attribute_info:
                data_type_list.append(attribute_info['data_type'])
            else:
                data_type_list.append("")
        return data_type_list

    def parse_attribute_default_value_list(self):
        default_value_list = []
        for attribute_info in self.op_yaml_item['attrs']:
            if 'default_value' in attribute_info:
                default_value = attribute_info['default_value']
                default_value_list.append(
                    self.get_phi_dtype_name(default_value)
                )
            else:
                default_value_list.append(None)
        return default_value_list

    def parse_infer_meta_map(self):
        if 'infer_meta' in self.op_yaml_item:
            return self.op_yaml_item['infer_meta']
        else:
            return None

    def parse_kernel_map(self):
        if 'kernel' in self.op_yaml_item:
            return self.op_yaml_item['kernel']
        else:
            return None

    def parse_invoke_map(self):
        if 'invoke' in self.op_yaml_item:
            return self.op_yaml_item['invoke']
        else:
            return None

    def parse_data_transform_info(self):
        if (
            'data_transform' in self.op_yaml_item
            and self.op_yaml_item['data_transform']
        ):
            data_trans_item = self.op_yaml_item['data_transform']
            return data_trans_item
        return None

    def parse_backward_name(self):
        if 'backward' in self.op_yaml_item:
            return self.op_yaml_item['backward']
        else:
            return None

    def get_phi_dtype_name(self, name):
        name = name.replace('Scalar', 'phi::Scalar')
        name = name.replace('IntArray', 'phi::IntArray')
        name = name.replace('DataLayout', 'phi::DataLayout')
        name = name.replace('DataType', 'phi::DataType')
        if name.startswith(
            (
                "Place",
                "CPUPlace",
                "GPUPlace",
                "GPUPinnedPlace",
                "XPUPlace",
                "IPUPlace",
                "CustomPlace",
            )
        ):
            return "phi::" + name
        return name


def to_pascal_case(s):
    words = s.split("_")
    if s[-1] == "_":
        return "".join([word.capitalize() for word in words]) + "_"
    else:
        return "".join([word.capitalize() for word in words]) + ""


def get_input_grad_semantic(op_info, op_info_items):
    input_grad_semantics = []
    num_inputs = len(op_info.input_name_list)

    # get backward op
    bwd_op_name = op_info.backward_name
    if (bwd_op_name is None) or (bwd_op_name not in op_info_items.keys()):
        input_grad_semantics = ["false" for i in range(num_inputs)]
    else:
        bwd_op_info = op_info_items[bwd_op_name]

        # cut "_grad" of each output of bwd_op, and then compare each modified output with corresponding input
        # thus determine whether each input has grad semantic
        bwd_output_list = bwd_op_info.output_name_list
        bwd_output_list_new = []
        for bwd_output in bwd_output_list:
            bwd_output_list_new.append(bwd_output[:-5])  # cut _grad

        bwd_fwd_input_list = bwd_op_info.forward_input_name_list
        if bwd_fwd_input_list is not None:
            assert (
                len(bwd_fwd_input_list) == num_inputs
            ), "Configuration of forward op and backward op is not match."
            for i in range(num_inputs):
                if bwd_fwd_input_list[i] in bwd_output_list_new:
                    input_grad_semantics.append("true")
                else:
                    input_grad_semantics.append("false")
        else:
            input_grad_semantics = ["false" for i in range(num_inputs)]

    return input_grad_semantics


def get_mutable_attribute_grad_semantic(op_info, op_info_items):
    mutable_attribute_grad_semantics = []
    fwd_mutable_attribute_list = op_info.mutable_attribute_name_list

    # get backward op
    bwd_op_name = op_info.backward_name
    if (bwd_op_name is None) or (bwd_op_name not in op_info_items.keys()):
        mutable_attribute_grad_semantics = [
            "false" for i in range(len(fwd_mutable_attribute_list))
        ]
    else:
        bwd_op_info = op_info_items[bwd_op_name]

        # cut "_grad" of each output of bwd_op, and then compare each modified output with corresponding attribute
        # thus determine whether each attribute has grad semantic
        bwd_output_list = bwd_op_info.output_name_list
        bwd_output_list_new = []
        for bwd_output in bwd_output_list:
            bwd_output_list_new.append(bwd_output[:-5])

        for i in range(len(fwd_mutable_attribute_list)):
            if fwd_mutable_attribute_list[i] in bwd_output_list_new:
                mutable_attribute_grad_semantics.append("true")
            else:
                mutable_attribute_grad_semantics.append("false")

    return mutable_attribute_grad_semantics


def check_need_update_ops(op_yaml_files):
    need_update_ops = False
    update_yaml_file = None
    for yaml_file in op_yaml_files:
        if yaml_file.find("update_ops.parsed.yaml") != -1:
            need_update_ops = True
            update_yaml_file = yaml_file
            break
    return need_update_ops, update_yaml_file


def update_ops(op_yaml_items, update_yaml_file):
    with open(update_yaml_file, "r") as f:
        update_ops = yaml.safe_load(f)
    for i in range(len(op_yaml_items)):
        for update_op in update_ops:
            if op_yaml_items[i]['name'] == update_op['name']:
                op_yaml_items[i] = update_op
                break


def OpGenerator(
    op_yaml_files,
    op_compat_yaml_file,
    namespaces,
    dialect_name,
    op_def_h_file,
    op_def_cc_file,
    op_vjp_cc_file,
):
    # (1) Prepare: Delete existing old files: pd_op.h.tmp, pd_op.cc.tmp
    if os.path.exists(op_def_h_file):
        os.remove(op_def_h_file)
    if os.path.exists(op_def_cc_file):
        os.remove(op_def_cc_file)

    # (2) Prepare: Get all op item in all op_yaml_files
    op_compat_parser = OpCompatParser(op_compat_yaml_file)
    need_update_ops, update_yaml_file = check_need_update_ops(op_yaml_files)

    op_yaml_items = []
    for yaml_file in op_yaml_files:
        if update_yaml_file == yaml_file:
            continue
        with open(yaml_file, "r") as f:
            ops = yaml.safe_load(f)
            op_yaml_items = op_yaml_items + ops
    # replace old ir ops with pir ops
    if need_update_ops:
        update_ops(op_yaml_items, update_yaml_file)

    op_info_items = {}
    for op in op_yaml_items:
        op_compat_item = None
        if dialect_name == "pd_op":
            op_compat_item = op_compat_parser.get_compat(op['name'])

        if (
            op_compat_item is None
            and op['name'].endswith(('_grad', '_grad_'))
            and 'forward' in op
        ):
            op_compat_item = op_compat_parser.get_compat(op['forward']['name'])

        if (
            op_compat_item is not None
            and op_compat_item['op'] == "pow"
            and 'scalar' in op_compat_item
        ):
            op_compat_item = op_compat_item.pop('scalar')

        if 'support_tensor' in op.keys() and op['support_tensor']:
            scalar_item, int_array_item = op_compat_parser.parse_support_tensor(
                op
            )
            op_compat_item['scalar'] = scalar_item
            op_compat_item['int_array'] = int_array_item

        op_info_items[op['name']] = OpInfoParser(op, op_compat_item)
    # (3) CodeGen: Traverse op_info_items and generate
    ops_name_list = []  # all op class name store in this list
    ops_declare_list = []  # all op class declare store in this list
    ops_defined_list = []  # all op class defined store in this list
    ops_vjp_defined_list = []  # all op vjp static interface defination

    # (4) parse name of ops which have custom vjp rules
    custom_vjp_op_name_list = []
    for custom_vjp in vjp_gen.CUSTOM_VJP:
        custom_vjp_op_name_list.append(custom_vjp[:-5])  # cut _grad

    op_to_multi_kernels_list = []
    for key, op_info in op_info_items.items():
        # get op inputs info
        op_input_name_list = op_info.input_name_list
        op_input_optional_list = op_info.input_optional_list
        op_input_no_need_buffer_list = op_info.input_no_need_buffer_list
        # get op outputs info
        op_output_name_list = op_info.output_name_list
        op_output_size_list = op_info.output_size_list
        op_output_optional_list = op_info.output_optional_list
        op_output_intermediate_list = op_info.output_intermediate_list
        # get op mutable attribute
        op_mutable_attribute_name_list = op_info.mutable_attribute_name_list
        op_mutable_attribute_type_list = op_info.mutable_attribute_type_list
        # get op attribute
        op_attribute_name_list = op_info.attribute_name_list
        op_attribute_type_list = op_info.attribute_type_list
        op_attribute_data_type_list = op_info.attribute_data_type_list
        op_attribute_build_arg_type_list = op_info.attribute_build_arg_type_list
        op_attribute_default_value_list = op_info.attribute_default_value_list
        op_non_mutable_attribute_name_list = (
            op_info.non_mutable_attribute_name_list
        )
        op_non_mutable_attribute_type_list = (
            op_info.non_mutable_attribute_type_list
        )
        op_non_mutable_attribute_data_type_list = (
            op_info.non_mutable_attribute_data_type_list
        )
        op_non_mutable_attribute_build_arg_type_list = (
            op_info.non_mutable_attribute_build_arg_type_list
        )
        op_non_mutable_attribute_default_value_list = (
            op_info.non_mutable_attribute_default_value_list
        )

        # others
        op_infer_meta_map = op_info.infer_meta_map
        op_kernel_map = op_info.kernel_map
        op_invoke_map = op_info.invoke_map
        op_inplace_map = op_info.inplace_map
        op_view_map = op_info.view_map
        op_data_transform_map = op_info.data_transform_map
        op_traits = op_info.traits_list
        op_interfaces = op_info.interfaces_list
        op_interfaces += ["paddle::dialect::OpYamlInfoInterface"]

        if op_info.infer_meta_func:
            op_interfaces += ["paddle::dialect::InferMetaInterface"]
        elif op_invoke_map and op_invoke_map['func'] in op_info_items:
            if op_info_items[op_invoke_map['func']].infer_meta_func:
                op_interfaces += ["paddle::dialect::InferMetaInterface"]

        if (
            op_info.backward_name
            and op_info.op_phi_name[0] not in vjp_interface_black_list
        ):
            op_interfaces += ["paddle::dialect::VjpInterface"]
        exclusive_interface_str = gen_exclusive_interface_str(
            op_info, op_info_items
        )

        if dialect_name == "pd_op":
            op_interfaces += ["paddle::dialect::GetKernelTypeForVarInterface"]

        # if op has custom vjp rule, then append a CustomVjpTrait to it
        if op_info.op_phi_name[0] in custom_vjp_op_name_list:
            op_traits += ["paddle::dialect::CustomVjpTrait"]

        # check op inputs and mutable_attributes grad semantics
        input_grad_semantics = get_input_grad_semantic(op_info, op_info_items)
        mutable_attribute_grad_semantics = get_mutable_attribute_grad_semantic(
            op_info, op_info_items
        )
        op_interfaces_tmp = op_interfaces
        exclusive_interface_str_tmp = exclusive_interface_str

        # If op has inplace info, we will generate inplace op and non-inplace op.
        for op_name in op_info.op_phi_name:
            # =================================== #
            #        gen trait list str           #
            # =================================== #
            if op_name[-1] == "_":
                op_traits += ["paddle::dialect::InplaceTrait"]

            op_traits_str = ""
            if len(op_traits) > 0:
                op_traits_str = "," + ",".join(op_traits)

            if op_name in PD_MANUAL_OP_LIST:
                continue
            if op_kernel_map is None:
                func_list = [None]
            else:
                func_list = op_kernel_map['func']

            for kernel_func_name in func_list:
                if (
                    op_name in decomp_interface_declare_gen_op_list
                    and kernel_func_name in decomp_interface_declare_gen_op_list
                ):
                    op_interfaces = op_interfaces + [
                        "paddle::dialect::DecompInterface"
                    ]
                    exclusive_interface_str += "\n  static std::vector<std::vector<pir::OpResult>> Decomp(pir::Operation* op);"
                else:
                    op_interfaces = op_interfaces_tmp
                    exclusive_interface_str = exclusive_interface_str_tmp

                # =================================== #
                #      gen interface list str         #
                # =================================== #
                op_interfaces_str = ""
                if len(op_interfaces) > 0:
                    op_interfaces_str = "," + ",".join(op_interfaces)

                if len(func_list) == 1:
                    op_class_name = to_pascal_case(op_name) + "Op"
                    op_dialect_name = dialect_name + "." + op_name
                else:
                    pascal_kernel_func_name = to_pascal_case(kernel_func_name)
                    if op_name[-1] == "_":
                        op_class_name = pascal_kernel_func_name + "_Op"
                        op_dialect_name = (
                            dialect_name + "." + kernel_func_name + "_"
                        )
                    else:
                        op_class_name = pascal_kernel_func_name + "Op"
                        op_dialect_name = dialect_name + "." + kernel_func_name

                if kernel_func_name is None:
                    op_input_type_list = op_info.input_type_dict['default']
                    op_output_type_list = op_info.output_type_dict['default']
                else:
                    op_input_type_list = op_info.input_type_dict[
                        kernel_func_name
                    ]
                    op_output_type_list = op_info.output_type_dict[
                        kernel_func_name
                    ]

                # =================================== #
                #  gen get input/output methods str   #
                # =================================== #
                op_get_inputs_outputs_str = gen_op_get_inputs_outputs_str(
                    op_input_name_list,
                    op_mutable_attribute_name_list,
                    op_output_name_list,
                )

                # =================================== #
                #         gen Build methods str       #
                # =================================== #
                build_args_with_muta_attr_not_input_for_declare = ""
                build_func_with_muta_attr_not_input = ""
                build_mutable_attr_is_input = ""
                build_func_with_muta_attr_is_input_with_attr_is_map = ""
                build_attr_num_over_1 = ""
                build_mutable_attr_is_input_attr_num_over_1 = ""
                build_func_with_attr_is_map = ""
                build_func_with_muta_attr_is_input = ""

                get_kernel_type_for_var_declare_str = ""
                if dialect_name == "pd_op":
                    get_kernel_type_for_var_declare_str = (
                        get_kernel_type_for_var_declare_template
                    )

                parse_kernel_key_str = ""
                if "paddle::dialect::ParseKernelKeyInterface" in op_interfaces:
                    parse_kernel_key_str = parse_kernel_key_template

                if op_infer_meta_map is not None:
                    (
                        build_args_with_muta_attr_not_input_for_declare,
                        build_func_with_muta_attr_not_input,
                    ) = gen_build_func_str(
                        op_class_name,
                        op_input_name_list,
                        op_input_type_list,
                        op_input_optional_list,
                        op_attribute_name_list,
                        op_attribute_type_list,
                        op_attribute_build_arg_type_list,
                        op_attribute_default_value_list,
                        op_mutable_attribute_name_list,
                        op_mutable_attribute_type_list,
                        op_non_mutable_attribute_name_list,
                        op_non_mutable_attribute_type_list,
                        op_non_mutable_attribute_build_arg_type_list,
                        op_non_mutable_attribute_default_value_list,
                        op_output_name_list,
                        op_output_type_list,
                        op_output_size_list,
                        op_output_optional_list,
                        op_infer_meta_map,
                        op_inplace_map,
                        muta_attr_is_input=False,
                    )
                    if len(op_attribute_name_list) > 0:
                        (
                            build_args_with_attr_is_map_for_declare,
                            build_func_with_attr_is_map,
                        ) = gen_build_func_str(
                            op_class_name,
                            op_input_name_list,
                            op_input_type_list,
                            op_input_optional_list,
                            op_attribute_name_list,
                            op_attribute_type_list,
                            op_attribute_build_arg_type_list,
                            op_attribute_default_value_list,
                            op_mutable_attribute_name_list,
                            op_mutable_attribute_type_list,
                            op_non_mutable_attribute_name_list,
                            op_non_mutable_attribute_type_list,
                            op_non_mutable_attribute_build_arg_type_list,
                            op_non_mutable_attribute_default_value_list,
                            op_output_name_list,
                            op_output_type_list,
                            op_output_size_list,
                            op_output_optional_list,
                            op_infer_meta_map,
                            op_inplace_map,
                            muta_attr_is_input=False,
                            attr_args_is_map=True,
                        )
                        build_attr_num_over_1 = f"static void Build({build_args_with_attr_is_map_for_declare});"

                    if len(op_mutable_attribute_name_list) > 0:
                        (
                            build_args_with_muta_attr_is_input_for_declare,
                            build_func_with_muta_attr_is_input,
                        ) = gen_build_func_str(
                            op_class_name,
                            op_input_name_list,
                            op_input_type_list,
                            op_input_optional_list,
                            op_attribute_name_list,
                            op_attribute_type_list,
                            op_attribute_build_arg_type_list,
                            op_attribute_default_value_list,
                            op_mutable_attribute_name_list,
                            op_mutable_attribute_type_list,
                            op_non_mutable_attribute_name_list,
                            op_non_mutable_attribute_type_list,
                            op_non_mutable_attribute_build_arg_type_list,
                            op_non_mutable_attribute_default_value_list,
                            op_output_name_list,
                            op_output_type_list,
                            op_output_size_list,
                            op_output_optional_list,
                            op_infer_meta_map,
                            op_inplace_map,
                            muta_attr_is_input=True,
                        )

                        build_mutable_attr_is_input = "static void Build({build_args});".format(
                            build_args=build_args_with_muta_attr_is_input_for_declare
                        )
                if (op_invoke_map is not None) and (
                    op_invoke_map['func'] in op_info_items
                ):
                    op_invoke_class_name = (
                        to_pascal_case(op_invoke_map['func']) + "Op"
                    )

                    (
                        build_args_with_muta_attr_not_input_for_declare,
                        build_func_with_muta_attr_not_input,
                    ) = gen_build_func_str_by_invoke(
                        op_class_name,
                        op_input_name_list,
                        op_input_type_list,
                        op_input_optional_list,
                        op_attribute_name_list,
                        op_attribute_type_list,
                        op_attribute_build_arg_type_list,
                        op_attribute_default_value_list,
                        op_mutable_attribute_name_list,
                        op_mutable_attribute_type_list,
                        op_non_mutable_attribute_name_list,
                        op_non_mutable_attribute_type_list,
                        op_non_mutable_attribute_build_arg_type_list,
                        op_non_mutable_attribute_default_value_list,
                        op_invoke_class_name,
                        op_invoke_map,
                    )

                # gen op_declare_str/op_defined_str
                if len(op_non_mutable_attribute_name_list) == 0:
                    op_declare_str = OP_DECLARE_TEMPLATE.format(
                        op_name=op_class_name,
                        dialect_op_name=op_dialect_name,
                        interfaces=op_interfaces_str,
                        traits=op_traits_str,
                        attribute_declare=op_0_attribute_declare_str,
                        attribute_num=0,
                        build_args=build_args_with_muta_attr_not_input_for_declare,
                        build_mutable_attr_is_input=build_mutable_attr_is_input,
                        build_attr_num_over_1=build_attr_num_over_1,
                        build_mutable_attr_is_input_attr_num_over_1=build_mutable_attr_is_input_attr_num_over_1,
                        get_inputs_and_outputs=op_get_inputs_outputs_str,
                        exclusive_interface=exclusive_interface_str,
                        get_kernel_type_for_var_declare=get_kernel_type_for_var_declare_str,
                        parse_kernel_key_declare=parse_kernel_key_str,
                    )
                    op_defined_str = ""
                else:
                    op_declare_str = OP_DECLARE_TEMPLATE.format(
                        op_name=op_class_name,
                        dialect_op_name=op_dialect_name,
                        interfaces=op_interfaces_str,
                        traits=op_traits_str,
                        attribute_declare=op_n_attribute_declare_str.format(
                            attribute_num=len(
                                op_non_mutable_attribute_name_list
                            )
                        ),
                        attribute_num=len(op_non_mutable_attribute_name_list),
                        build_args=build_args_with_muta_attr_not_input_for_declare,
                        build_mutable_attr_is_input=build_mutable_attr_is_input,
                        build_attr_num_over_1=build_attr_num_over_1,
                        build_mutable_attr_is_input_attr_num_over_1=build_mutable_attr_is_input_attr_num_over_1,
                        get_inputs_and_outputs=op_get_inputs_outputs_str,
                        exclusive_interface=exclusive_interface_str,
                        get_kernel_type_for_var_declare=get_kernel_type_for_var_declare_str,
                        parse_kernel_key_declare=parse_kernel_key_str,
                    )
                    attribute_names_str = (
                        '"'
                        + '", "'.join(op_non_mutable_attribute_name_list)
                        + '"'
                    )
                    op_defined_str = OP_N_ATTRIBUTE_DEFINED_TEMPLATE.format(
                        op_name=op_class_name,
                        attribute_num=len(op_non_mutable_attribute_name_list),
                        attribute_names=attribute_names_str,
                    )

                # =================================== #
                #         gen GetOpInfo func str      #
                # =================================== #
                # generate get op info funciton: inputs
                input_info_list = []
                for idx in range(len(op_input_name_list)):
                    input_info_list.append(
                        CONSTRUCT_INPUT_INFO_TEMPLATE.format(
                            name=op_input_name_list[idx],
                            typename=op_input_type_list[idx],
                            optional=op_input_optional_list[idx],
                            no_need_buffer=op_input_no_need_buffer_list[idx],
                            is_mutable_attribute='false',
                            with_grad_semantic=input_grad_semantics[idx],
                        )
                    )
                for idx in range(len(op_mutable_attribute_name_list)):
                    input_info_list.append(
                        CONSTRUCT_INPUT_INFO_TEMPLATE.format(
                            name=op_mutable_attribute_name_list[idx],
                            typename=op_mutable_attribute_type_list[idx][0],
                            optional='false',
                            no_need_buffer='false',
                            is_mutable_attribute='true',
                            with_grad_semantic=mutable_attribute_grad_semantics[
                                idx
                            ],
                        )
                    )
                if len(input_info_list) > 0:
                    inputs_info_str = ", ".join(input_info_list)
                else:
                    inputs_info_str = ""
                # generate get op info funciton: outputs
                outputs_info_str = ""
                if len(op_output_name_list) > 0:
                    output_info_list = []
                    for idx in range(len(op_output_name_list)):
                        output_info_list.append(
                            CONSTRUCT_OUTPUT_INFO_TEMPLATE.format(
                                name=op_output_name_list[idx],
                                typename=op_output_type_list[idx],
                                optional=op_output_optional_list[idx],
                                intermediate=op_output_intermediate_list[idx],
                            )
                        )
                    outputs_info_str = ", ".join(output_info_list)
                # generate get op info funciton: attributes
                attribute_info_str = ""
                if len(op_non_mutable_attribute_name_list) > 0:
                    attribute_info_list = []
                    for idx in range(len(op_non_mutable_attribute_name_list)):
                        attribute_info_list.append(
                            CONSTRUCT_ATTRIBUTE_INFO_TEMPLATE.format(
                                name=op_non_mutable_attribute_name_list[idx],
                                typename=op_non_mutable_attribute_type_list[
                                    idx
                                ],
                                data_type=op_non_mutable_attribute_data_type_list[
                                    idx
                                ],
                            )
                        )
                    attribute_info_str = ", ".join(attribute_info_list)
                # generate runtiem info
                infer_meta_func_str = ""
                infer_meta_param_str = ""
                if op_infer_meta_map is not None:
                    infer_meta_func_str = op_infer_meta_map['func']
                    infer_meta_param_str = '", "'.join(
                        op_infer_meta_map['param']
                    )

                kernel_func_str = ""
                kernel_param_str = ""
                kernel_key_dtype = ""
                kernel_key_backend = ""
                if op_kernel_map is not None:
                    kernel_func_str = kernel_func_name
                    kernel_param_str = '", "'.join(op_kernel_map['param'])
                    if (
                        'data_type' in op_kernel_map
                        and op_kernel_map['data_type']
                    ):
                        for idx in range(
                            len(op_kernel_map['data_type']['candidates'])
                        ):
                            if (
                                'to_complex_flag' in op_kernel_map['data_type']
                                and op_kernel_map['data_type'][
                                    'to_complex_flag'
                                ][idx]
                            ):
                                kernel_key_dtype += (
                                    'complex:'
                                    + op_kernel_map['data_type']['candidates'][
                                        idx
                                    ]
                                    + '", "'
                                )
                            else:
                                kernel_key_dtype += (
                                    op_kernel_map['data_type']['candidates'][
                                        idx
                                    ]
                                    + '", "'
                                )
                        if kernel_key_dtype != "":
                            kernel_key_dtype = '"' + kernel_key_dtype[:-3]
                    if 'backend' in op_kernel_map and op_kernel_map['backend']:
                        kernel_key_backend = '", "'.join(
                            op_kernel_map['backend']['candidates']
                        )
                        if kernel_key_backend != "":
                            kernel_key_backend = '"' + kernel_key_backend + '"'

                inplace_str = ""
                view_str = ""
                if op_name[-1] == "_":
                    if op_inplace_map is not None:
                        for key, value in op_inplace_map.items():
                            inplace_str += '{"' + key + '", "' + value + '"},'
                        inplace_str = inplace_str[:-1]
                    if op_view_map is not None:
                        for key, value in op_view_map.items():
                            view_str += '{"' + key + '", "' + value + '"},'
                        view_str = view_str[:-1]

                op_info_func_str = OP_INFO_TEMPLATE.format(
                    op_name=op_class_name,
                    inputs=inputs_info_str,
                    attributes=attribute_info_str,
                    outputs=outputs_info_str,
                    infer_meta_func=infer_meta_func_str,
                    infer_meta_param=infer_meta_param_str,
                    kernel_func=kernel_func_str,
                    kernel_param=kernel_param_str,
                    kernel_key_dtype=kernel_key_dtype,
                    kernel_key_backend=kernel_key_backend,
                    inplace=inplace_str,
                    view=view_str,
                    origin_op_name=op_info.op_yaml_item['name'],
                )

                # generate op verify function str
                op_verify_str = ''
                if not op_info.custom_verify:
                    op_verify_str = gen_verify_func_str(
                        op_class_name,
                        op_input_type_list,
                        op_input_optional_list,
                        op_mutable_attribute_name_list,
                        op_mutable_attribute_type_list,
                        op_non_mutable_attribute_name_list,
                        op_non_mutable_attribute_type_list,
                        op_output_type_list,
                        op_output_optional_list,
                    )

                # generate op GetKernelKeyForVar function str
                parse_kernel_key_define_str = ''
                if "paddle::dialect::ParseKernelKeyInterface" in op_interfaces:
                    parse_kernel_key_define_str = gen_parse_kernel_key_str(
                        op_class_name
                    )

                # generate op GetKernelKeyForVar function str
                op_get_kernel_type_for_var_str = ''
                if dialect_name == "pd_op":
                    op_get_kernel_type_for_var_str = (
                        gen_kernel_type_for_var_str(
                            op_class_name,
                            op_data_transform_map,
                            op_kernel_map,
                            op_info.op_compat_item,
                        )
                    )

                op_infer_meta_str = gen_op_infer_meta_str(
                    op_info, op_class_name, op_info_items
                )

                # =================================== #
                #         gen Vjp func str      #
                # =================================== #

                # generate op vjp function str
                op_vjp_str = ''
                if dialect_name == "cinn":
                    logging.warning(
                        "cinn is currently not support Vjp function"
                    )
                else:
                    if (
                        op_info.backward_name
                        and op_info.op_phi_name[0]
                        not in vjp_interface_black_list
                    ):
                        op_vjp_str = gen_op_vjp_str(
                            op_class_name,
                            op_info.backward_name,
                            op_name,
                            op_info_items[op_info.op_phi_name[0]],
                            op_info_items[op_info.backward_name],
                        )

                    ops_name_list.append(op_class_name)
                    ops_declare_list.append(op_declare_str)
                    ops_defined_list.append(op_defined_str)
                    ops_defined_list.append(op_info_func_str)
                    ops_defined_list.append(build_func_with_muta_attr_not_input)
                    ops_defined_list.append(build_func_with_attr_is_map)
                    if len(op_mutable_attribute_name_list) > 0:
                        ops_defined_list.append(
                            build_func_with_muta_attr_is_input
                        )
                        ops_defined_list.append(
                            build_func_with_muta_attr_is_input_with_attr_is_map
                        )

                    ops_defined_list.append(op_verify_str)
                    ops_defined_list.append(op_infer_meta_str)
                    ops_defined_list.append(op_get_kernel_type_for_var_str)
                    ops_defined_list.append(parse_kernel_key_define_str)

                    # NOTE(chenxi67)skip if dialect_name==cinn
                    if dialect_name == "cinn":
                        pass
                    else:
                        ops_vjp_defined_list.append(op_vjp_str)

            if op_kernel_map is not None and len(op_kernel_map['func']) > 1:
                OP_TO_MULTI_KERNELS_MAP_ITEM = (
                    """{{"{op_name}", {{{sig_list}}}}}"""
                )
                OP_TO_MULTI_KERNELS_MAP_ITEM_SIG = """paddle::dialect::PdOpSig("{kernel_name}", {{{inputs}}}, {{{outputs}}})"""
                op_to_multi_kernels_sig_list = []
                for kernel_func_name in op_kernel_map['func']:
                    inputs = op_kernel_map['dispatch'][kernel_func_name][0]
                    outputs = op_kernel_map['dispatch'][kernel_func_name][1]
                    inputs = '"' + '", "'.join(inputs) + '"'
                    outputs = '"' + '", "'.join(outputs) + '"'
                    if op_name[-1] == "_":
                        kernel_func_name = kernel_func_name + "_"

                    op_to_multi_kernels_sig_list.append(
                        OP_TO_MULTI_KERNELS_MAP_ITEM_SIG.format(
                            kernel_name=kernel_func_name,
                            inputs=inputs,
                            outputs=outputs,
                        )
                    )
                op_to_multi_kernels_str = OP_TO_MULTI_KERNELS_MAP_ITEM.format(
                    op_name=op_name,
                    sig_list=", ".join(op_to_multi_kernels_sig_list),
                )
                op_to_multi_kernels_list.append(op_to_multi_kernels_str)

    # (4) Generate head file str
    op_namespaces_prev = ""
    for name in namespaces:
        op_namespaces_prev += name + "::"
    ops_name_with_namespace_list = []
    for name in ops_name_list:
        ops_name_with_namespace_list.append(op_namespaces_prev + name)
    op_list_str = GET_OP_LIST_TEMPALTE.format(
        ", ".join(ops_name_with_namespace_list)
    )  # Add GET_OP_LIST

    declare_type_id_str = ""
    for op in ops_name_with_namespace_list:
        declare_type_id_str += DECLARE_OP_TYPE_ID.format(op_name=op)

    head_file_str = ""
    head_file_str += "".join(ops_declare_list)  # Add op class
    only_pd_op_header_files_str = ""

    if dialect_name == "pd_op":
        op_to_multi_kernels_map = OP_TO_MULTI_KERNELS_MAP_H
        for name in reversed(namespaces):
            op_to_multi_kernels_map = NAMESPACE_GARD_TEMPLATE.format(
                namespace=name, input=op_to_multi_kernels_map
            )  # Add namespaces
        only_pd_op_header_files_str = """
#include \"paddle/phi/common/data_type.h\"
#include \"paddle/fluid/pir/dialect/operator/interface/get_kernel_type_for_var.h\"
            """
    else:
        op_to_multi_kernels_map = ""

    for name in reversed(namespaces):
        head_file_str = NAMESPACE_GARD_TEMPLATE.format(
            namespace=name, input=head_file_str
        )  # Add namespaces
    head_file_str = H_FILE_TEMPLATE.format(
        op_to_multi_kernels_map=op_to_multi_kernels_map,
        input=head_file_str,
        declare_type_id=declare_type_id_str,
        only_pd_op_header_files=only_pd_op_header_files_str,
    )  # Add head

    # (5) Generate source file str
    source_file_str = "".join(ops_defined_list)  # Add op define
    for name in reversed(namespaces):
        source_file_str = NAMESPACE_GARD_TEMPLATE.format(
            namespace=name, input=source_file_str
        )  # Add namespaces

    define_type_id_str = ""
    for op in ops_name_with_namespace_list:
        define_type_id_str += DEFINE_OP_TYPE_ID.format(op_name=op)

    if dialect_name == "pd_op":
        op_to_multi_kernels_map_str = OP_TO_MULTI_KERNELS_MAPS.format(
            maps=", \r".join(op_to_multi_kernels_list)
        )
        for name in reversed(namespaces):
            op_to_multi_kernels_map_str = NAMESPACE_GARD_TEMPLATE.format(
                namespace=name, input=op_to_multi_kernels_map_str
            )  # Add namespaces
    else:
        op_to_multi_kernels_map_str = ""

    source_file_str = CC_FILE_TEMPLATE.format(
        op_declare=op_list_str,
        op_to_multi_kernels_map=op_to_multi_kernels_map_str,
        h_file=op_def_h_file[:-4],
        input=source_file_str,
        define_type_id=define_type_id_str,
    )  # Add head

    vjp_source_file_str = VJP_CC_FILE_TEMPLATE.format(
        input="".join(ops_vjp_defined_list)
    )
    # (5) Generate pd_op.h.tmp, pd_op.cc.tmp
    with open(op_def_h_file, 'w') as f:
        f.write(head_file_str)
    with open(op_def_cc_file, 'w') as f:
        f.write(source_file_str)
    # NOTE(Aurelius84): op_gen.py is called multiply times,
    # and vjp is only avaible for pd dialect.
    if dialect_name != 'cinn' and op_vjp_cc_file:
        with open(op_vjp_cc_file, 'w') as f:
            f.write(vjp_source_file_str)


# =====================================
# Script parameter parsing
# =====================================
def ParseArguments():
    parser = argparse.ArgumentParser(
        description='Generate Dialect OP Definition Files By Yaml'
    )
    parser.add_argument('--op_yaml_files', type=str)
    parser.add_argument('--op_compat_yaml_file', type=str)
    parser.add_argument('--namespaces', type=str)
    parser.add_argument('--dialect_name', type=str)
    parser.add_argument('--op_def_h_file', type=str)
    parser.add_argument('--op_def_cc_file', type=str)
    parser.add_argument('--op_vjp_cc_file', type=str)
    return parser.parse_args()


# =====================================
# Main
# =====================================
if __name__ == "__main__":
    # parse arguments
    args = ParseArguments()
    op_yaml_files = args.op_yaml_files.split(",")
    op_compat_yaml_file = args.op_compat_yaml_file
    namespaces = []
    if args.namespaces is not None:
        namespaces = args.namespaces.split(",")
    dialect_name = args.dialect_name
    op_def_h_file = args.op_def_h_file
    op_def_cc_file = args.op_def_cc_file
    op_vjp_cc_file = args.op_vjp_cc_file

    # auto code generate
    OpGenerator(
        op_yaml_files,
        op_compat_yaml_file,
        namespaces,
        dialect_name,
        op_def_h_file,
        op_def_cc_file,
        op_vjp_cc_file,
    )
