# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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.
"""Test the TIR codegen path of VM compiled mode.

Restrictions: all shape lowered, explicit allocation.
"""

import tvm
import tvm.testing
from tvm import relax
from tvm.ir import assert_structural_equal
from tvm.script import relax as R
from tvm.script import tirx as T


def get_tir_mod(mod):
    builder = relax.ExecBuilder()
    return relax.vm_build._vmcodegen(builder, mod, exec_mode="compiled")


def test_add():
    @tvm.script.ir_module
    class Before:
        @R.function(pure=False)
        def foo(x: R.Tensor):
            R.func_attr({"global_symbol": "foo"})
            z = R.call_packed("test.vm.add", x, x, ty_args=(R.Tensor))
            return z

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(s_tir=True)
        def __vmtir__foo(ctx_ptr: T.handle, r: T.handle, c: T.handle, f: T.handle):
            T.func_attr({"global_symbol": "__vmtir__foo"})
            T.anylist_setitem_call_packed(
                r,
                T.int32(2),
                "test.vm.add",
                T.anylist_getitem(r, T.int32(0)),
                T.anylist_getitem(r, T.int32(0)),
            )
            T.anylist_setitem_call_packed(
                r, T.int32(1), "vm.builtin.copy", T.anylist_getitem(r, T.int32(2))
            )

    before = Before
    expected = Expected
    after = get_tir_mod(before)
    assert_structural_equal(expected, after)


def test_tir_call():
    @tvm.script.ir_module
    class Before:
        @T.prim_func(s_tir=True)
        def shape_func(H: T.Buffer(T.int64(4), "int64")):
            T.func_attr({"global_symbol": "shape_func"})
            # generated compute function
            H[T.int64(0)] = H[T.int64(0)] + T.int64(1)

        @R.function(pure=False)
        def foo(x: R.Tensor([4], "int64")):
            R.func_attr({"global_symbol": "foo"})
            _ = Before.shape_func(x)
            return x

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(s_tir=True)
        def shape_func(H: T.Buffer(T.int64(4), "int64")):
            T.func_attr({"global_symbol": "shape_func"})
            # generated compute function
            H[T.int64(0)] = H[T.int64(0)] + T.int64(1)

        @T.prim_func(s_tir=True)
        def __vmtir__foo(ctx_ptr: T.handle, r: T.handle, c: T.handle, f: T.handle):
            T.func_attr({"global_symbol": "__vmtir__foo"})
            T.call_cpacked("shape_func", T.anylist_getitem(r, T.int32(0)))
            T.anylist_setitem_call_packed(
                r, T.int32(1), "vm.builtin.copy", T.anylist_getitem(r, T.int32(0))
            )

    before = Before
    expected = Expected
    after = get_tir_mod(before)
    assert_structural_equal(expected, after)


def test_if_cond():
    @tvm.script.ir_module
    class Before:
        @R.function(pure=False)
        def ife(cond: R.Tensor((), "bool"), x: R.Tensor) -> R.Tensor:
            R.func_attr({"global_symbol": "ife"})
            if cond:
                w = R.call_packed("test.vm.add", x, x, ty_args=(R.Tensor))
            else:
                w = R.call_packed("test.vm.mul", x, x, ty_args=(R.Tensor))
            return w

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(s_tir=True)
        def __vmtir__ife(ctx_ptr: T.handle, r: T.handle, c: T.handle, f: T.handle):
            T.func_attr({"global_symbol": "__vmtir__ife"})
            if T.Call(
                tvm.ir.Op.get("tirx.tvm_call_packed"),
                ["vm.builtin.read_if_cond", T.anylist_getitem(r, T.int32(0))],
                ret_ty="bool",
            ):
                T.anylist_setitem_call_packed(
                    r,
                    T.int32(4),
                    "test.vm.add",
                    T.anylist_getitem(r, T.int32(1)),
                    T.anylist_getitem(r, T.int32(1)),
                )
                T.anylist_setitem_call_packed(
                    r, T.int32(3), "vm.builtin.copy", T.anylist_getitem(r, T.int32(4))
                )
            else:
                T.anylist_setitem_call_packed(
                    r,
                    T.int32(5),
                    "test.vm.mul",
                    T.anylist_getitem(r, T.int32(1)),
                    T.anylist_getitem(r, T.int32(1)),
                )
                T.anylist_setitem_call_packed(
                    r, T.int32(3), "vm.builtin.copy", T.anylist_getitem(r, T.int32(5))
                )
            T.anylist_setitem_call_packed(
                r, T.int32(2), "vm.builtin.copy", T.anylist_getitem(r, T.int32(3))
            )

    before = Before
    expected = Expected
    after = get_tir_mod(before)
    assert_structural_equal(expected, after)


def test_const():
    @tvm.script.ir_module
    class Before:
        @R.function
        def main(x: R.Tensor):
            R.func_attr({"global_symbol": "main"})
            y = R.const([1, 2])
            z = (y, R.const([3, 4]), x)
            return z

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(s_tir=True)
        def __vmtir__main(ctx_ptr: T.handle, r: T.handle, c: T.handle, f: T.handle):
            # function attr dict
            T.func_attr({"global_symbol": "__vmtir__main"})
            # body
            T.anylist_setitem_call_packed(
                r,
                T.int32(2),
                "vm.builtin.make_tuple",
                T.anylist_getitem(c, T.int32(0)),
                T.anylist_getitem(c, T.int32(1)),
                T.anylist_getitem(r, T.int32(0)),
            )
            T.anylist_setitem_call_packed(
                r, T.int32(1), "vm.builtin.copy", T.anylist_getitem(r, T.int32(2))
            )

    before = Before
    expected = Expected
    after = get_tir_mod(before)
    assert_structural_equal(expected, after)


def test_const_call():
    @tvm.script.ir_module
    class Before:
        @R.function(pure=False)
        def main(x: R.Tensor):
            R.func_attr({"global_symbol": "main"})
            y = R.const([1, 2])
            z = R.call_packed("test.vm.add", x, y, ty_args=(R.Tensor))
            return z

    @tvm.script.ir_module
    class Expected:
        @T.prim_func(s_tir=True)
        def __vmtir__main(ctx_ptr: T.handle, r: T.handle, c: T.handle, f: T.handle):
            # function attr dict
            T.func_attr({"global_symbol": "__vmtir__main"})
            # body
            T.anylist_setitem_call_packed(
                r,
                2,
                "test.vm.add",
                T.anylist_getitem(r, 0),
                T.anylist_getitem(c, 0),
            )
            T.anylist_setitem_call_packed(r, 1, "vm.builtin.copy", T.anylist_getitem(r, 2))

    before = Before
    expected = Expected
    after = get_tir_mod(before)
    assert_structural_equal(expected, after)


if __name__ == "__main__":
    tvm.testing.main()
