# 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.
# pylint: disable=missing-docstring
# ruff: noqa: E501, F401, F841

import re

import pytest

import tvm.testing
from tvm import ir, tirx
from tvm.ir import Range
from tvm.script.ir_builder import IRBuilder
from tvm.script.ir_builder import tirx as T


def _assert_print(obj, expected):
    assert obj.script(verbose_expr=True).strip() == expected.strip()


def test_prim_func():
    A = tirx.decl_buffer(shape=[128, 128], dtype="float32", name="A")
    B = tirx.decl_buffer(shape=[256, 256], dtype="float32", name="B")
    func = (
        tirx.PrimFunc(
            params=[A, B],
            ret_type=None,
            body=tirx.Evaluate(0),
        )
        .with_attr("global_symbol", "main")
        .with_attr("s_tir", True)
    )
    _assert_print(
        func,
        expected="""
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def main(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256), "float32")):
    T.evaluate(0)""",
    )


def test_prim_func_symbolic_buffer_param_roundtrip():
    n = tirx.Var("n", "int32")
    A = tirx.decl_buffer(shape=[n + 1, n], dtype="float32", name="A", layout=None)
    func = (
        tirx.PrimFunc(params=[A], body=tirx.Evaluate(n))
        .with_attr("global_symbol", "main")
        .with_attr("s_tir", True)
    )

    source = func.script()
    assert 'T.Buffer(("n + 1", n)' in source
    assert source.index("n = T.int32()") < source.index("T.evaluate(n)")
    tvm.ir.assert_structural_equal(tvm.script.from_source(source), func)


def test_prim_func_compound_buffer_shape_first_use_roundtrip():
    n = tirx.Var("n", "int32")
    A = tirx.decl_buffer(shape=[tirx.max(n, 1)], dtype="float32", name="A", layout=None)
    func = (
        tirx.PrimFunc(params=[A], body=tirx.Evaluate(n))
        .with_attr("global_symbol", "main")
        .with_attr("s_tir", True)
    )

    source = func.script()
    assert 'T.Buffer(("T.max(n, 1)",)' in source
    assert source.index("n = T.int32()") < source.index("T.evaluate(n)")
    tvm.ir.assert_structural_equal(tvm.script.from_source(source), func)


def test_prim_func_symbolic_alloc_buffer_roundtrip():
    size = tirx.Var("size", "int32")
    buf = tirx.decl_buffer(shape=[size], dtype="float32", name="buf", layout=None)
    func = tirx.PrimFunc(
        params=[],
        body=tirx.SeqStmt([tirx.AllocBuffer(buf), tirx.Evaluate(tirx.BufferLoad(buf, [0]))]),
    ).with_attr("s_tir", True)

    source = func.script()
    assert "T.alloc_buffer((size,))" in source
    tvm.ir.assert_structural_equal(tvm.script.from_source(source, check_well_formed=False), func)


def test_prim_func_buffer_data_use():
    A = tirx.decl_buffer(shape=[128, 128], dtype="float32", name="A")
    B = tirx.decl_buffer(shape=[256, 256], dtype="float32", name="B")
    func = (
        tirx.PrimFunc(
            params=[A, B],
            ret_type=None,
            body=tirx.Evaluate(A.data),
        )
        .with_attr("global_symbol", "main")
        .with_attr("s_tir", True)
    )
    _assert_print(
        func,
        expected="""
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def main(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256), "float32")):
    T.evaluate(A.data)
""",
    )


def test_prim_func_buffer_data_argument_is_scope_hint():
    buffer_data = tirx.decl_buffer(shape=[128, 128], dtype="float32", name="A").data
    A = tirx.decl_buffer(shape=[128, 128], dtype="float32", name="A", data=buffer_data)
    B = tirx.decl_buffer(shape=[256, 256], dtype="float32", name="B", data=buffer_data)
    func = (
        tirx.PrimFunc(
            params=[A, B],
            ret_type=None,
            body=tirx.Evaluate(0),
        )
        .with_attr("global_symbol", "main")
        .with_attr("s_tir", True)
    )
    _assert_print(
        func,
        expected="""
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def main(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256), "float32")):
    T.evaluate(0)
""",
    )


