# RUN: %PYTHON %s | FileCheck %s

from mlir.ir import *
import mlir.dialects.arith as arith
import mlir.dialects.func as func
import mlir.dialects.tensor as tensor
from mlir.extras import types as T


def run(f):
    print("\nTEST:", f.__name__)
    f()
    return f


# CHECK-LABEL: TEST: testDimOp
@run
def testDimOp():
    with Context() as ctx, Location.unknown():
        module = Module.create()
        f32Type = F32Type.get()
        indexType = IndexType.get()
        with InsertionPoint(module.body):

            @func.FuncOp.from_py_func(
                RankedTensorType.get(
                    (ShapedType.get_dynamic_size(), ShapedType.get_dynamic_size()),
                    f32Type,
                )
            )
            #      CHECK: func @tensor_static_dim
            # CHECK-SAME:     %[[ARG0:.+]]: tensor<?x?xf32>
            #  CHECK-DAG:   %[[C0:.+]] = arith.constant 0 : index
            #  CHECK-DAG:   %[[C1:.+]] = arith.constant 1 : index
            #      CHECK:   %[[D0:.+]] = tensor.dim %[[ARG0]], %[[C0]]
            #      CHECK:   %[[D1:.+]] = tensor.dim %[[ARG0]], %[[C1]]
            #      CHECK:   return %[[D0]], %[[D1]]
            def tensor_static_dim(t):
                c0 = arith.ConstantOp(indexType, 0)
                c1 = arith.ConstantOp(indexType, 1)
                d0 = tensor.DimOp(t, c0)
                d1 = tensor.DimOp(t, c1)
                return [d0.result, d1.result]

        print(module)


# CHECK-LABEL: TEST: testEmptyOp
@run
def testEmptyOp():
    with Context() as ctx, Location.unknown():
        module = Module.create()
        f32 = F32Type.get()
        with InsertionPoint(module.body):
            # CHECK-LABEL: func @static_sizes
            # CHECK: %0 = tensor.empty() : tensor<3x4xf32>
            @func.FuncOp.from_py_func()
            def static_sizes():
                return tensor.EmptyOp([3, 4], f32)

            # CHECK-LABEL: func @dynamic_sizes
            # CHECK: %0 = tensor.empty(%arg0, %arg1) : tensor<?x?xf32>
            @func.FuncOp.from_py_func(IndexType.get(), IndexType.get())
            def dynamic_sizes(d0, d1):
                return tensor.EmptyOp([d0, d1], f32)

            # CHECK-LABEL: func @mixed_static_dynamic_sizes
            # CHECK: %0 = tensor.empty(%arg0) : tensor<?x4xf32>
            @func.FuncOp.from_py_func(IndexType.get())
            def mixed_static_dynamic_sizes(d0):
                return tensor.EmptyOp([d0, 4], f32)

            # CHECK-LABEL: func @zero_d
            # CHECK: %0 = tensor.empty() : tensor<f32>
            @func.FuncOp.from_py_func()
            def zero_d():
                return tensor.EmptyOp([], f32)

    print(module)


# CHECK-LABEL: TEST: testInferTypesInsertSlice
@run
def testInferTypesInsertSlice():
    with Context() as ctx, Location.unknown():
        module = Module.create()
        f32Type = F32Type.get()
        with InsertionPoint(module.body):

            @func.FuncOp.from_py_func(
                RankedTensorType.get((1, 1), f32Type),
                RankedTensorType.get((1, 1), f32Type),
            )
            # CHECK: func @f
            # CHECK:      tensor.insert_slice %arg0 into %arg1[0, 0] [1, 1] [0, 0] :
            # CHECK-SAME:   tensor<1x1xf32> into tensor<1x1xf32>
            def f(source, dest):
                d0 = tensor.InsertSliceOp(
                    source,
                    dest,
                    [],
                    [],
                    [],
                    DenseI64ArrayAttr.get([0, 0]),
                    DenseI64ArrayAttr.get([1, 1]),
                    DenseI64ArrayAttr.get([0, 0]),
                )
                return [d0.result]

    print(module)


# CHECK-LABEL: TEST: testFromElementsOp
@run
def testFromElementsOp():
    with Context() as ctx, Location.unknown():
        module = Module.create()
        f32 = F32Type.get()
        with InsertionPoint(module.body):

            @func.FuncOp.from_py_func()
            def default_builder():
                c0 = arith.ConstantOp(f32, 0.0)
                # CHECK: %[[C0:.*]] = "arith.constant
                # CHECK-SAME: value = 0.000000e+00 : f32
                print(c0)
                c1 = arith.ConstantOp(f32, 1.0)
                # CHECK: %[[C1:.*]] = "arith.constant
                # CHECK-SAME: value = 1.000000e+00 : f32
                print(c1)

                t = tensor.FromElementsOp(RankedTensorType.get((2,), f32), [c0, c1])
                # CHECK: %{{.*}} = "tensor.from_elements"(%[[C0]], %[[C1]]) : (f32, f32) -> tensor<2xf32>
                print(t)

                t = tensor.FromElementsOp(RankedTensorType.get((2, 1), f32), [c0, c1])
                # CHECK: %{{.*}} = "tensor.from_elements"(%[[C0]], %[[C1]]) : (f32, f32) -> tensor<2x1xf32>
                print(t)

                t = tensor.FromElementsOp(RankedTensorType.get((1, 2), f32), [c0, c1])
                # CHECK: %{{.*}} = "tensor.from_elements"(%[[C0]], %[[C1]]) : (f32, f32) -> tensor<1x2xf32>
                print(t)


# CHECK-LABEL: TEST: testGenerateRegionOp
@run
def testGenerateRegionOp():
    S = ShapedType.get_dynamic_size()
    with Context(), Location.unknown():
        module = Module.create()
        with InsertionPoint(module.body):
            # CHECK: %[[VAL_0:.*]] = arith.constant 1 : index
            # CHECK: %[[VAL_1:.*]] = arith.constant 2 : index
            one = arith.constant(T.index(), 1)
            two = arith.constant(T.index(), 2)

            @tensor.generate(T.tensor(S, 3, S, T.index()), dynamic_extents=[one, two])
            def generate_one(i: T.index(), j: T.index(), k: T.index()):
                ij = arith.addi(i, j)
                ijk = arith.addi(ij, k)
                return ijk

            assert (
                isinstance(generate_one, Value)
                and generate_one.owner.name == "tensor.generate"
            )

        # CHECK:         %[[GENERATED:.*]] = tensor.generate
        # CHECK-SAME:    %[[VAL_0]],
        # CHECK-SAME:    %[[VAL_1]] {
        # CHECK:         ^bb0(%[[VAL_1:.*]]: index, %[[VAL_2:.*]]: index, %[[VAL_3:.*]]: index):
        # CHECK:           %[[VAL_4:.*]] = arith.addi %[[VAL_1]], %[[VAL_2]] : index
        # CHECK:           %[[VAL_5:.*]] = arith.addi %[[VAL_4]], %[[VAL_3]] : index
        # CHECK:           tensor.yield %[[VAL_5]] : index
        # CHECK:         } : tensor<?x3x?xindex>
        print(module)
