def gen_function(
    func_attrs,
    backend_spec,
    element_func=None,
    element_func_def=None,
):
    """Generates function body.

    Parameters
    ----------
    func_attrs : Dict[str, Any]
        Stores the operation attributes.
    index_type: str
        Index type.
    prefix: str
        Backend function prefix, hip/cuda
    dtype_to_backend_type: Dict[str, str]
    header_src_template: jinja Template
    Header src template.

    Returns
    -------
    str
        Rendered function body.
    """
    inputs = func_attrs["inputs"]
    original_inputs = func_attrs["original_inputs"]
    concatenate.check_rank(original_inputs, func_attrs["concat_dim"])
    orig_x = concatenate.get_first_non_empty_input_if_any(original_inputs)
    y = func_attrs["outputs"][0]
    x_shape = orig_x._attrs["shape"]

    input_type = backend_spec.dtype_to_backend_type(orig_x._attrs["dtype"])
    output_type = backend_spec.dtype_to_backend_type(y._attrs["dtype"])

    # TODO: support type cast
    if input_type != output_type:
        raise NotImplementedError("input type must equal to output type")

    def _stride(shape, dim):
        stride = 1
        for v in shape[dim:]:
            stride = stride * v._attrs["values"][0]
        return stride

    concat_dim = func_attrs["concat_dim"]
    assert concat_dim < len(x_shape)
    strides = [_stride(i._attrs["shape"], concat_dim) for i in inputs]
    # the max number of elements in each concat loop iteration
    elems_per_iter = max(strides) if len(strides) > 0 else 0
    threads_per_block = 128
    # minimal number of elems per thread is 8, max is 480
    elems_per_thread = min(480, (int((elems_per_iter / threads_per_block + 8) / 8) * 8))

    input_accessors = []
    input_accessor_refs = []
    for i in range(len(inputs)):
        accessor_name = f"input_accessor{i}"
        input_accessor_refs.append(f"&{accessor_name}")
        input_accessors.append(
            tensor_accessor_codegen.TENSOR_ACCESSOR_TEMPLATE.render(
                name=accessor_name, tensor_accessor=func_attrs["input_accessors"][i]
            )
        )
    input_accessor_defs = INPUT_ACCESSOR_DEFS_TEMPLATE.render(
        indent="    ",
        input_accessors="".join(input_accessors),
        num_real_inputs=len(inputs),
        input_accessor_refs=", ".join(input_accessor_refs),
    )

    # TODO: consider to add profiling paths for tuning
    # elems_per_thread and threads_per_block
    exec_paths = EXEC_COND_TEMPLATE.render(
        indent="  ",
        rank=len(x_shape),
        num_real_inputs=len(inputs),
        input_accessor_defs=input_accessor_defs,
        elem_type=input_type,
        elems_per_thread=elems_per_thread,
        threads_per_block=threads_per_block,
        index_type=backend_spec.index_type,
    )

    header_src = backend_spec.header_src_template.render()
    if len(inputs) > 0:
        tensor_accessor_libs = tensor_accessor_codegen.get_libs()
        kernel_src = KERNEL_SRC_TEMPLATE.render(
            element_func=element_func,
            element_func_def=element_func_def,
            header_src=header_src,
            index_type=backend_spec.index_type,
            prefix=backend_spec.prefix,
            tensor_accessor_libs=tensor_accessor_libs,
        )
        return SRC_TEMPLATE.render(
            kernel_src=kernel_src,
            func_name=func_attrs["name"],
            exec_paths=exec_paths,
            index_type=backend_spec.index_type,
            prefix=backend_spec.prefix,
        )

    return DUMMY_KERNEL_TEMPLATE.render(
        func_name=func_attrs["name"],
        header_src=header_src,
        index_type=backend_spec.index_type,
        prefix=backend_spec.prefix,
    )
Generates function body.

Parameters
----------
func_attrs : Dict[str, Any]
Stores the operation attributes.
index_type: str
Index type.
prefix: str
Backend function prefix, hip/cuda
dtype_to_backend_type: Dict[str, str]
header_src_template: jinja Template
Header src template.

Returns
-------
str
Rendered function body.
python/aitemplate/backend/common/concatenate_common.py