def test_block_realize():
    i = tirx.Var("i", "int32")
    j = tirx.Var("j", "int32")
    k = tirx.Var("k", "int32")
    with IRBuilder() as ib:
        with T.sblock(name="block", no_realize=False):
            vi = ib.name("vi", T.axis.spatial(128, i))
            vj = ib.name("vj", T.axis.spatial(64, j))
            vk = ib.name("vk", T.axis.reduce(32, k))
            T.reads()
            T.writes()
            T.evaluate(0)
    obj = ib.get()
    _assert_print(
        obj,
        """
i = T.int32()
j = T.int32()
k = T.int32()
with T.sblock("block"):
    vi = T.axis.spatial(128, i)
    vj = T.axis.spatial(64, j)
    vk = T.axis.reduce(32, k)
    T.reads()
    T.writes()
    T.evaluate(0)""",
    )


def test_block():
    i = tirx.Var("i", "int32")
    j = tirx.Var("j", "int32")
    k = tirx.Var("k", "int32")
    with IRBuilder() as ib:
        with T.sblock(name="block", no_realize=False):
            vi = ib.name("vi", T.axis.spatial(128, i))
            vj = ib.name("vj", T.axis.spatial(64, j))
            vk = ib.name("vk", T.axis.reduce(32, k))
            T.reads()
            T.writes()
            T.evaluate(0)
    obj = ib.get().block
    _assert_print(
        obj,
        """
with T.sblock("block", no_realize=True):
    vi = T.axis.spatial(128)
    vj = T.axis.spatial(64)
    vk = T.axis.reduce(32)
    T.reads()
    T.writes()
    T.evaluate(0)""",
    )


def test_match_buffer_region():
    src = tirx.decl_buffer((128, 128), "float32", name="src")
    tgt = tirx.decl_buffer((64, 64), "float32", name="tgt")
    obj = tirx.MatchBufferRegion(
        tgt,
        tirx.BufferRegion(
            src,
            [
                Range(64, 128),
                Range(64, 128),
            ],
        ),
    )
    _assert_print(
        obj,
        """
src = T.Buffer((128, 128))
tgt = T.match_buffer(src[64:128, 64:128], (64, 64))
""",
    )


def test_buffer():
    a = tirx.decl_buffer((128, 128), "float16", name="A")
    _assert_print(
        a,
        """A = T.Buffer((128, 128), "float16")
A""",
    )


def test_buffer_region():
    src = tirx.decl_buffer((128, 128), "float32", name="src")
    obj = tirx.BufferRegion(
        src,
        [
            Range(64, 128),
            Range(64, 128),
        ],
    )
    _assert_print(
        obj,
        """
src = T.Buffer((128, 128))
src[64:128, 64:128]
""",
    )


def test_buffer_load():
    a = tirx.decl_buffer((128, 128), "float16", name="A")
    obj = tirx.BufferLoad(a, [128, 128])
    _assert_print(
        obj,
        """
A = T.Buffer((128, 128), "float16")
A[128, 128]
""",
    )


def test_buffer_store():
    a = tirx.decl_buffer((128, 128), "float16", name="A")
    with IRBuilder() as ib:
        T.buffer_store(a, a[128, 128] + 1, [128, 128])
    obj = ib.get()
    _assert_print(
        obj,
        """
A = T.Buffer((128, 128), "float16")
A[128, 128] = A[128, 128] + T.float16(1.0)
""",
    )


def test_for():
    with IRBuilder() as ib:
        with T.grid(128, 128, 128) as (i, j, k):
            ib.name_many(["i", "j", "k"], [i, j, k])
            T.evaluate(0)
    obj = ib.get()
    _assert_print(
        obj,
        """
for i, j, k in T.grid(128, 128, 128):
    T.evaluate(0)
""",
    )


def test_bind():
    with IRBuilder() as ib:
        with T.prim_func(s_tir=True):
            v = T.bind(T.float32(10))
            ib.name("v", v)
            T.evaluate(1)
    obj = ib.get()
    _assert_print(
        obj,
        """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(private=True, s_tir=True)
def main():
    v: T.let[T.float32] = T.float32(10.0)
    T.evaluate(1)
""",
    )


def test_attr_stmt():
    with IRBuilder() as ib:
        with T.attr("pragma", "unroll", 1):
            T.evaluate(0)
    obj = ib.get()
    _assert_print(
        obj,
        """
with T.attr("pragma", "unroll", 1):
    T.evaluate(0)
""",
    )


def test_assert_stmt():
    with IRBuilder() as ib:
        with T.Assert(True, "assertion"):
            T.evaluate(0)
    obj = ib.get()
    _assert_print(
        obj,
        """
assert T.bool(True), ("RuntimeError", ["assertion"])
T.evaluate(0)
""",
    )


def test_while():
    with IRBuilder() as ib:
        x = T.int32()
        with T.While(x < 10):
            T.evaluate(0)
    obj = ib.get()
    _assert_print(
        obj,
        """
v = T.int32()
while v < 10:
    T.evaluate(0)
""",
    )


def test_allocate():
    with IRBuilder() as ib:
        with T.prim_func():
            T.func_name("test")
            buf = T.alloc_buffer([128, 128], "float32")
            T.evaluate(1)
    obj = ib.get()
    _assert_print(
        obj.body,
        """
buffer = T.alloc_buffer((128, 128))
T.evaluate(1)
""",
    )


def test_allocate_with_decl_buffer_sugar():
    # AllocBuffer and DeclBuffer are flat siblings
    with IRBuilder() as ib:
        with T.prim_func():
            T.func_name("test")
            buf = T.alloc_buffer([128, 128], "float32")
            buf2 = T.decl_buffer([128, 128], "float32", data=buf.data)
            T.evaluate(1)
    obj = ib.get()
    _assert_print(
        obj.body,
        """
buffer = T.alloc_buffer((128, 128))
buffer_1 = T.decl_buffer((128, 128), data=buffer.data)
T.evaluate(1)
""",
    )


def test_allocate_with_decl_buffer_sugar_multi_usage():
    # AllocBuffer and DeclBuffer are flat siblings
    with IRBuilder() as ib:
        with T.prim_func():
            T.func_name("test")
            buf = T.alloc_buffer([128, 128], "float32")
            buf2 = T.decl_buffer([128, 128], "float32", data=buf.data)
            T.evaluate(buf.data)
    obj = ib.get()
    _assert_print(
        obj.body,
        """
buffer = T.alloc_buffer((128, 128))
buffer_1 = T.decl_buffer((128, 128), data=buffer.data)
T.evaluate(buffer.data)
""",
    )


def test_allocate_with_decl_buffer_no_sugar_mismatch():
    with IRBuilder() as ib:
        with T.prim_func():
            T.func_name("test")
            buf = T.alloc_buffer([128, 128], "float32")
            buf2 = T.decl_buffer([256, 256], "float32", data=buf.data)
            T.evaluate(buf.data)
    obj = ib.get()
    _assert_print(
        obj.body,
        """
buffer = T.alloc_buffer((128, 128))
buffer_1 = buffer.view(256, 256)
T.evaluate(buffer.data)
""",
    )


def test_decl_buffer():
    # DeclBuffer is flat: we need a frame to hold multiple stmts
    with IRBuilder() as ib:
        with T.prim_func():
            T.func_name("test")
            buf = T.decl_buffer((10, 10), data=T.ptr("float32"))
            T.evaluate(1)
    obj = ib.get()
    # Print only the body (skip PrimFunc wrapper)
    _assert_print(
        obj.body,
        """
v = T.handle("float32", "global")
buffer = T.decl_buffer((10, 10), data=v)
T.evaluate(1)
""",
    )


def test_seq_stmt():
    with IRBuilder() as ib:
        with T.serial(10):
            T.evaluate(1)
            T.evaluate(2)
    obj = ib.get().body
    _assert_print(
        obj,
        """
T.evaluate(1)
T.evaluate(2)
""",
    )


def test_if_then_else():
    with IRBuilder() as ib:
        with T.If(T.int32() == 1):
            with T.Then():
                T.evaluate(0)

    obj = ib.get()
    _assert_print(
        obj,
        """
v = T.int32()
if v == 1:
    T.evaluate(0)
""",
    )


def test_evaluate():
    with IRBuilder() as ib:
        T.evaluate(0)
    obj = ib.get()
    _assert_print(
        obj,
        """
T.evaluate(0)
""",
    )


def test_var():
    a = tirx.Var("a", "float32")
    _assert_print(
        a,
        """
a = T.float32()
a""",
    )

    a = tirx.Var("a", "handle")
    _assert_print(
        a,
        """
a = T.handle()
a""",
    )

    a = tirx.Var("a", ir.PointerType(ir.PrimType("void"), "shared"))
    _assert_print(
        a,
        """
a = T.handle(storage_scope="shared")
a""",
    )


def test_iter_var():
    a = tirx.IterVar((0, 8), "a", iter_type=tirx.IterVar.DataPar)
    _assert_print(
        a,
        """
a = T.int32()
T.iter_var(a, T.Range(0, 8), "DataPar", "")
""",
    )


def test_string_imm():
    s = tirx.StringImm("str")
    _assert_print(s, '"str"')


def test_cast():
    obj = tirx.Cast("float64", tirx.Var("a", "float32"))
    _assert_print(
        obj,
        """
a = T.float32()
T.Cast("float64", a)
""",
    )


def test_llvm_intrin_imm():
    a = tirx.call_llvm_intrin("int32x4", "llvm.donothing")
    _assert_print(a, 'T.call_llvm_intrin("int32x4", "llvm.donothing")')
    a = tirx.call_llvm_pure_intrin("int32x4", "llvm.donothing")
    _assert_print(a, 'T.call_llvm_pure_intrin("int32x4", "llvm.donothing")')


def test_binary_arith():
    a = tirx.Var("a", "int32")
    b = tirx.Var("b", "int32")
    for op, sign in [
        (tirx.Add, "+"),
        (tirx.Sub, "-"),
        (tirx.Mul, "*"),
        (tirx.Mod, "truncmod"),
        (tirx.FloorDiv, "//"),
        (tirx.FloorMod, "%"),
        (tirx.LT, "<"),
        (tirx.LE, "<="),
        (tirx.EQ, "=="),
        (tirx.NE, "!="),
        (tirx.GT, ">"),
        (tirx.GE, ">="),
    ]:
        obj = op(a, b)
        if sign.isalpha():
            expected = f"""
a = T.int32()
b = T.int32()
T.{sign}(a, b)"""
        else:
            expected = f"""
a = T.int32()
b = T.int32()
a {sign} b"""
        _assert_print(obj, expected)


def test_binary_arith_const():
    a = tirx.IntImm("int64", 3)
    b = tirx.IntImm("int64", 4)
    for op, name in [
        (tirx.Add, "Add"),
        (tirx.Sub, "Sub"),
        (tirx.Mul, "Mul"),
        (tirx.Div, "Div"),
        (tirx.Mod, "truncmod"),
        (tirx.FloorDiv, "FloorDiv"),
        (tirx.FloorMod, "FloorMod"),
        (tirx.LT, "LT"),
        (tirx.LE, "LE"),
        (tirx.EQ, "EQ"),
        (tirx.NE, "NE"),
        (tirx.GT, "GT"),
        (tirx.GE, "GE"),
    ]:
        obj = op(a, b)
        expected = f"""
T.{name}({a!s}, {b!s})"""
        _assert_print(obj, expected)


def test_int_div():
    a = tirx.Var("a", "int32")
    b = tirx.Var("b", "int32")
    _assert_print(
        tirx.Div(a, b),
        """
a = T.int32()
b = T.int32()
T.Div(a, b)
""",
    )


def test_logical():
    a = tirx.Var("a", "bool")
    b = tirx.Var("b", "bool")
    _assert_print(
        tirx.And(a, b),
        """
a = T.bool()
b = T.bool()
a and b
""",
    )
    _assert_print(
        tirx.Or(a, b),
        """
a = T.bool()
b = T.bool()
a or b
""",
    )
    _assert_print(
        tirx.Not(a),
        """
a = T.bool()
not a
""",
    )


def test_select():
    obj = tirx.Select(True, 0, 2)
    _assert_print(
        obj,
        """T.Select(T.bool(True), 0, 2)
""",
    )


@pytest.mark.parametrize(
    "lanes, scripted_lanes", [(32, "32"), (tvm.tirx.vscale() * 8, "T.vscale() * 8")]
)
def test_ramp(lanes, scripted_lanes):
    a = tirx.Var("a", "int32")
    obj = tirx.Ramp(a, 1, lanes)
    _assert_print(
        obj,
        f"""
a = T.int32()
T.Ramp(a, 1, {scripted_lanes})
""",
    )


@pytest.mark.parametrize(
    "lanes, scripted_lanes", [(4, "4"), (tvm.tirx.vscale() * 4, "T.vscale() * 4")]
)
def test_broadcast(lanes, scripted_lanes):
    obj = tirx.Broadcast(0, lanes)
    _assert_print(
        obj,
        f"""
T.Broadcast(0, {scripted_lanes})
""",
    )


def test_let_expr():
    x = tirx.Var("x", "int32")
    obj = tirx.Let(x, 1, x + 1)
    _assert_print(
        obj,
        """
x = T.int32()
T.Let(x + 1, where={x: 1})
""",
    )


def test_call():
    obj = tirx.atan(T.float32(1.0))
    _assert_print(
        obj,
        """
T.atan(T.float32(1.0))
""",
    )


def test_comm_reducer():
    obj = T.comm_reducer(lambda x, y: x + y, identity=[T.float32(0)])
    _assert_print(
        obj,
        """
T.comm_reducer(lambda x, y: x + y, [T.float32(0.0)])
""",
    )


def test_int_imm():
    obj = T.int16(1)
    _assert_print(
        obj,
        """
T.int16(1)
""",
    )


def test_float_imm():
    obj = T.float16(1)
    _assert_print(
        obj,
        """
T.float16(1.0)
""",
    )


def test_range():
    obj = Range(0, 10)
    _assert_print(
        obj,
        """
I.Range(0, 10)
""",
    )


def test_prim_type():
    obj = ir.PrimType("float32")
    _assert_print(obj, "T.float32")


def test_pointer_type():
    obj = ir.PointerType(ir.PrimType("int32"), "global")
    _assert_print(obj, 'T.handle("int32", "global")')

    obj = ir.PointerType(ir.PrimType("void"))
    _assert_print(obj, "T.handle")

    obj = ir.PointerType(ir.PrimType("void"), "shared")
    _assert_print(obj, 'T.handle(storage_scope="shared")')


def test_tuple_type():
    obj = ir.TupleType([ir.PrimType("float32"), ir.PrimType("int32")])
    _assert_print(obj, "T.Tuple(T.float32, T.int32)")


def test_remap():
    from tvm.script import tirx as T

    @T.prim_func(s_tir=True)
    def block_with_remap_implicitly():
        for i0, i1, i2, i3, i4, i5 in T.grid(128, 128, 128, 128, 128, 128):
            with T.sblock("update"):
                v0 = T.axis.spatial(128, i0 + 1)
                v1 = T.axis.spatial(128, i1)
                v2 = T.axis.reduce(128, i2)
                v3 = T.axis.spatial(128, i3 - 1)
                v4 = T.axis.reduce(128, i4)
                v5 = T.axis.spatial(128, i5)

    @T.prim_func(s_tir=True)
    def block_with_remap_explicitly():
        for i0, i1, i2, i3, i4, i5 in T.grid(128, 128, 128, 128, 128, 128):
            with T.sblock("update"):
                v0 = T.axis.spatial(128, i0 + 1)
                v1, v2 = T.axis.remap("SR", [i1, i2])
                v3 = T.axis.spatial(128, i3 - 1)
                v4, v5 = T.axis.remap("RS", [i4, i5])

    expected_output = """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def main():
    # with T.sblock("root"):
    for i0, i1, i2, i3, i4, i5 in T.grid(128, 128, 128, 128, 128, 128):
        with T.sblock("update"):
            v0 = T.axis.spatial(128, i0 + 1)
            v1, v2 = T.axis.remap("SR", [i1, i2])
            v3 = T.axis.spatial(128, i3 - 1)
            v4, v5 = T.axis.remap("RS", [i4, i5])
            T.reads()
            T.writes()
            T.evaluate(0)"""
    _assert_print(block_with_remap_explicitly.with_attr("global_symbol", "main"), expected_output)
    _assert_print(block_with_remap_implicitly.with_attr("global_symbol", "main"), expected_output)


def test_root_block():
    from tvm.script import tirx as T

    @T.prim_func(s_tir=True)
    def root_block_implicitly():
        a = T.sblock_alloc_buffer([128, 128])
        for i, j in T.grid(128, 128):
            with T.sblock():
                T.evaluate(0)

    @T.prim_func(s_tir=True)
    def root_block_explicitly():
        with T.sblock("root"):
            a = T.sblock_alloc_buffer([128, 128])
            for i, j in T.grid(128, 128):
                with T.sblock():
                    T.evaluate(0)

    expected_output = """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def main():
    # with T.sblock("root"):
    a = T.sblock_alloc_buffer((128, 128))
    for i, j in T.grid(128, 128):
        with T.sblock(""):
            T.reads()
            T.writes()
            T.evaluate(0)
    """
    _assert_print(root_block_implicitly.with_attr("global_symbol", "main"), expected_output)
    _assert_print(root_block_explicitly.with_attr("global_symbol", "main"), expected_output)


def test_private_primfunc():
    from tvm.script import tirx as T

    A = tirx.decl_buffer(shape=[128, 128], dtype="float32", name="A")
    B = tirx.decl_buffer(shape=[256, 256], dtype="float32", name="B")
    func = tirx.PrimFunc(
        params=[A, B],
        ret_type=None,
        body=tirx.Evaluate(0),
    ).with_attr("s_tir", True)
    _assert_print(
        func,
        expected="""
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256), "float32")):
    T.evaluate(0)""",
    )


def test_prim_func_different_symbol():
    from tvm.script import tirx as T

    @T.prim_func(s_tir=True)
    def main(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256), "float32")):
        T.func_attr({"global_symbol": "func"})
        T.evaluate(0)

    expected_output = """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def func(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256), "float32")):
    T.evaluate(0)
    """
    _assert_print(main, expected_output)


def test_variable_with_cpp_address():
    """The show_object_address option displays the C++ addressess

    Because the C++ address may vary with each execution, the output
    produced with this option cannot be compared to a fixed string.
    Instead, this test uses the normal script output to generate a
    regular expression against with the test output must match.  The
    regular expression validates that all names have been appended
    with "_0x" followed by a hexadecimal number, and that the address
    is the same for each variable.
    """
    from tvm.script import tirx as T

    # The test function has all named objects suffixed with "_name",
    # to avoid spurious replacement when generating the expected
    # regex.
    @T.prim_func(s_tir=True)
    def func(a_name: T.handle):
        N_name = T.int64()
        A_name = T.match_buffer(a_name, N_name, "float32")
        for i_name in range(N_name):
            A_name[i_name] = A_name[i_name] + 1.0

    without_address = func.script(show_object_address=False)
    script = func.script(show_object_address=True)

    expected_regex = re.escape(without_address)
    for name in ["a_name", "A_name", "N_name", "i_name"]:
        # Replace all occurrences with a backref to an earlier match
        expected_regex = expected_regex.replace(name, rf"(?P={name})")
        # Then replace the first such backref with a capturing group.
        expected_regex = expected_regex.replace(
            rf"(?P={name})", rf"(?P<{name}>{name}_0x[A-Fa-f0-9]+)", 1
        )

    assert re.match(expected_regex, script)


def test_return_statement():
    from tvm.script import tirx as T

    @T.prim_func(s_tir=True)
    def func():
        return T.int32(5)

    expected_output = """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def func():
    return 5
    """
    _assert_print(func, expected_output)
    assert func.script(verbose_expr=True, syntax_sugar=False).strip() == expected_output.strip()


CUSTOM_FLOAT_DTYPES = [
    # Float8 variants
    "float8_e3m4",
    "float8_e4m3",
    "float8_e4m3b11fnuz",
    "float8_e4m3fn",
    "float8_e4m3fnuz",
    "float8_e5m2",
    "float8_e5m2fnuz",
    "float8_e8m0fnu",
    # Float6 variants
    "float6_e2m3fn",
    "float6_e3m2fn",
    # Float4 variant
    "float4_e2m1fn",
]


@pytest.mark.parametrize("dtype", CUSTOM_FLOAT_DTYPES)
def test_custom_float_types(dtype):
    from tvm.script import tirx as T

    @T.prim_func(s_tir=True)
    def func():
        T.evaluate(getattr(T, dtype)(0.0))

    expected_output = f"""
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def func():
    T.evaluate(T.{dtype}(0.0))
"""
    _assert_print(func, expected_output)


def test_predicated_load_store():
    from tvm.script import tirx as T

    @T.prim_func(s_tir=True)
    def main(a: T.handle, b: T.handle):
        A = T.match_buffer(a, (128, 128), "float32")
        B = T.match_buffer(b, (256, 256), "float32")
        T.func_attr({"global_symbol": "func"})
        a_load = T.meta_var(A.vload([0, T.Ramp(0, 4, 4)], predicate=T.Broadcast(T.bool(False), 4)))
        A.vstore([0, T.Ramp(0, 2, 4)], a_load, predicate=T.Broadcast(T.bool(False), 4))

    expected_output = """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def func(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256), "float32")):
    A.vstore([0, T.Ramp(0, 2, 4)], A.vload([0, T.Ramp(0, 4, 4)], predicate=T.Broadcast(T.bool(False), 4)), predicate=T.Broadcast(T.bool(False), 4))
    """
    _assert_print(main, expected_output)


def test_predicated_buffer_load_store():
    a = tirx.Var("a", "handle")
    b = tirx.Var("b", "handle")
    buffers = {
        a: tirx.decl_buffer(shape=[128, 128], dtype="float32", name="A"),
        b: tirx.decl_buffer(shape=[256, 256], dtype="float32", name="B"),
    }
    buffer_load = tirx.BufferLoad(
        buffer=buffers[b],
        indices=[0, tirx.Ramp(0, 4, 4)],
        predicate=tirx.Broadcast(tirx.IntImm("bool", 0), 4),
    )
    body = tirx.BufferStore(
        buffer=buffers[a],
        value=buffer_load,
        indices=[0, tirx.Ramp(0, 2, 4)],
        predicate=tirx.Broadcast(tirx.IntImm("bool", 0), 4),
    )
    func = tirx.PrimFunc(
        params=[buffers[a], buffers[b]],
        ret_type=None,
        body=body,
    ).with_attr("s_tir", True)

    expected_output = """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(private=True, s_tir=True)
def main(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256), "float32")):
    A.vstore([0, T.Ramp(0, 2, 4)], B.vload([0, T.Ramp(0, 4, 4)], predicate=T.Broadcast(T.bool(False), 4)), predicate=T.Broadcast(T.bool(False), 4))
    """
    _assert_print(func, expected_output)


def test_predicated_scalable_load_store():
    from tvm.script import tirx as T

    @T.prim_func(s_tir=True)
    def main(a: T.handle, b: T.handle):
        A = T.match_buffer(a, (128, 128), "float32")
        B = T.match_buffer(b, (256, 256), "float32")
        T.func_attr({"global_symbol": "func"})
        mask = T.meta_var(T.get_active_lane_mask("uint1xvscalex4", 0, 13))
        a_load = T.meta_var(A.vload([0, T.Ramp(0, 4, T.vscale() * 4)], predicate=mask))
        A.vstore([0, T.Ramp(0, 2, T.vscale() * 4)], a_load, predicate=mask)

    expected_output = """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def func(A: T.Buffer((128, 128), "float32"), B: T.Buffer((256, 256), "float32")):
    A.vstore([0, T.Ramp(0, 2, T.vscale() * 4)], A.vload([0, T.Ramp(0, 4, T.vscale() * 4)], predicate=T.get_active_lane_mask("uint1xvscalex4", 0, 13)), predicate=T.get_active_lane_mask("uint1xvscalex4", 0, 13))
    """
    _assert_print(main, expected_output)


def test_vload_with_explicit_scalable_data_type():
    from tvm.script import tirx as T

    @T.prim_func(s_tir=True)
    def main(a: T.handle, b: T.handle):
        A = T.match_buffer(a, (128,), "float32")
        B = T.match_buffer(b, (128,), "float32")
        B[0 : T.vscale() * 4] = A.vload([T.Ramp(0, 1, T.vscale() * 4)], dtype="float32xvscalex4")

    expected_output = """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def main(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32")):
    B[0:T.vscale() * 4] = A[0:T.vscale() * 4]
    """
    _assert_print(main, expected_output)


def test_vectorize_llvm_pure_intrin():
    from tvm.script import tirx as T

    @T.prim_func(s_tir=True)
    def main(a: T.handle, b: T.handle):
        A = T.match_buffer(a, (4,), "float32")
        B = T.match_buffer(b, (4,), "float32")
        A[T.Ramp(0, 1, 4)] = T.call_llvm_pure_intrin("float32x4", "llvm.sqrt", B[T.Ramp(0, 1, 4)])

    expected_output = """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def main(A: T.Buffer((4,), "float32"), B: T.Buffer((4,), "float32")):
    A[0:4] = T.call_llvm_pure_intrin("float32x4", "llvm.sqrt", B[0:4])
    """
    _assert_print(main, expected_output)


def test_func_with_loop_jumps():
    from tvm.script import tirx as T

    @T.prim_func(s_tir=True)
    def main(a: T.handle, b: T.handle):
        A = T.match_buffer(a, (4,), "float32")
        B = T.match_buffer(b, (4,), "float32")
        for i in range(1000):
            if i % 13 == 0:
                A[1] = A[1] + 1
                continue
            if A[0] >= B[0]:
                break

    expected_output = """
# from tvm.script import tirx as T
# from tvm.tirx.layout import Axis

@T.prim_func(s_tir=True)
def main(A: T.Buffer((4,), "float32"), B: T.Buffer((4,), "float32")):
    for i in range(1000):
        if i % 13 == 0:
            A[1] = A[1] + T.float32(1.0)
            T.continue_loop()
        if A[0] >= B[0]:
            T.break_loop()
    """
    _assert_print(main, expected_output)


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