# 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.
"""IRBuilder for TIR"""

import contextlib
import functools
import inspect
import threading
from collections.abc import Callable
from functools import partial
from numbers import Integral
from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar, Union

# isort: off
from typing import Literal

# isort: on

from tvm_ffi.core import String

from tvm import DataType, ir
from tvm import tirx as tir
from tvm.ir import Call, Type, is_prim_expr
from tvm.ir import register_op_attr as _register_op_attr
from tvm.ir.base import deprecated
from tvm.runtime import convert
from tvm.script.ir_builder.base import IRBuilder
from tvm.target import Target

# pylint: disable=unused-import
from tvm.target.codegen import llvm_lookup_intrinsic_id
from tvm.tirx import Buffer, BufferRegion, Expr, IndexMap, type_annotation
from tvm.tirx import _ffi_api as _tirx_ffi_api
from tvm.tirx import op as _tir_op
from tvm.tirx.exec_scope import ExecScope, ScopeIdDef, Var

# import tirx.expr for direct ir construction to pass structural_equal comparison
from tvm.tirx.expr import (
    EQ,
    GE,
    GT,
    LE,
    LT,
    NE,
    Add,
    And,
    Broadcast,
    BufferLoad,
    CallEffectKind,
    Cast,
    CommReducer,
    Div,
    FloatImm,
    FloorDiv,
    FloorMod,
    IntImm,
    IterVar,
    Max,
    Min,
    Mod,
    Mul,
    Not,
    Or,
    ProducerLoad,
    Ramp,
    Reduce,
    Select,
    Shuffle,
    StringImm,
    Sub,
)
from tvm.tirx.layout import (
    ComposeLayout,
    Iter,
    Layout,
    R,
    S,
    SwizzleLayout,
    TileLayout,
    wg_local_layout,
)

from . import _ffi_api, frame, utils
from .external_kernel import call_kernel

# pylint: enable=unused-import


def cast(value, dtype, span=None):
    """Cast an expression to the requested data type."""
    return _tirx_ffi_api._cast(dtype, value, span)  # type: ignore[attr-defined]


def _current_s_tir() -> bool:
    """Return True if the innermost enclosing PrimFuncFrame has ``s_tir=True``.

    Gates the parser's default layout fill: ``s_tir=True`` PrimFuncs leave
    ``layout=None`` (so s_tir-style passes that don't touch layout round-trip
    cleanly); ``s_tir=False`` (default, tirx) get ``DefaultLayout(shape)``.
    """
    from tvm.script.ir_builder.base import IRBuilder  # local import to avoid cycle

    if not IRBuilder.is_in_scope():
        return False
    builder = IRBuilder.current()
    for f in reversed(list(builder.frames)):
        if isinstance(f, frame.PrimFuncFrame):
            return bool(f.s_tir)
    return False


def _get_layout(layout: str | Layout | None, shape: list[Expr], scope: str) -> Layout | None:
    if layout is None:
        return None
    if isinstance(layout, Layout):
        return layout
    assert isinstance(layout, str)
    if layout == "default":
        if _current_s_tir():
            return None
        if scope in ["trn.sbuf", "trn.psum"]:
            return None
        return TileLayout(S[tuple(shape)])
    shape = tuple(shape)
    if scope == "trn.sbuf":
        layout = TileLayout.trainium(layout, shape)
    elif scope == "trn.psum":
        layout = TileLayout.trainium(layout, shape).to_psum()
    return layout


def _normalize_prim_type(dtype) -> ir.PrimType:
    if isinstance(dtype, ir.PrimType):
        return dtype
    dtype_str = getattr(dtype, "_dtype_str", None)
    if dtype_str is not None:
        return ir.PrimType(dtype_str)
    if callable(dtype):
        value = dtype()
        ty = getattr(value, "ty", None)
        if isinstance(ty, ir.PrimType):
            return ty
    return ir.PrimType(dtype)


def _get_elem_offset(elem_offset, byte_offset, dtype: str):
    assert elem_offset is None or byte_offset is None, (
        "elem_offset and byte_offset cannot be set at the same time"
    )
    if elem_offset is not None:
        return elem_offset
    if byte_offset is None:
        return None
    return byte_offset * 8 // (DataType(_normalize_prim_type(dtype).dtype).bits)


_block_name_suffix = threading.local()
_meta_construction_state = threading.local()
_THIS_FILE = __file__


class _MetaResourceRecord:
    """Resource created while constructing a meta_class instance."""

    def __init__(
        self, value: Any, filename: str, lineno: int, colno: int | None, code: str
    ) -> None:
        self.value = value
        self.filename = filename
        self.lineno = lineno
        self.colno = colno
        self.code = code


class _MetaConstructionScope:
    """Thread-local construction scope for a single meta_class __init__ call."""

    def __init__(self, instance: Any, cls: type) -> None:
        self.instance = instance
        self.cls = cls
        self.created: list[_MetaResourceRecord] = []

    def record(self, value: Any, frame_info: inspect.FrameInfo) -> None:
        positions = getattr(frame_info, "positions", None)
        colno = None
        if positions is not None and positions.col_offset is not None:
            colno = positions.col_offset + 1
        code = frame_info.code_context[0].strip() if frame_info.code_context else ""
        self.created.append(
            _MetaResourceRecord(
                value=value,
                filename=frame_info.filename,
                lineno=frame_info.lineno,
                colno=colno,
                code=code,
            )
        )


def _meta_construction_stack() -> list[_MetaConstructionScope]:
    stack = getattr(_meta_construction_state, "stack", None)
    if stack is None:
        stack = []
        _meta_construction_state.stack = stack
    return stack


def _current_meta_construction_scope() -> _MetaConstructionScope | None:
    stack = _meta_construction_stack()
    return stack[-1] if stack else None


@contextlib.contextmanager
def _with_meta_construction_scope(instance: Any, cls: type):
    scope = _MetaConstructionScope(instance, cls)
    stack = _meta_construction_stack()
    stack.append(scope)
    try:
        yield scope
    finally:
        stack.pop()


def _record_meta_resource(value: Any, skip_frames: int = 2) -> None:
    scope = _current_meta_construction_scope()
    if scope is not None:
        stack = inspect.stack(context=1)
        frame_info = None
        for candidate in stack[2:]:
            if candidate.filename != _THIS_FILE:
                frame_info = candidate
                break
        if frame_info is None:
            frame_info = stack[min(skip_frames + 1, len(stack) - 1)]
        scope.record(value, frame_info)


def _get_sblock_name_suffix() -> str:
    """Get the current block name suffix for macro expansion."""
    return getattr(_block_name_suffix, "value", "")


@contextlib.contextmanager
def block_name_suffix_context(block_suffix: str):
    """Context manager to set block name suffix during macro expansion.

    Parameters
    ----------
    block_suffix : str
        The suffix to append to block names (e.g., "_1", "_2").

    Yields
    ------
    None
    """
    old_suffix = getattr(_block_name_suffix, "value", "")
    _block_name_suffix.value = block_suffix
    try:
        yield
    finally:
        _block_name_suffix.value = old_suffix


def buffer(
    shape: list[Expr] | tuple[Expr] | Expr | Integral,
    dtype: str = "float32",
    data: Var = None,
    strides: list[Expr] | None = None,
    elem_offset: Expr = None,
    byte_offset: Expr = None,
    scope: str = "global",
    align: int = 0,
    offset_factor: int = 0,
    buffer_type: str = "",
    axis_separators: list[int] | None = None,
    layout: str | Layout | None = "default",
    allocated_addr: int | tuple[int, ...] | None = None,
    buffer_name: str = "",
) -> Buffer:
    """The buffer declaration function.

    Parameters
    ----------
    shape : Union[List[Expr], Tuple[Expr], Expr, Integral]
        The type of the buffer prior to flattening.

    dtype : str
        The data type in the content of the buffer.

    data : Var
        The pointer to the head of the data.

    strides : List[Expr]
        The strides of each dimension.

    elem_offset : Expr
        The offset in terms of number of dtype elements (including lanes).

    scope : str
        The optional storage scope of buffer data pointer.

    align : int
        The alignment requirement of data pointer in bytes.

    offset_factor : int
        The factor of elem_offset field.

    buffer_type : str
        The buffer type.

    axis_separators : List[int]
        The separators between input axes when generating flattened output axes.

    buffer_name : str
        The name of the buffer.

    Returns
    -------
    res : Buffer
        The declared buffer.
    """
    shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral) else shape
    if strides is not None:
        strides = [Var(s, "int32") if isinstance(s, str) else s for s in strides]
    else:
        strides = []
    if allocated_addr is None:
        allocated_addr = []
    if not isinstance(allocated_addr, list | tuple):
        allocated_addr = [allocated_addr]
    return _ffi_api.Buffer(  # type: ignore[attr-defined] # pylint: disable=no-member
        shape,
        dtype,
        buffer_name,
        data,
        strides,
        _get_elem_offset(elem_offset, byte_offset, dtype),
        scope,
        align,
        offset_factor,
        buffer_type,
        axis_separators,
        _get_layout(layout, shape, scope),
        allocated_addr,
    )


@deprecated("T.buffer_decl(...)", "T.Buffer(...)")
def buffer_decl(*args, **kwargs):
    return buffer(*args, **kwargs)


def prim_func(
    is_private: bool = False,
    s_tir: bool = False,
    persistent: bool = False,
    *,
    private: bool | None = None,
) -> frame.PrimFuncFrame:
    """The primitive function statement.

    Parameters
    ----------
    is_private : bool
        Whether the PrimFunc is annotated as private.
    s_tir : bool
        Whether this PrimFunc uses s_tir (apache-derived TIR) semantics:
        parser fills layout=None on buffers, ScriptComplete wraps body in a
        root SBlock. Default (False) selects tirx semantics: parser fills
        ``DefaultLayout(shape)`` and no root-block wrapping.
    persistent : bool
        Whether this is a persistent kernel.
    private : bool
        Alias for ``is_private`` (used in decorator syntax).

    Returns
    -------
    res : frame.PrimFuncFrame
        The PrimFuncFrame.
    """
    if private is not None:
        is_private = private
    return _ffi_api.PrimFunc(is_private, s_tir, persistent)  # type: ignore[attr-defined] # pylint: disable=no-member


def arg(name: str, obj: Var | Buffer) -> Var | Buffer:
    """The PrimFunc arguments adding function.

    Parameters
    ----------
    name : str
        The name of the argument.

    var : Union[Var, Buffer]
        The argument of Var or Buffer.

    Returns
    -------
    res : Union[Var, Buffer]
        The argument.
    """
    return _ffi_api.Arg(name, obj)  # type: ignore[attr-defined] # pylint: disable=no-member


def func_name(name: str) -> None:
    """The PrimFunc naming statement.

    Parameters
    ----------
    name : str
        The name of the PrimFunc.
    """
    _ffi_api.FuncName(name)  # type: ignore[attr-defined] # pylint: disable=no-member


def func_attr(attrs: dict[str, Any]) -> None:
    """The PrimFunc annotation statement.

    Parameters
    ----------
    attrs : Dict[str, Any]
        The annotations of the PrimFunc.
    """
    _ffi_api.FuncAttrs(attrs)  # type: ignore[attr-defined] # pylint: disable=no-member


def func_ret(ret_type: Type | None) -> Type:
    """The PrimFunc return type statement.

    Parameters
    ----------
    ret_type : Type
        The return type of the PrimFunc.

    Returns
    -------
    res : Type
        The return type.
    """
    if ret_type is None:
        ret_type = Type.missing()
    return _ffi_api.FuncRet(ret_type)  # type: ignore[attr-defined] # pylint: disable=no-member


def match_buffer(
    param: Var | BufferLoad | BufferRegion,
    shape: list[Expr] | tuple[Expr] | Expr | Integral = None,
    dtype: str = "float32",
    data: Var = None,
    strides: list[Expr] | None = None,
    elem_offset: Expr = None,
    scope: str = "global",
    align: int = -1,
    offset_factor: int = 0,
    buffer_type: str = "default",
    axis_separators: list[int] | None = None,
    layout: str | Layout | None = "default",
) -> Buffer:
    """The buffer match function.

    Note
    ----
    This function will perform different behavior, depending on the type of param.
    If the param is a var in function parameter, it will create a buffer from DLTensor.
    Else if the param is a subregion of other buffers, then create a subregion match inside a block.

    Example
    -------
    Match buffer from function parameter
    .. code-block:: python
        A = T.match_buffer(a, (128, 128), dtype="float32")

    Match buffer from Buffer subregion
    .. code-block:: python
        A = T.match_buffer(B[0:128, i * 128 : i * 128 + 128], (128, 128), dtype="float32")

    Parameters
    ----------
    param : Union[Var, BufferLoad, BufferRegion]
        The parameter of the PrimFunc to match.

    shape : Union[List[Expr], Tuple[Expr], Expr, Integral]
        The type of the buffer prior to flattening.

    dtype : str
        The data type in the content of the buffer.

    data : Var
        The pointer to the head of the data.

    strides : List[Expr]
        The strides of each dimension.

    elem_offset : Expr
        The offset in terms of number of dtype elements (including lanes).

    scope : str
        The optional storage scope of buffer data pointer.

    align : int
        The alignment requirement of data pointer in bytes.

    offset_factor : int
        The factor of elem_offset field.

    buffer_type : str
        The buffer type.

    axis_separators : List[int]
        The separators between input axes when generating flattened output axes.

    layout: Optional[Union[str, Layout]]
        The layout of the buffer.

    Returns
    -------
    res : Buffer
        The matched buffer.
    """
    if shape is None:
        if isinstance(param, BufferRegion):
            dtype = param.buffer.dtype
            shape = [region.extent for region in param.region]
        else:
            raise ValueError("Shape must be specified when binding input param")
    shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral) else shape
    if strides is not None:
        idx_dtype = shape[0].ty if is_prim_expr(shape[0]) else "int32"
        strides = [Var(s, idx_dtype) if isinstance(s, str) else s for s in strides]
    else:
        strides = []
    return _ffi_api.MatchBuffer(  # type: ignore[attr-defined] # pylint: disable=no-member
        param,
        shape,
        dtype,
        data,
        strides,
        elem_offset,
        scope,
        align,
        offset_factor,
        buffer_type,
        axis_separators,
        _get_layout(layout, shape, scope),
    )


def sblock(name: str = "", no_realize: bool = False, exec_scope: str = "") -> frame.SBlockFrame:
    """The sblock declaration statement.

    Parameters
    ----------
    name : str
        The name of the sblock.

    no_realize : bool
        The flag whether to construct SBlockRealize or SBlock.

    exec_scope : str
        The execution scope of the block.

    Returns
    -------
    res : frame.SBlockFrame
        The SBlockFrame.
    """
    if isinstance(name, list):
        # tir+
        return _ffi_api.ScopeSlice(name, no_realize)
    block_suffix = _get_sblock_name_suffix()
    if block_suffix and name:
        name = name + block_suffix
    return _ffi_api.Block(name, no_realize, exec_scope)  # type: ignore[attr-defined] # pylint: disable=no-member


def device_entry() -> None:
    """Mark the device-region entry within the enclosing PrimFunc body.

    Flat marker (no ``with``). Subsequent statements in the function body
    accumulate into an ``AttrStmt("tirx.device_entry", True, body=...)``;
    the wrapping is closed by the PrimFunc frame at function end.

    Anything written before this marker is host code (e.g. ``T.match_buffer``);
    anything after is device code.

    Example::

        @T.prim_func
        def kernel(...):
            A = T.match_buffer(...)
            T.device_entry()           # device region starts here
            bx = T.cta_id([SM_COUNT])  # standalone scope-id def
            ...
    """
    attr_frame = _ffi_api.DeviceEntry()  # type: ignore[attr-defined] # pylint: disable=no-member
    attr_frame.__enter__()
    # No return: the frame is registered on the IRBuilder stack; the
    # PrimFunc frame's exit drains it.


def elected():
    """Stub that rejects the removed ``T.elected()`` sugar.

    Write the explicit form instead::

        if T.ptx.elect_sync():
            ...                         # thread is the default scope
    """
    raise RuntimeError(
        "T.elected() is no longer available. Write explicitly: "
        "`if T.ptx.elect_sync(): ...` (thread is the default scope)"
    )


def scope_id(extents: list[Expr | int] | None, parent: str, cur: str) -> Var | list[Var]:
    ret = _ffi_api.ScopeId(extents, parent, "T.scope_id", cur)  # type: ignore[attr-defined] # pylint: disable=no-member
    if len(ret) == 1:
        return ret[0]
    return ret


def cluster_id(extents: list[Expr | int] | None = None) -> Var | list[Var]:
    """Define a kernel→cluster scope id. Pass ``None`` (the default) to defer the
    extent; it will be inferred at LowerTIRx from sibling ScopeIdDef closure."""
    ret = _ffi_api.ClusterId(extents, "kernel")  # type: ignore[attr-defined] # pylint: disable=no-member
    if len(ret) == 1:
        return ret[0]
    return ret


def cta_id(extents: list[Expr | int] | None = None, preferred=None) -> Var | list[Var]:
    """Define a kernel→cta scope id. Pass ``None`` (the default) to defer the
    extent; it will be inferred at LowerTIRx from sibling ScopeIdDef closure."""
    ret = _ffi_api.CtaId(extents, "kernel", preferred)  # type: ignore[attr-defined] # pylint: disable=no-member
    if len(ret) == 1:
        return ret[0]
    return ret


def cta_id_in_cluster(extents: list[Expr | int] | None = None, preferred=None) -> Var | list[Var]:
    """Define a cluster→cta scope id. Pass ``None`` (the default) to defer the
    extent; it will be inferred at LowerTIRx from sibling ScopeIdDef closure."""
    ret = _ffi_api.CtaId(extents, "cluster", preferred)  # type: ignore[attr-defined] # pylint: disable=no-member
    if len(ret) == 1:
        return ret[0]
    return ret


def cta_id_in_pair() -> Var:
    ret = _ffi_api.CtaIdInPair()  # type: ignore[attr-defined] # pylint: disable=no-member
    return ret[0]


def warpgroup_id(extents: list[Expr | int] | None = None) -> Var | list[Var]:
    """Define a cta→warpgroup scope id. Pass ``None`` (the default) to defer
    the extent; it will be inferred at LowerTIRx from sibling closure."""
    ret = _ffi_api.WarpgroupId(extents, "cta")  # type: ignore[attr-defined] # pylint: disable=no-member
    if len(ret) == 1:
        return ret[0]
    return ret


def warp_id(extents: list[Expr | int] | None = None) -> Var | list[Var]:
    """Define a cta→warp scope id. Pass ``None`` (the default) to defer the
    extent; it will be inferred at LowerTIRx from sibling closure."""
    ret = _ffi_api.WarpId(extents, "cta")  # type: ignore[attr-defined] # pylint: disable=no-member
    if len(ret) == 1:
        return ret[0]
    return ret


def warp_id_in_wg(extents: list[Expr | int] | None = None) -> Var | list[Var]:
    """Define a warpgroup→warp scope id. Pass ``None`` (the default) to defer
    the extent; it will be inferred at LowerTIRx from sibling closure."""
    ret = _ffi_api.WarpId(extents, "warpgroup")  # type: ignore[attr-defined] # pylint: disable=no-member
    if len(ret) == 1:
        return ret[0]
    return ret


def lane_id(extents: list[Expr | int] | None = None) -> Var | list[Var]:
    """Define a warp→thread scope id. Pass ``None`` (the default) to defer the
    extent; it will be inferred at LowerTIRx from sibling closure."""
    ret = _ffi_api.ThreadId(extents, "warp")  # type: ignore[attr-defined] # pylint: disable=no-member
    if len(ret) == 1:
        return ret[0]
    return ret


def thread_id(extents: list[Expr | int] | None = None) -> Var | list[Var]:
    """Define a cta→thread scope id. Pass ``None`` (the default) to defer the
    extent; it will be inferred at LowerTIRx from sibling closure."""
    ret = _ffi_api.ThreadId(extents, "cta")  # type: ignore[attr-defined] # pylint: disable=no-member
    if len(ret) == 1:
        return ret[0]
    return ret


def thread_id_in_wg(extents: list[Expr | int] | None = None) -> Var | list[Var]:
    """Define a warpgroup→thread scope id. Pass ``None`` (the default) to defer
    the extent; it will be inferred at LowerTIRx from sibling closure."""
    ret = _ffi_api.ThreadId(extents, "warpgroup")  # type: ignore[attr-defined] # pylint: disable=no-member
    if len(ret) == 1:
        return ret[0]
    return ret


def init() -> frame.BlockInitFrame:
    """The block initialization statement.

    Returns
    -------
    res : frame.BlockInitFrame
        The BlockInitFrame.
    """
    return _ffi_api.Init()  # type: ignore[attr-defined] # pylint: disable=no-member


def where(predicate: Expr | int) -> None:
    """The block predicate statement.

    Parameters
    ----------
    predicate : Union[Expr, Literal[0, 1]]
        The predicate condition.
    """
    if isinstance(predicate, bool):
        predicate = IntImm("bool", predicate)
    if isinstance(predicate, int):
        if predicate in [0, 1]:
            predicate = IntImm("bool", predicate)
        else:
            raise ValueError(f"Invalid value for predicate: {predicate}")
    _ffi_api.Where(predicate)  # type: ignore[attr-defined] # pylint: disable=no-member


def reads(*buffer_slices: list[BufferRegion | BufferLoad]) -> None:
    """The block buffer region reading statement.

    Parameters
    ----------
    buffer_slices : List[Union[BufferRegion, BufferLoad]]
        The array of buffer regions to read.
    """
    if len(buffer_slices) == 1:
        if isinstance(buffer_slices[0], tuple):
            buffer_slices = list(buffer_slices[0])
        elif isinstance(buffer_slices[0], list):
            buffer_slices = buffer_slices[0]  # type: ignore[assignment]
        else:
            buffer_slices = [buffer_slices[0]]
    else:
        buffer_slices = list(buffer_slices)  # type: ignore[assignment]
    _ffi_api.Reads(buffer_slices)  # type: ignore[attr-defined] # pylint: disable=no-member


def writes(*buffer_slices: list[BufferRegion | BufferLoad]) -> None:
    """The block buffer region writing statement.

    Parameters
    ----------
    buffer_slices : List[Union[BufferRegion, BufferLoad]]
        The array of buffer regions to write.
    """
    if len(buffer_slices) == 1:
        if isinstance(buffer_slices[0], tuple):
            buffer_slices = list(buffer_slices[0])
        elif isinstance(buffer_slices[0], list):
            buffer_slices = buffer_slices[0]  # type: ignore[assignment]
        else:
            buffer_slices = [buffer_slices[0]]
    else:
        buffer_slices = list(buffer_slices)  # type: ignore[assignment]
    _ffi_api.Writes(buffer_slices)  # type: ignore[attr-defined] # pylint: disable=no-member


def sblock_attr(attrs: dict[str, Any]) -> None:
    """The block annotation statement (for non-tirx SBlock usage).

    Parameters
    ----------
    attrs : Dict[str, Any]
        The annotation of the block.
    """
    return _ffi_api.BlockAttrs(attrs)  # type: ignore[attr-defined] # pylint: disable=no-member


def alloc_buffer(
    shape: list[Expr] | tuple[Expr] | Expr | Integral,
    dtype: str = "float32",
    data: Var | None = None,
    strides: list[Expr] | None = None,
    elem_offset: Expr | None = None,
    byte_offset: Expr | None = None,
    scope: str = "global",
    align: int = -1,
    offset_factor: int = 0,
    buffer_type: str = "default",
    axis_separators: list[int] | None = None,
    layout: str | Layout | None = "default",
    allocated_addr: int | tuple[int, ...] | None = None,
    annotations: dict[str, Any] | None = None,
) -> Buffer:
    """Statement-level buffer allocation (creates an AllocBuffer IR node).

    Emits an AllocBuffer statement and returns the Buffer directly::

        buf = T.alloc_buffer((128, 128))

    For SBlock-level buffer allocation (added to SBlock.alloc_buffers),
    use T.sblock_alloc_buffer() instead.

    Parameters
    ----------
    shape : Union[List[Expr], Tuple[Expr], Expr, Integral]
        The shape of the buffer to allocate.
    dtype : str
        The data type of the buffer elements.
    scope : str
        The storage scope of the buffer (e.g., "global", "shared").
    data : Optional[Var]
        Optional explicit data pointer.
    strides : Optional[List[Expr]]
        Optional strides.
    elem_offset : Optional[Expr]
        Optional element offset.
    byte_offset : Optional[Expr]
        Optional byte offset.
    align : int
        Alignment requirement in bytes.
    offset_factor : int
        Offset factor.
    buffer_type : str
        Buffer type.
    axis_separators : Optional[List[int]]
        Optional axis separators.
    layout : Optional[Union[str, Layout]]
        Optional layout.
    allocated_addr : Optional[Union[int, Tuple[int, ...]]]
        Optional pre-allocated address metadata.
    annotations : Optional[Dict[str, Any]]
        Optional annotations for the allocation.

    Returns
    -------
    res : Buffer
        The allocated buffer.
    """
    shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral) else shape
    buf = buffer(
        shape=shape,
        dtype=dtype,
        data=data,
        strides=strides,
        elem_offset=elem_offset,
        byte_offset=byte_offset,
        scope=scope,
        align=align,
        offset_factor=offset_factor,
        buffer_type=buffer_type,
        axis_separators=axis_separators,
        layout=layout,
        allocated_addr=allocated_addr,
        buffer_name="",
    )
    _record_meta_resource(buf, skip_frames=2)

    # AllocBuffer.annotations holds typed IR values. The C++ side stores
    # alignment / shape-like ints as ``IntImm(int32, ...)``; if the user
    # (or a parsed-source round-trip) passes a bare Python int, normalize
    # it so structural equality is preserved against the LowerOpaqueBlock
    # output. Booleans must stay as IntImm("bool", ...).
    def _normalize_ann_value(v):
        if isinstance(v, bool):
            return tir.IntImm("bool", int(v))
        if isinstance(v, int):
            return tir.IntImm("int32", v)
        if isinstance(v, float):
            return tir.FloatImm("float32", v)
        return v

    norm_annotations = {k: _normalize_ann_value(v) for k, v in (annotations or {}).items()}
    _ffi_api.AddToParent(tir.AllocBuffer(buf, norm_annotations))  # type: ignore[attr-defined] # pylint: disable=no-member
    return buf


def wg_reg_tile(elem_per_thread: int, dtype: str = "float32") -> Buffer:
    """Warpgroup-wide ``(128, elem_per_thread)`` register tile in local scope.

    Sugar for the recurring pattern::

        T.alloc_buffer(
            (128, elem_per_thread), dtype,
            layout=wg_local_layout(elem_per_thread),
            scope="local",
        )

    Used to stage a tcgen05 load: each of the 128 threads in a warpgroup
    owns one row of ``elem_per_thread`` contiguous elements.
    """
    return alloc_buffer(
        (128, elem_per_thread),
        dtype,
        layout=wg_local_layout(elem_per_thread),
        scope="local",
    )


def sblock_alloc_buffer(
    shape: list[Expr] | tuple[Expr] | Expr | Integral,
    dtype: str = "float32",
    data: Var = None,
    strides: list[Expr] | None = None,
    elem_offset: Expr = None,
    scope: str = "global",
    align: int = -1,
    offset_factor: int = 0,
    buffer_type: str = "default",
    axis_separators: list[int] | None = None,
    layout: str | Layout | None = "default",
    allocated_addr: int | tuple[int, ...] | None = None,
) -> Buffer:
    """SBlock-level buffer allocation function.

    Parameters
    ----------
    shape : Union[List[Expr], Tuple[Expr], Expr, Integral]
        The type of the buffer prior to flattening.
    dtype : str
        The data type in the content of the buffer.
    data : Var
        The pointer to the head of the data.
    strides : List[Expr]
        The strides of each dimension.
    elem_offset : Expr
        The offset in terms of number of dtype elements (including lanes).
    scope : str
        The optional storage scope of buffer data pointer.
    align : int
        The alignment requirement of data pointer in bytes.
    offset_factor : int
        The factor of elem_offset field.
    buffer_type : str
        The buffer type.
    axis_separators : List[int]
        The separators between input axes when generating flattened output axes.

    layout: Optional[Union[str, Layout]]
        The layout of the buffer.

    allocated_addr: Optional[Union[int, Tuple[int]]]
        The address of the allocated buffer. Might be multi-dimensional.
        There can be pooled storage scopes on some devices. For example,
        the Trainium device has a pooled storage scope for the SRAN buffers. ("trn.sbuf")
        CUDA has a pooled storage scope for the shared memory ("shared.dyn")

    Returns
    -------
    res : Buffer
        The allocated buffer.
    """
    shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral) else shape
    if strides is not None:
        strides = [Var(s, "int32") if isinstance(s, str) else s for s in strides]
    else:
        strides = []
    if axis_separators is None:
        axis_separators = []
    if allocated_addr is None:
        allocated_addr = []
    if not isinstance(allocated_addr, list | tuple):
        allocated_addr = [allocated_addr]
    alloc_frame = _ffi_api.SBlockAllocBuffer(  # type: ignore[attr-defined] # pylint: disable=no-member
        shape,
        dtype,
        data,
        strides,
        elem_offset,
        scope,
        align,
        offset_factor,
        buffer_type,
        axis_separators,
        _get_layout(layout, shape, scope),
        allocated_addr,
    )
    if isinstance(alloc_frame, frame.AllocBufferFrame):
        alloc_frame.add_callback(partial(alloc_frame.__exit__, None, None, None))
        buf = alloc_frame.__enter__()
    else:
        buf = alloc_frame
    _record_meta_resource(buf, skip_frames=2)
    return buf


def _as_range(dom: ir.Range | list[Expr]) -> ir.Range:
    """The range constructor.

    Parameters
    ----------
    dom : Union[Range, List[Expr]]
        The domain.

    Returns
    -------
    res : Range
        The Range.
    """
    if isinstance(dom, ir.Range):
        return dom
    if isinstance(dom, list | tuple):
        from tvm.arith import Analyzer  # pylint: disable=import-outside-toplevel

        extent = Analyzer().simplify(dom[1] - dom[0])
        if isinstance(extent, tir.IntImm):
            return ir.Range.from_min_extent(dom[0], extent)
        return ir.Range(dom[0], dom[1])
    if is_prim_expr(dom):
        return ir.Range(IntImm(dom.ty, 0), dom)
    return ir.Range(0, dom)


class axis:  # pylint: disable=invalid-name
    """The axis class"""

    @staticmethod
    def spatial(
        dom: ir.Range | list[Expr] | tuple[Expr],
        binding: Expr,
        dtype: str = "int32",
    ) -> Var:
        """The spatial block axis defining function.

        Parameters
        ----------
        dom : Union[Range, List[Expr], Tuple[Expr]]
            The domain of the iteration variable.

        binding : Expr
            The binding value of the iteration variable.

        dtype : str
            The data type of the iteration variable.

        Returns
        -------
        res : Var
            The iteration variable.
        """
        return _ffi_api.AxisSpatial(  # type: ignore[attr-defined] # pylint: disable=no-member
            _as_range(dom), binding, dtype
        )

    @staticmethod
    def reduce(
        dom: ir.Range | list[Expr] | tuple[Expr],
        binding: Expr,
        dtype: str = "int32",
    ) -> Var:
        """The reduced block axis defining function.

        Parameters
        ----------
        dom : Union[Range, List[Expr], Tuple[Expr]]
            The domain of the iteration variable.

        binding : Expr
            The binding value of the iteration variable.

        dtype : str
            The data type of the iteration variable.

        Returns
        -------
        res : Var
            The iteration variable.
        """
        return _ffi_api.AxisReduce(  # type: ignore[attr-defined] # pylint: disable=no-member
            _as_range(dom), binding, dtype
        )

    @staticmethod
    def scan(
        dom: ir.Range | list[Expr] | tuple[Expr],
        binding: Expr,
        dtype: str = "int32",
    ) -> Var:
        """The scanning block axis defining function.

        Parameters
        ----------
        dom : Union[Range, List[Expr], Tuple[Expr]]
            The domain of the iteration variable.

        binding : Expr
            The binding value of the iteration variable.

        dtype : str
            The data type of the iteration variable.

        Returns
        -------
        res : Var
            The iteration variable.
        """
        return _ffi_api.AxisScan(  # type: ignore[attr-defined] # pylint: disable=no-member
            _as_range(dom), binding, dtype
        )

    @staticmethod
    def opaque(
        dom: ir.Range | list[Expr] | tuple[Expr],
        binding: Expr,
        dtype: str = "int32",
    ) -> Var:
        """The opaque block axis defining function.

        Parameters
        ----------
        dom : Union[Range, List[Expr], Tuple[Expr]]
            The domain of the iteration variable.

        binding : Expr
            The binding value of the iteration variable.

        dtype : str
            The data type of the iteration variable.

        Returns
        -------
        res : Var
            The iteration variable.
        """
        return _ffi_api.AxisOpaque(  # type: ignore[attr-defined] # pylint: disable=no-member
            _as_range(dom), binding, dtype
        )

    @staticmethod
    def remap(kinds: str, bindings: list[Expr], dtype: str = "int32") -> list[Var] | Var:
        """The block axis remapping function.

        Parameters
        ----------
        kinds : str
            The types of the iteration variables.

        bindings : List[Expr]
            The binding values of the iteration variables.

        dtype : str
            The data types of the iteration variables.

        Returns
        -------
        res : Var
            The iteration variables.
        """
        iter_vars = _ffi_api.AxisRemap(  # type: ignore[attr-defined] # pylint: disable=no-member
            kinds, bindings, dtype
        )
        return iter_vars[0] if len(iter_vars) == 1 else iter_vars

    S = spatial  # pylint: disable=invalid-name
    R = reduce  # pylint: disable=invalid-name


def serial(
    start: Expr,
    stop: Expr = None,
    *,
    annotations: dict[str, Any] | None = None,
    step: Expr | None = None,
    unroll: bool | None = None,
) -> frame.ForFrame:
    """The serial For statement.

    Parameters
    ----------
    start : Expr
        The minimum value of iteration.

    stop : Expr
        The maximum value of iteration.

    annotations : Dict[str, Any]
        The optional annotations of the For statement.

    step : Expr
        The optional step value of iteration.

    unroll : bool, optional
        If True, adds ``{"pragma_unroll": True}`` annotation, which asks CUDA codegen
        to emit ``#pragma unroll`` while preserving the loop as a C++ ``for``.
        If False, adds ``{"disable_unroll": True}`` annotation.
        Shorthand for ``annotations={"disable_unroll": True}``.

    Returns
    -------
    res : frame.ForFrame
        The ForFrame.
    """
    if unroll is not None:
        annotations = dict(annotations) if annotations else {}
        if unroll:
            annotations["pragma_unroll"] = True
        else:
            annotations["disable_unroll"] = True
    if stop is None:
        stop = start
        if is_prim_expr(start):
            start = IntImm(start.ty, 0)
        else:
            start = 0
    return _ffi_api.Serial(start, stop, annotations, step)  # type: ignore[attr-defined] # pylint: disable=no-member


def parallel(
    start: Expr,
    stop: Expr = None,
    *,
    annotations: dict[str, Any] | None = None,
    step: Expr | None = None,
) -> frame.ForFrame:
    """The parallel For statement.

    Parameters
    ----------
    start : Expr
        The minimum value of iteration.

    stop : Expr
        The maximum value of iteration.

    annotations : Dict[str, Any]
        The optional annotations of the For statement.

    step : Expr
        The optional step value of iteration.

    Returns
    -------
    res : frame.ForFrame
        The ForFrame.
    """
    if stop is None:
        stop = start
        if is_prim_expr(start):
            start = IntImm(start.ty, 0)
        else:
            start = 0
    return _ffi_api.Parallel(start, stop, annotations, step)  # type: ignore[attr-defined] # pylint: disable=no-member


def vectorized(
    start: Expr,
    stop: Expr = None,
    *,
    annotations: dict[str, Any] | None = None,
    step: Expr | None = None,
) -> frame.ForFrame:
    """The vectorized For statement.

    Parameters
    ----------
    start : Expr
        The minimum value of iteration.

    stop : Expr
        The maximum value of iteration.

    annotations : Dict[str, Any]
        The optional annotations of the For statement.

    step : Expr
        The optional step value of iteration.

    Returns
    -------
    res : frame.ForFrame
        The ForFrame.
    """
    if stop is None:
        stop = start
        if is_prim_expr(start):
            start = IntImm(start.ty, 0)
        else:
            start = 0
    return _ffi_api.Vectorized(start, stop, annotations, step)  # type: ignore[attr-defined] # pylint: disable=no-member


def unroll(
    start: Expr,
    stop: Expr = None,
    *,
    annotations: dict[str, Any] | None = None,
    step: Expr | None = None,
) -> frame.ForFrame:
    """The unrolled For statement.

    Parameters
    ----------
    start : Expr
        The minimum value of iteration.

    stop : Expr
        The maximum value of iteration.

    annotations : Dict[str, Any]
        The optional annotations of the For statement.

    step : Expr
        The optional step value of iteration.

    Returns
    -------
    res : frame.ForFrame
        The ForFrame.
    """
    if stop is None:
        stop = start
        if is_prim_expr(start):
            start = IntImm(start.ty, 0)
        else:
            start = 0
    return _ffi_api.Unroll(start, stop, annotations, step)  # type: ignore[attr-defined] # pylint: disable=no-member


def thread_binding(
    start: Expr,
    stop: Expr = None,
    thread: str | None = None,
    *,
    annotations: dict[str, Any] | None = None,
) -> frame.ForFrame:
    """The thread-binding For statement.

    Parameters
    ----------
    start : Expr
        The minimum value of iteration.

    stop : Expr
        The maximum value of iteration.

    thread : str
        The thread for loop variable to bind.

    annotations : Dict[str, Any]
        The optional annotations of the For statement.

    Returns
    -------
    res : frame.ForFrame
        The ForFrame.
    """
    if thread is None:
        if not isinstance(stop, str):
            raise ValueError("Thread cannot be None for thread_binding")
        thread = stop
        stop = start
        if is_prim_expr(start):
            start = IntImm(start.ty, 0)
        else:
            start = 0
    elif stop is None:
        stop = start
        if is_prim_expr(start):
            start = IntImm(start.ty, 0)
        else:
            start = 0
    return _ffi_api.ThreadBinding(  # type: ignore[attr-defined] # pylint: disable=no-member
        start, stop, thread, annotations
    )


def grid(*extents: tuple[Expr | tuple[Expr, Expr]]) -> frame.ForFrame:
    """The grid For statement.

    Parameters
    ----------
    extents : Tuple[Union[Expr, Tuple[Expr, Expr]]]
        If a single Expr is provided, it is used as the extent of the iteration.
        If a tuple of two Expr is provided, the first is the start of the iteration,
        and the second is the extent of the iteration.

    Returns
    -------
    res : frame.ForFrame
        The ForFrame.
    """
    # Convert integer extents to IntImm
    # TODO(@bohan): fix this after FFI refactor
    processed_extents = []
    for extent in extents:
        if isinstance(extent, tuple):
            start, extent = extent
            start = IntImm("int32", start) if isinstance(start, int) else start
            extent = IntImm("int32", extent) if isinstance(extent, int) else extent
            processed_extents.append((start, extent))
        else:
            processed_extents.append(IntImm("int32", extent) if isinstance(extent, int) else extent)
    extents = tuple(processed_extents)
    return _ffi_api.Grid(extents)  # type: ignore[attr-defined] # pylint: disable=no-member


def Assert(condition: Expr, message, error_kind: str = "RuntimeError") -> frame.AssertFrame:  # pylint: disable=invalid-name
    """Create an assertion statement.

    Parameters
    ----------
    condition : Expr
        The Expr to test.

    message : str or list[str]
        The error message when the assertion fails. Can be a single string
        or a list of string parts (fragments stored separately in the IR
        for binary size reduction through string reuse).

    error_kind : str
        The error kind (e.g. "RuntimeError", "TypeError", "ValueError").

    Returns
    -------
    res : frame.AssertFrame
        The result AssertFrame.
    """
    if isinstance(condition, bool):
        condition = IntImm("bool", condition)
    if not isinstance(message, list | tuple):
        message = [message]
    return _ffi_api.Assert(condition, error_kind, message)  # type: ignore[attr-defined] # pylint: disable=no-member


def Bind(  # pylint: disable=invalid-name
    value: Expr,
    type_annotation: Type | None = None,  # pylint: disable=redefined-outer-name
    *,
    var: Var | None = None,  # pylint: disable=redefined-outer-name
) -> Var:
    """Create a Bind (variable binding).

    Emits a flat Bind statement to the current frame and returns the bound variable.

    Parameters
    ----------
    value : Expr
        The value to be bound.
    type_annotation : Optional[Type] = None
        The type annotation of the binding. Usually it is used for fine-grained var typing,
        particularly, PointerType.
    var : Optional[Var] = None
        The variable to bind. If not specified, a new variable will be created.

    Returns
    -------
    var : Var
        The bound variable.
    """
    if type_annotation is not None:
        if callable(type_annotation):
            type_annotation = type_annotation()
        if isinstance(type_annotation, Var):
            type_annotation = type_annotation.ty
    return _ffi_api.Bind(value, type_annotation, var)  # type: ignore[attr-defined] # pylint: disable=no-member


def Let(  # pylint: disable=invalid-name
    expr: Expr,
    where: dict[Var, Expr],  # pylint: disable=redefined-outer-name
) -> Expr:
    """Create a Let expression binding"""
    assert len(where) == 1, "T.Let only allows `where` to have exactly one element"
    var, value = next(iter(where.items()))  # pylint: disable=redefined-outer-name
    return tir.Let(var, value, expr)


bind = Bind


class LetAnnotation:
    """Marker for explicit LetStmt. Created by T.let or T.let[type].
    Usage in TVMScript:
        x: T.let[T.int32] = expr   # LetStmt with explicit type
        x: T.let = expr             # LetStmt with auto-typed RHS
    """

    def __init__(self, type_spec=None):
        self.type_spec = type_spec

    def __class_getitem__(cls, item):
        return LetAnnotation(item)

    def __getitem__(self, item):
        return LetAnnotation(item)

    def as_var(self, rhs_dtype=None):
        """Resolve to a tir.Var."""
        if self.type_spec is not None:
            if isinstance(self.type_spec, Var):
                return self.type_spec  # Already a Var (e.g. T.handle(...))
            elif callable(self.type_spec):
                return self.type_spec()  # e.g. T.int32() -> Var
            elif isinstance(self.type_spec, Type):
                return Var("", self.type_spec)
            else:
                raise TypeError(f"Invalid type for T.let: {self.type_spec}")
        elif rhs_dtype is not None:
            rhs_ty = rhs_dtype if isinstance(rhs_dtype, Type) else ir.PrimType(rhs_dtype)
            return Var("", rhs_ty)
        else:
            raise TypeError("T.let requires either a type or an RHS value")


let = LetAnnotation()  # Singleton for T.let (no subscript)


class LocalVectorAnnotation:
    """Marker for local vector/tensor allocation via type annotation subscript.

    Created when a DtypeConstructor is subscripted, e.g. ``T.float32[N]`` or
    ``T.float32[M, N]``.  The parser's ``visit_ann_assign`` recognises this
    object and lowers it to ``T.alloc_local(shape=..., dtype=...)``.
    """

    __slots__ = ("dtype", "shape")

    def __init__(self, dtype: str, shape: tuple):
        self.dtype = dtype
        self.shape = shape


class DtypeConstructor:
    """Callable + subscriptable dtype object.

    Replaces the plain functions previously returned by ``func_gen``.

    * ``T.float32()``        — same FFI call as before (returns ``Var``).
    * ``T.float32[N]``       — returns ``LocalVectorAnnotation("float32", (N,))``.
    * ``T.float32[M, N]``    — returns ``LocalVectorAnnotation("float32", (M, N))``.
    * ``x: T.float32``       — parser calls this object, gets a ``Var``.
    """

    def __init__(self, ffi_name: str, dtype_str: str):
        self._ffi_name = ffi_name
        self._dtype_str = dtype_str

    def __call__(
        self,
        expr: "None | Expr | Literal['inf', '-inf', 'nan'] | int | float" = None,
    ) -> "Expr":
        if isinstance(expr, str):
            expr = float(expr)
        return getattr(_ffi_api, self._ffi_name)(expr)

    def __getitem__(self, shape):
        if isinstance(shape, tuple):
            return LocalVectorAnnotation(self._dtype_str, shape)
        return LocalVectorAnnotation(self._dtype_str, (shape,))

    def __repr__(self):
        return f"DtypeConstructor({self._dtype_str!r})"


def allocate(
    extents: list[Expr],
    dtype: str,
    scope: str = "global",
    condition: Expr = None,
    annotations=None,
) -> frame.AllocateFrame:
    """Allocate node.

    Parameters
    ----------
    extents : List[Expr]
        The extents of the allocate.

    dtype : str
        The data type of the buffer.

    scope : str
        The storage scope.

    condition : Expr
        The condition.

    annotations: Optional[Mapping[str, Object]]
        Additional annotation hints.
    """
    if isinstance(condition, bool):
        condition = IntImm("bool", condition)
    return _ffi_api.Allocate(  # type: ignore[attr-defined] # pylint: disable=no-member
        extents, dtype, scope, condition, annotations
    )


def attr(
    node_or_dict: Any, attr_key: str | None = None, value: Expr | str | None = None
) -> Union[frame.AttrFrame, "utils._FrameScope"]:
    """Create an attribute node, or multiple attribute nodes from a dict.

    Usage 1 — single attr::

        with T.attr(node, key, value):
            ...

    Usage 2 — dict sugar (node defaults to ``T.int32(0)``)::

        with T.attr({"key1": value1, "key2": value2}):
            ...

    Parameters
    ----------
    node_or_dict : Any
        If a dict, each key-value pair becomes an AttrStmt with
        ``node=T.int32(0)``.  Otherwise the node to annotate.

    attr_key : str, optional
        Attribute type key (required when ``node_or_dict`` is not a dict).

    value : Union[Expr, str], optional
        The attribute value (required when ``node_or_dict`` is not a dict).

    Returns
    -------
    res : Union[frame.AttrFrame, _FrameScope]
        A single AttrFrame, or a _FrameScope wrapping multiple AttrFrames.
    """
    if isinstance(node_or_dict, dict):
        frames = []
        for k, v in node_or_dict.items():
            if isinstance(v, bool):
                v = IntImm("bool", v)
            frames.append(
                _ffi_api.Attr(  # type: ignore[attr-defined]
                    convert(IntImm("int32", 0)), k, convert(v)
                )
            )
        if len(frames) == 1:
            return frames[0]
        return utils._FrameScope(frames)
    else:
        if attr_key is None or value is None:
            raise ValueError("T.attr(node, attr_key, value) requires all three arguments")
        node_or_dict = convert(node_or_dict)
        value = convert(value)
        return _ffi_api.Attr(node_or_dict, attr_key, value)  # type: ignore[attr-defined] # pylint: disable=no-member


def hint(message: str = "", **attrs) -> frame.HintFrame:
    """Universal directive primitive for the sketch language.

    Parameters
    ----------
    message : str
        Free-form directive string that the agent interprets.
    **attrs
        Optional structured key-value attributes for known patterns.

    Returns
    -------
    res : frame.HintFrame
        Usable as context manager (with T.hint("msg"):) or bare statement (T.hint("msg")).
    """
    return _ffi_api.Hint(message, attrs or {})  # type: ignore[attr-defined] # pylint: disable=no-member


def While(condition: Expr) -> frame.WhileFrame:  # pylint: disable=invalid-name
    """Create a while node.

    Parameters
    ----------
    condition : Expr
        The termination condition of the loop.

    Returns
    -------
    res : frame.WhileFrame
        The result WhileFrame.
    """
    if isinstance(condition, bool):
        condition = IntImm("bool", condition)
    return _ffi_api.While(condition)  # type: ignore[attr-defined] # pylint: disable=no-member


def Break() -> None:  # pylint: disable=invalid-name
    """Create a break node."""
    return _ffi_api.Break()  # type: ignore[attr-defined] # pylint: disable=no-member


def Continue() -> None:  # pylint: disable=invalid-name
    """Create a continue node."""
    return _ffi_api.Continue()  # type: ignore[attr-defined] # pylint: disable=no-member


def If(condition: Expr) -> frame.IfFrame:  # pylint: disable=invalid-name
    """Create an if node.

    Parameters
    ----------
    condition : Expr
        The condition of if statement, executes the true branch if the condition is true,
        otherwise jump into the false branch.

    Returns
    -------
    res : frame.IfFrame
        The result IfFrame.
    """
    if isinstance(condition, bool):
        condition = IntImm("bool", condition)
    return _ffi_api.If(condition)  # type: ignore[attr-defined] # pylint: disable=no-member


def Then() -> frame.ThenFrame:  # pylint: disable=invalid-name
    """Create a then.

    Returns
    -------
    res : frame.ThenFrame
        The result ThenFrame.
    """
    return _ffi_api.Then()  # type: ignore[attr-defined] # pylint: disable=no-member


def Else() -> frame.ElseFrame:  # pylint: disable=invalid-name
    """Create an else.

    Returns
    -------
    res : frame.ElseFrame
        The result ElseFrame.
    """
    return _ffi_api.Else()  # type: ignore[attr-defined] # pylint: disable=no-member


def decl_buffer(
    shape,
    dtype="float32",
    data=None,
    strides=None,
    elem_offset=None,
    byte_offset=None,
    scope="global",
    align=0,
    offset_factor=0,
    buffer_type="",
    axis_separators=None,
    layout="default",
    allocated_addr=None,
) -> Buffer:
    """Create a buffer declaration node.

    When ``data`` is provided, creates a DeclBuffer (alias to existing data).
    When ``data`` is None, creates an AllocBuffer (new allocation).

    Parameters
    ----------
    shape : Union[List[Expr], Tuple[Expr], Expr, Integral]
        The type of the buffer prior to flattening.

    dtype : str
        The data type in the content of the buffer.

    data : Var
        The pointer to the head of the data.

    strides : List[Expr]
        The strides of each dimension.

    elem_offset : Expr
        The offset in terms of number of dtype elements (including lanes).

    byte_offset : Expr
        The offset in terms of number of bytes.

    scope : str
        The optional storage scope of buffer data pointer.

    align : int
        The alignment requirement of data pointer in bytes.

    offset_factor : int
        The factor of elem_offset field.

    buffer_type : str
        The buffer type.

    axis_separators : List[int]
        The separators between input axes when generating flattened output axes.

    layout : Layout
        The layout of the buffer.

    Returns
    -------
    res : Buffer
        The declared buffer.
    """
    shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral) else shape
    if strides is not None:
        strides = [Var(s, "int32") if isinstance(s, str) else s for s in strides]
    else:
        strides = []
    dtype = _normalize_prim_type(dtype)
    decl_frame = _ffi_api.DeclBuffer(  # type: ignore[attr-defined] # pylint: disable=no-member
        shape,
        dtype,
        "",
        data,
        strides,
        _get_elem_offset(elem_offset, byte_offset, dtype),
        scope,
        align,
        offset_factor,
        buffer_type,
        axis_separators,
        _get_layout(layout, shape, scope),
        allocated_addr,
    )
    if isinstance(decl_frame, frame.DeclBufferFrame):
        decl_frame.add_callback(partial(decl_frame.__exit__, None, None, None))
        buf = decl_frame.__enter__()
    else:
        buf = decl_frame
    _record_meta_resource(buf, skip_frames=2)
    return buf


alloc_shared = functools.partial(alloc_buffer, scope="shared")
alloc_local = functools.partial(alloc_buffer, scope="local")
smem = alloc_shared
tmem = functools.partial(alloc_buffer, scope="tmem")


def alloc_tcgen05_ldst_frag(instr_shape, tensor_shape, dtype):
    """Allocate a register fragment for ``tcgen05.{ld,st}`` atoms.

    Sizes the per-thread storage, allocates ``local`` scope memory, and returns
    a 2-D view of shape ``tensor_shape`` with a matching ``tcgen05_atom_layout``.
    Pass the result to ``Tx.copy_async`` (with a ``(128, W)``-shaped TMEM
    buffer) to trigger the corresponding dispatch path.

    Parameters
    ----------
    instr_shape : str
        ``"32x32b"`` (M=128 fragment, 128 row warpgroup tile, layout
        ``(128, K):(1@tid_in_wg, 1)``); or ``"16x64b"`` / ``"16x128b"`` /
        ``"16x256b"`` (M=64 fragments, 64 row warpgroup tile with the
        per-shape per-lane register decomposition).
    tensor_shape : tuple[int, int]
        Logical fragment shape ``(frag_rows, K)`` in element units. ``frag_rows``
        is ``128`` for ``.32x32b`` and ``64`` for the ``.16x*b`` shapes.
    dtype : str
        ``"float32"``, ``"float16"``, or ``"bfloat16"``.

    Returns
    -------
    Buffer
        2-D view of shape ``tensor_shape`` whose layout matches
        ``tcgen05_atom_layout(instr_shape, tensor_shape, dtype)``.

    Examples
    --------
    M=128 readback (existing dispatch):
        ``frag = T.alloc_tcgen05_ldst_frag("32x32b", (128, 64), "float32")``
        ``Tx.copy_async(frag[:, :], tmem[:, 0:64])``

    M=64 readback (.16x64b dispatch):
        ``frag = T.alloc_tcgen05_ldst_frag("16x64b", (64, 64), "float32")``
        ``Tx.copy_async(frag[:, :], tmem[0:64, 0:64])``
    """
    from tvm.tirx.layout import tcgen05_atom_layout  # local import to avoid cycle

    rows, cols = tensor_shape
    bits = DataType(dtype).bits
    # Per-warpgroup total bits = 64 rows x K cols x bits. Divided across 128
    # threads gives per-thread bits; convert to element count.
    per_thread_bits = (rows * cols * bits) // 128
    if per_thread_bits % bits != 0:
        raise ValueError(
            f"alloc_tcgen05_ldst_frag tensor_shape={tensor_shape} dtype={dtype!r} "
            f"does not evenly divide across 128 threads"
        )
    per_thread_elems = per_thread_bits // bits

    layout = tcgen05_atom_layout(instr_shape, tensor_shape, dtype)
    flat = alloc_local((per_thread_elems,), dtype)
    return flat.view(rows, cols, layout=layout)


def alloc_cast_frag(src, dtype):
    """Allocate a register frag holding ``src`` value-cast to ``dtype``.

    Inherits ``src``'s logical shape and its ``(lane, register)`` layout — only
    the element dtype changes — so ``Tx.cast(dst, src)`` is a per-thread
    element-wise cast with no cross-lane movement. ``.permute(...)`` the result
    to the axis order a downstream consumer (e.g. ``stmatrix`` via
    ``Tx.copy(dispatch="ldstmatrix")``) expects.

    Parameters
    ----------
    src : Buffer
        Source register frag (e.g. from ``alloc_tcgen05_ldst_frag``).
    dtype : str
        Destination element dtype.

    Returns
    -------
    Buffer
        Fresh ``local`` frag, ``src.shape`` shaped, ``src.layout``, dtype-cast.
    """
    rows, cols = src.shape
    per_thread_elems = (rows * cols) // 128
    flat = alloc_local((per_thread_elems,), dtype)
    return flat.view(rows, cols, layout=src.layout)


if TYPE_CHECKING:
    ScalarT = TypeVar("ScalarT")

    # Keep type checking/linting simple by treating wrapper as identity.
    def scalar_wrapper(x: ScalarT) -> ScalarT:
        return x

else:

    class scalar_wrapper:
        """Internal wrapper to allow IRBuilder auto-naming on scalar assignment."""

        def __init__(self, scalar: BufferLoad):
            assert isinstance(scalar, BufferLoad)
            self.scalar = scalar

        def __getattr__(self, name: str) -> Any:
            return getattr(self.scalar, name)

        def __add__(self, other):
            return self.scalar + other

        def __radd__(self, other):
            return other + self.scalar

        def __sub__(self, other):
            return self.scalar - other

        def __rsub__(self, other):
            return other - self.scalar

        def __mul__(self, other):
            return self.scalar * other

        def __rmul__(self, other):
            return other * self.scalar

        def __truediv__(self, other):
            return self.scalar / other

        def __rtruediv__(self, other):
            return other / self.scalar

        def __floordiv__(self, other):
            return self.scalar // other

        def __rfloordiv__(self, other):
            return other // self.scalar

        def __mod__(self, other):
            return self.scalar % other

        def __rmod__(self, other):
            return other % self.scalar

        def __lt__(self, other):
            return self.scalar < other

        def __le__(self, other):
            return self.scalar <= other

        def __gt__(self, other):
            return self.scalar > other

        def __ge__(self, other):
            return self.scalar >= other

        def __eq__(self, other):
            return self.scalar == other

        def __ne__(self, other):
            return self.scalar != other

        def __and__(self, other):
            return self.scalar & other

        def __rand__(self, other):
            return other & self.scalar

        def __or__(self, other):
            return self.scalar | other

        def __ror__(self, other):
            return other | self.scalar

        def __xor__(self, other):
            return self.scalar ^ other

        def __rxor__(self, other):
            return other ^ self.scalar

        def __neg__(self):
            return -self.scalar

        def __invert__(self):
            return ~self.scalar


def alloc_scalar(dtype: str = "float32", scope: str = "global") -> BufferLoad:
    """Allocate a zero-dimensional buffer (scalar)."""
    buf = alloc_buffer(shape=(1,), dtype=dtype, scope=scope, layout=TileLayout(S[1]))
    assert isinstance(buf, Buffer)
    scalar = buf[0]
    if _current_meta_construction_scope() is not None:
        return scalar
    return scalar_wrapper(scalar)


def decl_scalar(dtype, data, scope, elem_offset=None, byte_offset=None) -> BufferLoad:
    """Declare a zero-dimensional buffer (scalar) from a pointer."""
    buf = decl_buffer(
        shape=(1,),
        dtype=dtype,
        data=data,
        scope=scope,
        elem_offset=_get_elem_offset(elem_offset, byte_offset, dtype),
        strides=None,
        align=-1,
        offset_factor=0,
        buffer_type="default",
        axis_separators=None,
        layout=TileLayout(S[1]),
    )
    assert isinstance(buf, Buffer)
    scalar = buf[0]
    if _current_meta_construction_scope() is not None:
        return scalar
    return scalar_wrapper(scalar)


def shared_scalar(dtype: str = "float32") -> BufferLoad:
    """Allocate a zero-dimensional buffer in shared memory."""
    return alloc_scalar(dtype=dtype, scope="shared")


def local_scalar(dtype: str = "float32") -> BufferLoad:
    """Allocate a zero-dimensional buffer in local memory."""
    return alloc_scalar(dtype=dtype, scope="local")


def _is_meta_class_instance(value: Any) -> bool:
    return getattr(type(value), "_is_meta_class", False)


def _sanitize_meta_name_part(value: Any, fallback: str) -> str:
    if isinstance(value, str) and value.isidentifier():
        return value
    if isinstance(value, str):
        sanitized = "".join(c if c.isalnum() or c == "_" else "_" for c in value)
        if sanitized and sanitized[0].isalpha():
            return sanitized
    return fallback


def _meta_resource_for_value(value: Any) -> Any | None:
    if isinstance(value, scalar_wrapper):
        return value.scalar.buffer
    if isinstance(value, BufferLoad):
        return value.buffer
    if isinstance(value, Buffer):
        return value
    return None


def _resource_in(resource: Any, resources: list[Any]) -> bool:
    return any(_same_meta_resource(resource, other) for other in resources)


def _name_meta_value(
    prefix: str,
    value: Any,
    visited: set[int] | None = None,
    owned_resources: list[Any] | None = None,
    named_resources: list[Any] | None = None,
) -> None:
    if visited is None:
        visited = set()
    if named_resources is None:
        named_resources = []
    obj_id = id(value)
    if obj_id in visited:
        return
    visited.add(obj_id)

    resource = _meta_resource_for_value(value)
    if resource is not None:
        if owned_resources is not None and not _resource_in(resource, owned_resources):
            return
        if _resource_in(resource, named_resources):
            return
        IRBuilder.name(prefix, resource)
        named_resources.append(resource)
        return
    if isinstance(value, Var | IterVar):
        if owned_resources is not None:
            return
        IRBuilder.name(prefix, value)
        return
    if _is_meta_class_instance(value):
        existing_prefix = getattr(value, "_tirx_meta_name", None)
        if existing_prefix is not None and existing_prefix != prefix:
            return
        object.__setattr__(value, "_tirx_meta_name", prefix)
        instance_owned_resources = getattr(value, "_tirx_meta_owned_resources", [])
        for field_name, field_value in vars(value).items():
            if field_name.startswith("_tirx_"):
                continue
            _name_meta_value(
                f"{prefix}_{field_name}",
                field_value,
                visited,
                instance_owned_resources,
                named_resources,
            )
        return
    if isinstance(value, list | tuple):
        for i, item in enumerate(value):
            _name_meta_value(f"{prefix}_{i}", item, visited, owned_resources, named_resources)
        return
    if isinstance(value, dict):
        for i, (key, item) in enumerate(value.items()):
            part = _sanitize_meta_name_part(key, f"item{i}")
            _name_meta_value(f"{prefix}_{part}", item, visited, owned_resources, named_resources)


def _same_meta_resource(lhs: Any, rhs: Any) -> bool:
    same_as = getattr(lhs, "same_as", None)
    if same_as is not None:
        try:
            return bool(same_as(rhs))
        except TypeError:
            pass
    return lhs is rhs


def _collect_meta_resources(value: Any, visited: set[int] | None = None) -> list[Any]:
    if visited is None:
        visited = set()
    obj_id = id(value)
    if obj_id in visited:
        return []
    visited.add(obj_id)

    resource = _meta_resource_for_value(value)
    if resource is not None:
        return [resource]
    if _is_meta_class_instance(value):
        owned = []
        for field_name, field_value in vars(value).items():
            if field_name.startswith("_tirx_"):
                continue
            owned.extend(_collect_meta_resources(field_value, visited))
        return owned
    if isinstance(value, list | tuple):
        owned = []
        for item in value:
            owned.extend(_collect_meta_resources(item, visited))
        return owned
    if isinstance(value, dict):
        owned = []
        for item in value.values():
            owned.extend(_collect_meta_resources(item, visited))
        return owned
    return []


def _format_unowned_meta_resource_error(cls: type, record: _MetaResourceRecord, total: int) -> str:
    count = "" if total == 1 else f" ({total} total)"
    location = f"{record.filename}:{record.lineno}"
    if record.colno is not None:
        location = f"{location}:{record.colno}"
    message = [
        f"TIRx meta_class constructor created an unowned resource{count}.",
        f"  class: {cls.__name__}",
        f"  location: {location}",
    ]
    if record.code:
        message.extend(["", f"  {record.code}", "  ^ resource must be assigned to self.<field>"])
    message.extend(
        [
            "",
            "Resources created in a meta_class constructor must be reachable from the",
            "constructed instance.",
            "unowned resource at "
            f"{location}: assign it to self.<field>, or move the allocation into a "
            "parser-owned assignment.",
        ]
    )
    return "\n".join(message)


def _validate_meta_construction_scope(scope: _MetaConstructionScope) -> None:
    if not scope.created:
        object.__setattr__(scope.instance, "_tirx_meta_owned_resources", [])
        return
    created_resources = [record.value for record in scope.created]
    owned_resources = _collect_meta_resources(scope.instance)
    missing = [
        record
        for record in scope.created
        if not any(_same_meta_resource(record.value, owned) for owned in owned_resources)
    ]
    if missing:
        raise ValueError(_format_unowned_meta_resource_error(scope.cls, missing[0], len(missing)))
    object.__setattr__(scope.instance, "_tirx_meta_owned_resources", created_resources)


def name_meta_class_value(prefix: str, value: Any) -> None:
    """Name all TIR resources owned by a meta_class instance."""
    _name_meta_value(prefix, value)


def launch_thread(
    thread: IterVar | str,  # pylint: disable=redefined-outer-name
    extent: Expr,
) -> frame.LaunchThreadFrame:
    """Launch a thread.

    Parameters
    ----------
    thread : Union[IterVar, str]
        The iteration variable.

    extent : Expr
        The extent of environment thread.

    Returns
    -------
    res : frame.LaunchThreadFrame
        The result LaunchThreadFrame.

    Examples
    --------

    .. code-block:: python

    from tvm.script.ir_builder import tirx as T
    brow = T.env_thread("blockIdx.y")
    T.launch_thread(brow, 1)

    """

    if isinstance(thread, str):
        thread = String(thread)
    return _ffi_api.LaunchThread(thread, extent)  # type: ignore[attr-defined] # pylint: disable=no-member


def env_thread(thread_tag: str, dtype: str = "int32") -> IterVar:
    """Bind a var to thread env

    Parameters
    ----------
    thread_tag : str
        The thread type tag.

    dtype : str
        The data type of the thread env.

    Returns
    -------
    res : IterVar
        The result iteration variable gets bound to the thread env.

    """
    return _ffi_api.EnvThread(thread_tag, dtype)  # type: ignore[attr-defined] # pylint: disable=no-member


def buffer_store(
    buffer: Buffer,  # pylint: disable=redefined-outer-name
    value: Expr,
    indices: list[Expr | slice],
    predicate: Expr | None = None,
) -> None:
    """Buffer store node.

    Parameters
    ----------
    buffer : Buffer
        The buffer.

    value : Expr
        The value to be stored.

    indices : List[Union[Expr, slice]]
        The indices location to be stored.

    predicate : Optional[Expr]
        A vector mask of boolean values indicating which lanes of a vector are to be
        stored. The number lanes of the mask must be equal to the number of lanes in
        value.
    """
    from tvm.arith import Analyzer  # pylint: disable=import-outside-toplevel

    if not isinstance(indices, list | tuple | ir.Array):
        indices = [indices]

    expr_indices = []
    for index in indices:
        if isinstance(index, slice):
            step = 1 if index.step is None else index.step
            lanes = Analyzer().simplify(  # pylint: disable=redefined-outer-name
                (index.stop - index.start + step - 1) // step
            )
            if lanes == 1:
                expr_indices.append(index.start)
            else:
                expr_indices.append(ramp(index.start, step, lanes))
        else:
            expr_indices.append(index)
    if isinstance(value, bool) and buffer.dtype == "bool":
        value = IntImm("bool", value)
    return _ffi_api.BufferStore(  # type: ignore[attr-defined] # pylint: disable=no-member
        buffer, value, expr_indices, predicate
    )


def evaluate(value: Expr) -> None:
    """Evaluate the input expression.

    Parameters
    ----------
    value: Expr
        The input expression to evaluate.
    """
    if isinstance(value, str):
        value = StringImm(value)
    if isinstance(value, bool):
        value = IntImm("bool", value)
    return _ffi_api.Evaluate(value)  # type: ignore[attr-defined] # pylint: disable=no-member


def _ffi_name_to_dtype(name: str) -> str:
    """Convert an FFI type name to its TVM dtype string.

    Examples: "Float32" -> "float32", "Int8x4" -> "int8x4",
    "Float8E4M3" -> "float8_e4m3", "Float8E4M3B11FNUZ" -> "float8_e4m3b11fnuz".
    """
    import re

    # Insert underscore before E-notation in float8 names (E3M4, E4M3, etc.)
    s = re.sub(r"(?<=[a-z0-9])E(\d)", r"_e\1", name, flags=re.IGNORECASE)
    return s.lower()


def func_gen(name: str):
    """Generate a DtypeConstructor for each Expr dtype.

    Parameters
    ----------
    name: str
        The ffi function name to call, e.g. "Float32", "Int32".
    """
    return DtypeConstructor(name, _ffi_name_to_dtype(name))


def static_assert(x: Any, message: str = ""):
    assert x, message


def add_to_parent(stmt: tir.Stmt) -> None:
    """Add a statement to the parent frame."""
    _ffi_api.AddToParent(stmt)  # type: ignore[attr-defined] # pylint: disable=no-member


# pylint: disable=invalid-name
int8 = func_gen("Int8")
int16 = func_gen("Int16")
int32 = func_gen("Int32")
int64 = func_gen("Int64")
int8x2 = func_gen("Int8x2")
int16x2 = func_gen("Int16x2")
int32x2 = func_gen("Int32x2")
int64x2 = func_gen("Int64x2")
int8x4 = func_gen("Int8x4")
int16x4 = func_gen("Int16x4")
int32x4 = func_gen("Int32x4")
int64x4 = func_gen("Int64x4")
int8x8 = func_gen("Int8x8")
int16x8 = func_gen("Int16x8")
int32x8 = func_gen("Int32x8")
int64x8 = func_gen("Int64x8")
int8x16 = func_gen("Int8x16")
int16x16 = func_gen("Int16x16")
int32x16 = func_gen("Int32x16")
int64x16 = func_gen("Int64x16")
int8x32 = func_gen("Int8x32")
int16x32 = func_gen("Int16x32")
int32x32 = func_gen("Int32x32")
int64x32 = func_gen("Int64x32")
int8x64 = func_gen("Int8x64")
int16x64 = func_gen("Int16x64")
int32x64 = func_gen("Int32x64")
int64x64 = func_gen("Int64x64")

uint8 = func_gen("UInt8")
uint16 = func_gen("UInt16")
uint32 = func_gen("UInt32")
uint64 = func_gen("UInt64")
uint8x2 = func_gen("UInt8x2")
uint16x2 = func_gen("UInt16x2")
uint32x2 = func_gen("UInt32x2")
uint64x2 = func_gen("UInt64x2")
uint8x4 = func_gen("UInt8x4")
uint16x4 = func_gen("UInt16x4")
uint32x4 = func_gen("UInt32x4")
uint64x4 = func_gen("UInt64x4")
uint8x8 = func_gen("UInt8x8")
uint16x8 = func_gen("UInt16x8")
uint32x8 = func_gen("UInt32x8")
uint64x8 = func_gen("UInt64x8")
uint8x16 = func_gen("UInt8x16")
uint16x16 = func_gen("UInt16x16")
uint32x16 = func_gen("UInt32x16")
uint64x16 = func_gen("UInt64x16")
uint8x32 = func_gen("UInt8x32")
uint16x32 = func_gen("UInt16x32")
uint32x32 = func_gen("UInt32x32")
uint64x32 = func_gen("UInt64x32")
uint8x64 = func_gen("UInt8x64")
uint16x64 = func_gen("UInt16x64")
uint32x64 = func_gen("UInt32x64")
uint64x64 = func_gen("UInt64x64")

float16 = func_gen("Float16")
float32 = func_gen("Float32")
float64 = func_gen("Float64")
float16x2 = func_gen("Float16x2")
float32x2 = func_gen("Float32x2")
float64x2 = func_gen("Float64x2")
float16x4 = func_gen("Float16x4")
float32x4 = func_gen("Float32x4")
float64x4 = func_gen("Float64x4")
float16x8 = func_gen("Float16x8")
float32x8 = func_gen("Float32x8")
float64x8 = func_gen("Float64x8")
float16x16 = func_gen("Float16x16")
float32x16 = func_gen("Float32x16")
float64x16 = func_gen("Float64x16")
float16x32 = func_gen("Float16x32")
float32x32 = func_gen("Float32x32")
float64x32 = func_gen("Float64x32")
float16x64 = func_gen("Float16x64")
float32x64 = func_gen("Float32x64")
float64x64 = func_gen("Float64x64")

# Float8 variants
float8_e3m4 = func_gen("Float8E3M4")
float8_e3m4x2 = func_gen("Float8E3M4x2")
float8_e3m4x4 = func_gen("Float8E3M4x4")
float8_e3m4x8 = func_gen("Float8E3M4x8")
float8_e3m4x16 = func_gen("Float8E3M4x16")
float8_e3m4x32 = func_gen("Float8E3M4x32")
float8_e3m4x64 = func_gen("Float8E3M4x64")

float8_e4m3 = func_gen("Float8E4M3")
float8_e4m3x2 = func_gen("Float8E4M3x2")
float8_e4m3x4 = func_gen("Float8E4M3x4")
float8_e4m3x8 = func_gen("Float8E4M3x8")
float8_e4m3x16 = func_gen("Float8E4M3x16")
float8_e4m3x32 = func_gen("Float8E4M3x32")
float8_e4m3x64 = func_gen("Float8E4M3x64")

float8_e4m3b11fnuz = func_gen("Float8E4M3B11FNUZ")
float8_e4m3b11fnuzx2 = func_gen("Float8E4M3B11FNUZx2")
float8_e4m3b11fnuzx4 = func_gen("Float8E4M3B11FNUZx4")
float8_e4m3b11fnuzx8 = func_gen("Float8E4M3B11FNUZx8")
float8_e4m3b11fnuzx16 = func_gen("Float8E4M3B11FNUZx16")
float8_e4m3b11fnuzx32 = func_gen("Float8E4M3B11FNUZx32")
float8_e4m3b11fnuzx64 = func_gen("Float8E4M3B11FNUZx64")

float8_e4m3fn = func_gen("Float8E4M3FN")
float8_e4m3fnx2 = func_gen("Float8E4M3FNx2")
float8_e4m3fnx4 = func_gen("Float8E4M3FNx4")
float8_e4m3fnx8 = func_gen("Float8E4M3FNx8")
float8_e4m3fnx16 = func_gen("Float8E4M3FNx16")
float8_e4m3fnx32 = func_gen("Float8E4M3FNx32")
float8_e4m3fnx64 = func_gen("Float8E4M3FNx64")

float8_e4m3fnuz = func_gen("Float8E4M3FNUZ")
float8_e4m3fnuzx2 = func_gen("Float8E4M3FNUZx2")
float8_e4m3fnuzx4 = func_gen("Float8E4M3FNUZx4")
float8_e4m3fnuzx8 = func_gen("Float8E4M3FNUZx8")
float8_e4m3fnuzx16 = func_gen("Float8E4M3FNUZx16")
float8_e4m3fnuzx32 = func_gen("Float8E4M3FNUZx32")
float8_e4m3fnuzx64 = func_gen("Float8E4M3FNUZx64")

float8_e5m2 = func_gen("Float8E5M2")
float8_e5m2x2 = func_gen("Float8E5M2x2")
float8_e5m2x4 = func_gen("Float8E5M2x4")
float8_e5m2x8 = func_gen("Float8E5M2x8")
float8_e5m2x16 = func_gen("Float8E5M2x16")
float8_e5m2x32 = func_gen("Float8E5M2x32")
float8_e5m2x64 = func_gen("Float8E5M2x64")

float8_e5m2fnuz = func_gen("Float8E5M2FNUZ")
float8_e5m2fnuzx2 = func_gen("Float8E5M2FNUZx2")
float8_e5m2fnuzx4 = func_gen("Float8E5M2FNUZx4")
float8_e5m2fnuzx8 = func_gen("Float8E5M2FNUZx8")
float8_e5m2fnuzx16 = func_gen("Float8E5M2FNUZx16")
float8_e5m2fnuzx32 = func_gen("Float8E5M2FNUZx32")
float8_e5m2fnuzx64 = func_gen("Float8E5M2FNUZx64")

float8_e8m0fnu = func_gen("Float8E8M0FNU")
float8_e8m0fnux2 = func_gen("Float8E8M0FNUx2")
float8_e8m0fnux4 = func_gen("Float8E8M0FNUx4")
float8_e8m0fnux8 = func_gen("Float8E8M0FNUx8")
float8_e8m0fnux16 = func_gen("Float8E8M0FNUx16")
float8_e8m0fnux32 = func_gen("Float8E8M0FNUx32")
float8_e8m0fnux64 = func_gen("Float8E8M0FNUx64")

# Float6 variants
float6_e2m3fn = func_gen("Float6E2M3FN")
float6_e2m3fnx2 = func_gen("Float6E2M3FNx2")
float6_e2m3fnx4 = func_gen("Float6E2M3FNx4")
float6_e2m3fnx8 = func_gen("Float6E2M3FNx8")
float6_e2m3fnx16 = func_gen("Float6E2M3FNx16")
float6_e2m3fnx32 = func_gen("Float6E2M3FNx32")
float6_e2m3fnx64 = func_gen("Float6E2M3FNx64")

float6_e3m2fn = func_gen("Float6E3M2FN")
float6_e3m2fnx2 = func_gen("Float6E3M2FNx2")
float6_e3m2fnx4 = func_gen("Float6E3M2FNx4")
float6_e3m2fnx8 = func_gen("Float6E3M2FNx8")
float6_e3m2fnx16 = func_gen("Float6E3M2FNx16")
float6_e3m2fnx32 = func_gen("Float6E3M2FNx32")
float6_e3m2fnx64 = func_gen("Float6E3M2FNx64")

# Float4 variants
float4_e2m1fn = func_gen("Float4E2M1FN")
float4_e2m1fnx2 = func_gen("Float4E2M1FNx2")
float4_e2m1fnx4 = func_gen("Float4E2M1FNx4")
float4_e2m1fnx8 = func_gen("Float4E2M1FNx8")
float4_e2m1fnx16 = func_gen("Float4E2M1FNx16")
float4_e2m1fnx32 = func_gen("Float4E2M1FNx32")
float4_e2m1fnx64 = func_gen("Float4E2M1FNx64")

bfloat16 = func_gen("BFloat16")

# Shorthand aliases
f16 = float16
f32 = float32
f64 = float64
bf16 = bfloat16
i8 = int8
i16 = int16
i32 = int32
i64 = int64
u8 = uint8
u16 = uint16
u32 = uint32
u64 = uint64
# pylint: enable=invalid-name


def boolean(expr: Expr | None = None) -> Expr:
    """Construct a new tirx.Var with type boolean or cast expression to type boolean.

    Parameters
    ----------
    expr: Expr
        The expression to be cast.

    Returns
    -------
    res : Expr
        The new tirx.Var with type boolean or casted expression with type boolean.
    """
    return _ffi_api.Boolean(expr)  # type: ignore[attr-defined] # pylint: disable=no-member


def handle(
    dtype: str | None = None,
    storage_scope: str = "global",
) -> Var:
    """Create a TIR var that represents a pointer.

    Parameters
    ----------
    dtype: str | None
        The data type of the pointer. If omitted, construct an opaque handle.

    storage_scope: str
        The storage scope of the pointer.

    Returns
    -------
    res : Expr
        The new tirx.Var with type handle or casted expression with type handle.
    """
    if dtype in ("TensorMap", "tensormap", "CUtensorMap", "cuTensorMap"):
        return _ffi_api.TensorMap()  # type: ignore[attr-defined] # pylint: disable=no-member
    return _ffi_api.Handle(  # type: ignore[attr-defined] # pylint: disable=no-member
        dtype,
        storage_scope,
    )


def TensorMap() -> Var:  # pylint: disable=invalid-name
    """Create a TIRx var that represents a CUDA tensor-map descriptor.

    The host/runtime ABI passes a handle to descriptor storage. CUDA kernel
    codegen lowers this type to ``const __grid_constant__ CUtensorMap`` when it
    appears as a kernel parameter.
    """
    return _ffi_api.TensorMap()  # type: ignore[attr-defined] # pylint: disable=no-member


def void(expr: Expr | None = None) -> Expr:
    """Construct a new tirx.Var with type void or cast expression to type void.

    Parameters
    ----------
    expr: Expr
        The expression to be cast.

    Returns
    -------
    res : Expr
        The new tirx.Var with type void or casted expression with type void.
    """
    return _ffi_api.Void(expr)  # type: ignore[attr-defined] # pylint: disable=no-member


@deprecated("T.var", "T.{dtype}")
def var(dtype: str, name: str = "") -> Var:
    """Construct a new tirx.Var.

    Parameters
    ----------
    dtype: str
        The dtype of the Var.

    name: str
        The name of the Var.

    Returns
    -------
    res : Var
        The result tirx.Var.
    """
    return Var(name, dtype)  # pylint: disable=no-member


def ptr(dtype: str, storage_scope: str = "global") -> Var:
    """The pointer declaration function.

    Parameters
    ----------
    dtype : str
        The data type of the pointer.

    storage_scope : str
        The storage scope of the pointer.

    Returns
    -------
    res : Var
        The pointer.
    """
    return _ffi_api.Ptr(dtype, storage_scope)  # type: ignore[attr-defined] # pylint: disable=no-member


@deprecated("T.buffer_var", "T.handle")
def buffer_var(dtype: str, storage_scope: str = "global") -> Var:
    """The pointer declaration function.

    Parameters
    ----------
    dtype : str
        The data type of the pointer.

    storage_scope : str
        The storage scope of the pointer.

    Returns
    -------
    res : Var
        The pointer.
    """
    return _ffi_api.Ptr(dtype, storage_scope)  # type: ignore[attr-defined] # pylint: disable=no-member


def min(a: Expr, b: Expr) -> Expr:  # pylint: disable=redefined-builtin
    """Compute the minimum value of two expressions.

    Parameters
    ----------
    a : Expr
        The left hand operand

    b : Expr
        The right hand operand

    Returns
    -------
    res : Expr
        The result expression.
    """
    return _ffi_api.min(a, b)  # type: ignore[attr-defined] # pylint: disable=no-member


def max(a: Expr, b: Expr) -> Expr:  # pylint: disable=redefined-builtin
    """Compute the maximum value of two expressions.

    Parameters
    ----------
    a : Expr
        The left hand operand

    b : Expr
        The right hand operand

    Returns
    -------
    res : Expr
        The result expression.
    """
    return _ffi_api.max(a, b)  # type: ignore[attr-defined] # pylint: disable=no-member


def iter_var(v: Var | str, dom: ir.Range, iter_type: str, thread_tag: str) -> IterVar:
    """The iteration variable.

    Parameters
    ----------
    var : Union[Var, str]
        The internal variable that is used for iteration.

    dom : Range
        The domain of the iteration.

    iter_type : str
        The iteration type.

    thread_tag : str
        The thread type tag.

    Returns
    -------
    res : IterVar
        The iteration variable.
    """
    iter_type = getattr(IterVar, iter_type)
    return IterVar(dom, v, iter_type, thread_tag)


def comm_reducer(combiner: Callable, identity: list[Expr]) -> CommReducer:
    """
    Create a CommReducer from lambda inputs/outputs and the identities

    Parameters
    ----------
    combiner : Callable
        A binary function which takes two Expr as input to return a Expr.

    identity : List[Expr]
        A list of types of output Expr.

    Returns
    -------
    res : CommReducer
        The CommReducer.
    """
    params = inspect.signature(combiner).parameters
    num_args = len(params)
    args = []
    for name, i in zip(params.keys(), identity + identity):
        if isinstance(i, int):
            args.append(Var(name, "int32"))
        else:
            args.append(Var(name, i.ty))
    res = combiner(*args)
    if not isinstance(res, tuple):
        res = (res,)
    return CommReducer(args[: num_args // 2], args[num_args // 2 :], res, identity)


def index_map(
    mapping: Callable,
    *,
    inverse_index_map: Callable | None = None,
    index_dtype: str = "int64",
) -> IndexMap:
    """Create a TIR Index mapping"""
    return IndexMap.from_func(mapping, inverse_index_map=inverse_index_map, index_dtype=index_dtype)


def target(
    target_config: dict | str,
    host: dict | str | Target | None = None,
) -> Target:
    """
    Create a target

    Parameters
    ----------
    target_config : Union[Dict, str]
        The target configuration.

    host : Optional[Union[Dict, str, Target]]
        The target configuration.

    Returns
    -------
    res : Target
        The target.
    """
    if not isinstance(target_config, str | dict):
        raise ValueError(
            f"T.target expected a config dict or string, but got {type(target_config)}"
        )
    if host is not None and not isinstance(host, str | dict | Target):
        raise ValueError(
            "T.target expected the host to be "
            "a config dict, string, or T.target, "
            f"but got {type(host)}"
        )
    if isinstance(target_config, dict) and "host" in target_config and host is not None:
        raise ValueError(
            "T.target expects to either receive the host "
            "as part of the target's config dictionary, "
            "or as a separate argument, but not both."
        )
    return Target(target_config, host)


def Range(begin: Expr, end: Expr) -> ir.Range:  # pylint: disable=invalid-name
    """
    Create a Range object.

    Parameters
    ----------
    begin : Expr
        The begin value of the range.

    end : Optional[Expr]
        The end value of the range.
    """
    return ir.Range(begin, end)


if TYPE_CHECKING:
    T = TypeVar("T")
    C = TypeVar("C")

    # When type checking (and by extension, for linters like Pylint), treat
    # meta_var as an identity function.
    def meta_var(x: T) -> T:
        return x

    def meta_class(cls: C) -> C:
        return cls

else:

    def _install_meta_class(cls):
        if cls.__dict__.get("_tirx_meta_class_installed", False):
            cls._is_meta_class = True
            return cls

        original_init = getattr(cls, "__init__", object.__init__)
        original_setattr = getattr(cls, "__setattr__", object.__setattr__)
        original_init_subclass = getattr(cls, "__init_subclass__", None)

        def __init__(self, *args, **kwargs):
            with _with_meta_construction_scope(self, type(self)) as scope:
                original_init(self, *args, **kwargs)
                _validate_meta_construction_scope(scope)

        def __setattr__(self, name, value):
            if isinstance(value, scalar_wrapper):
                value = value.scalar
            original_setattr(self, name, value)

        @classmethod
        def __init_subclass__(subcls, **kwargs):
            if original_init_subclass is not None:
                original_init_subclass(**kwargs)
            _install_meta_class(subcls)

        cls.__init__ = __init__
        cls.__setattr__ = __setattr__
        cls.__init_subclass__ = __init_subclass__
        cls._is_meta_class = True
        cls._tirx_meta_class_installed = True
        return cls

    def meta_class(cls):
        """Decorator for utility classes used inside @T.prim_func.

        Instances of decorated classes are treated as parser meta values.
        """
        return _install_meta_class(cls)

    class meta_var:
        """A meta variable used in TVMScript metaprogramming.

        The value does not appear in the final TIR and only exists in the parser.

        Parameters
        ----------
        value: Any
            The meta variable.
        """

        def __init__(self, value: Any) -> None:
            self.value = value

        def __iter__(self):
            # Return a generator that yields wrapped items.
            return (meta_var(i) for i in self.value)


# pylint: disable=invalid-name


T = TypeVar("T")
P = ParamSpec("P")


def _op_wrapper(func: Callable[P, T]) -> Callable[P, T]:
    @functools.wraps(func)
    def wrapped(*args, **kwargs) -> T:
        if "dtype" in kwargs:
            kwargs.pop("dtype")
        return func(*args, **kwargs)

    # Expose underlying tir op name for printer registration
    try:
        wrapped.__tir_op_name__ = getattr(func, "__name__", None)
    except Exception:  # pragma: no cover
        pass
    return wrapped


def _dtype_forward(func):
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        if "dtype" in kwargs:
            args = (kwargs.pop("dtype"), *args)
        return func(*args, **kwargs)

    # Expose underlying tir op name for printer registration
    try:
        wrapped.__tir_op_name__ = getattr(func, "__name__", None)
    except Exception:  # pragma: no cover
        pass
    return wrapped


class WebGPUNamespace:
    """The WebGPU intrinsics submodule."""

    @staticmethod
    def subgroup_shuffle(var, lane):
        if isinstance(var, Buffer):
            var = var[0]
        return _tir_op.call_intrin(var.ty, "tirx.webgpu.subgroup_shuffle", var, lane)

    @staticmethod
    def subgroup_shuffle_up(var, delta):
        if isinstance(var, Buffer):
            var = var[0]
        return _tir_op.call_intrin(var.ty, "tirx.webgpu.subgroup_shuffle_up", var, delta)

    @staticmethod
    def subgroup_shuffle_down(var, delta):
        if isinstance(var, Buffer):
            var = var[0]
        return _tir_op.call_intrin(var.ty, "tirx.webgpu.subgroup_shuffle_down", var, delta)


webgpu = WebGPUNamespace()


#
# Register printer namespace mapping from the builder namespaces so the
# TVMScript printer emits dotted names that match parser namespaces.
#
def _register_script_namespace_printer_names(ns_obj, dotted_prefix):
    def register_printer_name(op_name, script_name):
        try:
            ir.Op.get(op_name)
        except Exception:
            return
        try:
            _register_op_attr(op_name, "TScriptPrinterName", script_name, level=20)
        except Exception:
            pass

    def visit(ns_obj, dotted_prefix):
        # If the namespace object itself maps to an op via __call__
        call_op = getattr(ns_obj, "__tir_call_op_name__", None)
        if call_op:
            flat_name = f"tirx.{call_op}"
            for op_name in {flat_name, _tir_op._canonical_device_intrin_name(flat_name)}:
                register_printer_name(op_name, dotted_prefix)
        # Walk attributes to find wrapped ops and sub-namespaces
        for name in dir(ns_obj):
            if name.startswith("_"):
                continue
            try:
                val = getattr(ns_obj, name)
            except Exception:
                continue
            # Sub-namespace: recurse
            if hasattr(val, "__dict__") and val.__class__.__name__.endswith("Namespace"):
                visit(val, f"{dotted_prefix}.{name}")
                continue
            # Wrapped op (callable with attached __tir_op_name__)
            op_name = getattr(val, "__tir_op_name__", None)
            if callable(val) and op_name:
                flat_name = f"tirx.{op_name}"
                script_name = f"{dotted_prefix}.{name}"
                for full_op_name in {flat_name, _tir_op._canonical_device_intrin_name(flat_name)}:
                    register_printer_name(full_op_name, script_name)

    visit(ns_obj, dotted_prefix)


def register_script_namespace(name: str, namespace: object) -> object:
    """Register a TVMScript namespace on the TIRx builder facade."""
    globals()[name] = namespace
    if "__all__" in globals() and name not in __all__:
        __all__.append(name)

    import sys  # pylint: disable=import-outside-toplevel

    for module_name in [
        "tvm.tirx.script.builder",
        "tvm.tirx.script.parser",
        "tvm.tirx.script",
        "tvm.script.tirx",
    ]:
        module = sys.modules.get(module_name)
        if module is None:
            continue
        setattr(module, name, namespace)
        module_all = getattr(module, "__all__", None)
        if isinstance(module_all, list) and name not in module_all:
            module_all.append(name)

    _register_script_namespace_printer_names(namespace, name)
    return namespace


def _register_tir_namespace_printer_names():
    try:
        _register_script_namespace_printer_names(webgpu, "webgpu")
    except Exception:
        # Best-effort registration; avoid import-time hard failure
        pass


# Execute registration on import so printer picks up dotted names
_register_tir_namespace_printer_names()


abs = _op_wrapper(_tir_op.abs)  # pylint: disable=redefined-builtin
acos = _op_wrapper(_tir_op.acos)
acosh = _op_wrapper(_tir_op.acosh)
address_of = _op_wrapper(_tir_op.address_of)
asin = _op_wrapper(_tir_op.asin)
asinh = _op_wrapper(_tir_op.asinh)
atan = _op_wrapper(_tir_op.atan)
atan2 = _op_wrapper(_tir_op.atan2)
atanh = _op_wrapper(_tir_op.atanh)
bitwise_and = _op_wrapper(_tir_op.bitwise_and)
bitwise_not = _op_wrapper(_tir_op.bitwise_not)
bitwise_or = _op_wrapper(_tir_op.bitwise_or)
bitwise_xor = _op_wrapper(_tir_op.bitwise_xor)
ceil = _op_wrapper(_tir_op.ceil)
clz = _op_wrapper(_tir_op.clz)
copysign = _op_wrapper(_tir_op.copysign)
cos = _op_wrapper(_tir_op.cos)
cosh = _op_wrapper(_tir_op.cosh)
erf = _op_wrapper(_tir_op.erf)
exp = _op_wrapper(_tir_op.exp)
exp2 = _op_wrapper(_tir_op.exp2)
exp10 = _op_wrapper(_tir_op.exp10)
filter = _op_wrapper(_tir_op.filter)  # pylint: disable=redefined-builtin
selector = _op_wrapper(_tir_op.selector)
floor = _op_wrapper(_tir_op.floor)
ceildiv = _op_wrapper(_tir_op.ceildiv)
floordiv = _op_wrapper(_tir_op.floordiv)
floormod = _op_wrapper(_tir_op.floormod)
fmod = _op_wrapper(_tir_op.fmod)
fma = _op_wrapper(_tir_op.fma)
hypot = _op_wrapper(_tir_op.hypot)
if_then_else = _op_wrapper(_tir_op.if_then_else)
infinity = _op_wrapper(_tir_op.infinity)
isfinite = _op_wrapper(_tir_op.isfinite)
isinf = _op_wrapper(_tir_op.isinf)
isnan = _op_wrapper(_tir_op.isnan)
isnullptr = _op_wrapper(_tir_op.isnullptr)
ldexp = _op_wrapper(_tir_op.ldexp)
likely = _op_wrapper(_tir_op.likely)
log = _op_wrapper(_tir_op.log)
log1p = _op_wrapper(_tir_op.log1p)
log2 = _op_wrapper(_tir_op.log2)
log10 = _op_wrapper(_tir_op.log10)
lookup_param = _op_wrapper(_tir_op.lookup_param)
max_value = _op_wrapper(_tir_op.max_value)
min_value = _op_wrapper(_tir_op.min_value)
nearbyint = _op_wrapper(_tir_op.nearbyint)
nextafter = _op_wrapper(_tir_op.nextafter)
popcount = _op_wrapper(_tir_op.popcount)
pow = _op_wrapper(_tir_op.pow)  # pylint: disable=redefined-builtin
q_multiply_shift = _op_wrapper(_tir_op.q_multiply_shift)
q_multiply_shift_per_axis = _op_wrapper(_tir_op.q_multiply_shift_per_axis)
ret = _op_wrapper(_tir_op.ret)
continue_loop = _op_wrapper(_tir_op.continue_loop)
break_loop = _op_wrapper(_tir_op.break_loop)
round = _op_wrapper(_tir_op.round)  # pylint: disable=redefined-builtin
rsqrt = _op_wrapper(_tir_op.rsqrt)
shift_left = _op_wrapper(_tir_op.shift_left)
shift_right = _op_wrapper(_tir_op.shift_right)
sigmoid = _op_wrapper(_tir_op.sigmoid)
sin = _op_wrapper(_tir_op.sin)
sinh = _op_wrapper(_tir_op.sinh)
sqrt = _op_wrapper(_tir_op.sqrt)
tan = _op_wrapper(_tir_op.tan)
tanh = _op_wrapper(_tir_op.tanh)
thread_return = _op_wrapper(_tir_op.thread_return)
trunc = _op_wrapper(_tir_op.trunc)
truncdiv = _op_wrapper(_tir_op.truncdiv)
truncmod = _op_wrapper(_tir_op.truncmod)
tvm_access_ptr = _op_wrapper(_tir_op.tvm_access_ptr)
ptr_byte_offset = _op_wrapper(_tir_op.ptr_byte_offset)
tvm_throw_last_error = _op_wrapper(_tir_op.tvm_throw_last_error)
print_buffer = _op_wrapper(_tir_op.print_buffer)
tvm_stack_alloca = _op_wrapper(_tir_op.tvm_stack_alloca)
tvm_stack_make_shape = _op_wrapper(_tir_op.tvm_stack_make_shape)
tvm_stack_make_array = _op_wrapper(_tir_op.tvm_stack_make_array)
call_packed = _op_wrapper(_tir_op.call_packed)
call_cpacked = _op_wrapper(_tir_op.call_cpacked)
call_packed_lowered = _op_wrapper(_tir_op.call_packed_lowered)
call_cpacked_lowered = _op_wrapper(_tir_op.call_cpacked_lowered)
tvm_tuple = _op_wrapper(_tir_op.tvm_tuple)
handle_add_byte_offset = _op_wrapper(_tir_op.handle_add_byte_offset)
tvm_struct_set = _op_wrapper(_tir_op.tvm_struct_set)
tvm_struct_get = _tir_op.tvm_struct_get
tvm_thread_invariant = _op_wrapper(_tir_op.tvm_thread_invariant)
tvm_thread_allreduce = _op_wrapper(_tir_op.tvm_thread_allreduce)
tvm_load_matrix_sync = _op_wrapper(_tir_op.tvm_load_matrix_sync)
tvm_mma_sync = _op_wrapper(_tir_op.tvm_mma_sync)
tvm_bmma_sync = _op_wrapper(_tir_op.tvm_bmma_sync)
tvm_fill_fragment = _op_wrapper(_tir_op.tvm_fill_fragment)
tvm_store_matrix_sync = _op_wrapper(_tir_op.tvm_store_matrix_sync)
tvm_storage_sync = _tir_op.tvm_storage_sync
tvm_kernel_replace_point = _op_wrapper(_tir_op.tvm_kernel_replace_point)
tvm_global_barrier_kinit = _tir_op.tvm_global_barrier_kinit
tvm_warp_shuffle = _tir_op.tvm_warp_shuffle
tvm_warp_shuffle_up = _tir_op.tvm_warp_shuffle_up
tvm_warp_shuffle_down = _tir_op.tvm_warp_shuffle_down
tvm_warp_shuffle_xor = _tir_op.tvm_warp_shuffle_xor
tvm_warp_activemask = _tir_op.tvm_warp_activemask
cooperative_tensor_fill = _op_wrapper(_tir_op.cooperative_tensor_fill)
cooperative_tensor_load = _op_wrapper(_tir_op.cooperative_tensor_load)
cooperative_tensor_store = _op_wrapper(_tir_op.cooperative_tensor_store)
cooperative_tensor_multiply_accumulate = _op_wrapper(_tir_op.cooperative_tensor_multiply_accumulate)
assume = _op_wrapper(_tir_op.assume)
undef = _op_wrapper(_tir_op.undef)
TVMBackendAllocWorkspace = _op_wrapper(_tir_op.TVMBackendAllocWorkspace)
TVMBackendFreeWorkspace = _op_wrapper(_tir_op.TVMBackendFreeWorkspace)
start_profile_intrinsic = _op_wrapper(_tir_op.start_profile_intrinsic)
end_profile_intrinsic = _op_wrapper(_tir_op.end_profile_intrinsic)
anylist_getitem = _op_wrapper(_tir_op.anylist_getitem)
anylist_resetitem = _op_wrapper(_tir_op.anylist_resetitem)
anylist_setitem_call_packed = _op_wrapper(_tir_op.anylist_setitem_call_packed)
anylist_setitem_call_cpacked = _op_wrapper(_tir_op.anylist_setitem_call_cpacked)
vscale = _op_wrapper(_tir_op.vscale)
ignore_loop_partition = _op_wrapper(_tir_op.ignore_loop_partition)

reinterpret = _dtype_forward(_tir_op.reinterpret)
call_extern = _dtype_forward(_tir_op.call_extern)
call_intrin = _dtype_forward(_tir_op.call_intrin)
call_llvm_intrin = _dtype_forward(_tir_op.call_llvm_intrin)
call_llvm_pure_intrin = _dtype_forward(_tir_op.call_llvm_pure_intrin)
call_pure_extern = _dtype_forward(_tir_op.call_pure_extern)
vectorlow = _dtype_forward(_tir_op.vectorlow)
vectorhigh = _dtype_forward(_tir_op.vectorhigh)
vectorcombine = _dtype_forward(_tir_op.vectorcombine)
get_active_lane_mask = _dtype_forward(_tir_op.get_active_lane_mask)
dp4a = _dtype_forward(_tir_op.dp4a)


broadcast = Broadcast
ramp = Ramp
fabs = abs
tvm_call_packed = call_packed
tvm_call_cpacked = call_cpacked
tvm_call_packed_lowered = call_packed_lowered
tvm_call_cpacked_lowered = call_cpacked_lowered

# pylint: enable=invalid-name

bases = [
    "float8_e3m4",
    "float8_e4m3",
    "float8_e4m3b11fnuz",
    "float8_e4m3fn",
    "float8_e4m3fnuz",
    "float8_e5m2",
    "float8_e5m2fnuz",
    "float8_e8m0fnu",
    "float6_e2m3fn",
    "float6_e3m2fn",
    "float4_e2m1fn",
    "float16",
    "float32",
    "float64",
]
lanes = [1, 2, 4, 8, 16, 32, 64]

float_types = []
for base in bases:
    for lane in lanes:
        suffix = f"x{lane}" if lane != 1 else ""
        float_types.append(f"{base}{suffix}")

__all__ = [
    *float_types,
    "int8",
    "int16",
    "int32",
    "int64",
    "int8x2",
    "int16x2",
    "int32x2",
    "int64x2",
    "int8x4",
    "int16x4",
    "int32x4",
    "int64x4",
    "int8x8",
    "int16x8",
    "int32x8",
    "int64x8",
    "int8x16",
    "int16x16",
    "int32x16",
    "int64x16",
    "int8x32",
    "int16x32",
    "int32x32",
    "int64x32",
    "int8x64",
    "int16x64",
    "int32x64",
    "int64x64",
    "uint8",
    "uint16",
    "uint32",
    "uint64",
    "uint8x2",
    "uint16x2",
    "uint32x2",
    "uint64x2",
    "uint8x4",
    "uint16x4",
    "uint32x4",
    "uint64x4",
    "uint8x8",
    "uint16x8",
    "uint32x8",
    "uint64x8",
    "uint8x16",
    "uint16x16",
    "uint32x16",
    "uint64x16",
    "uint8x32",
    "uint16x32",
    "uint32x32",
    "uint64x32",
    "uint8x64",
    "uint16x64",
    "uint32x64",
    "uint64x64",
    "float8_e4m3fn",
    "float8_e5m2",
    "float4_e2m1fn",
    "float16",
    "float32",
    "float64",
    "float4_e2m1fnx2",
    "float8_e4m3fnx4",
    "float8_e5m2x4",
    "float4_e2m1fnx4",
    "float16x2",
    "float32x2",
    "float64x2",
    "float16x4",
    "float32x4",
    "float64x4",
    "float8_e4m3fnx8",
    "float8_e5m2x8",
    "float4_e2m1fnx8",
    "float16x8",
    "float32x8",
    "float64x8",
    "float8_e4m3fnx16",
    "float8_e5m2x16",
    "float4_e2m1fnx16",
    "float16x16",
    "float32x16",
    "float64x16",
    "float8_e4m3fnx32",
    "float8_e5m2x32",
    "float4_e2m1fnx32",
    "float16x32",
    "float32x32",
    "float64x32",
    "float8_e4m3fnx64",
    "float8_e5m2x64",
    "float4_e2m1fnx64",
    "float16x64",
    "float32x64",
    "float64x64",
    "bfloat16",
    "buffer",
    "buffer_decl",
    "prim_func",
    "arg",
    "func_name",
    "func_attr",
    "func_ret",
    "match_buffer",
    "sblock",
    "block_name_suffix_context",
    "init",
    "where",
    "reads",
    "writes",
    "sblock_attr",
    "alloc_buffer",
    "sblock_alloc_buffer",
    "wg_reg_tile",
    "axis",
    "serial",
    "parallel",
    "vectorized",
    "unroll",
    "thread_binding",
    "grid",
    "Assert",
    "attr",
    "hint",
    "While",
    "Break",
    "Continue",
    "If",
    "Then",
    "Else",
    "decl_buffer",
    "launch_thread",
    "env_thread",
    "buffer_store",
    "evaluate",
    "boolean",
    "handle",
    "void",
    "var",
    "ptr",
    "min",
    "max",
    "iter_var",
    "comm_reducer",
    "index_map",
    "target",
    "buffer_var",
    "abs",
    "fabs",
    "acos",
    "acosh",
    "address_of",
    "asin",
    "asinh",
    "atan",
    "atan2",
    "atanh",
    "bitwise_and",
    "bitwise_not",
    "bitwise_or",
    "bitwise_xor",
    "ceil",
    "clz",
    "copysign",
    "cos",
    "cosh",
    "erf",
    "exp",
    "exp2",
    "exp10",
    "floor",
    "ceildiv",
    "floordiv",
    "floormod",
    "fmod",
    "fma",
    "filter",
    "selector",
    "hypot",
    "if_then_else",
    "infinity",
    "isfinite",
    "isinf",
    "isnan",
    "isnullptr",
    "ldexp",
    "likely",
    "log",
    "log1p",
    "log2",
    "log10",
    "lookup_param",
    "max_value",
    "min_value",
    "nearbyint",
    "nextafter",
    "popcount",
    "pow",
    "q_multiply_shift",
    "q_multiply_shift_per_axis",
    "ret",
    "continue_loop",
    "break_loop",
    "reinterpret",
    "round",
    "rsqrt",
    "shift_left",
    "shift_right",
    "sigmoid",
    "sin",
    "sinh",
    "sqrt",
    "tan",
    "tanh",
    "thread_return",
    "trunc",
    "truncdiv",
    "truncmod",
    "tvm_access_ptr",
    "ptr_byte_offset",
    "tvm_throw_last_error",
    "print_buffer",
    "tvm_stack_alloca",
    "tvm_stack_make_shape",
    "tvm_stack_make_array",
    "call_packed",
    "call_cpacked",
    "call_packed_lowered",
    "call_cpacked_lowered",
    "call_extern",
    "call_intrin",
    "call_llvm_intrin",
    "call_llvm_pure_intrin",
    "call_pure_extern",
    "tvm_tuple",
    "handle_add_byte_offset",
    "tvm_struct_set",
    "tvm_struct_get",
    "tvm_thread_invariant",
    "tvm_thread_allreduce",
    "tvm_load_matrix_sync",
    "tvm_mma_sync",
    "tvm_bmma_sync",
    "tvm_fill_fragment",
    "tvm_store_matrix_sync",
    "tvm_storage_sync",
    "tvm_kernel_replace_point",
    "tvm_global_barrier_kinit",
    "tvm_warp_shuffle",
    "tvm_warp_shuffle_up",
    "tvm_warp_shuffle_down",
    "tvm_warp_shuffle_xor",
    "tvm_warp_activemask",
    "cooperative_tensor_fill",
    "cooperative_tensor_load",
    "cooperative_tensor_store",
    "cooperative_tensor_multiply_accumulate",
    "vectorlow",
    "vectorhigh",
    "vectorcombine",
    "dp4a",
    "assume",
    "undef",
    "tvm_call_packed",
    "tvm_call_cpacked",
    "tvm_call_packed_lowered",
    "tvm_call_cpacked_lowered",
    "TVMBackendAllocWorkspace",
    "TVMBackendFreeWorkspace",
    "start_profile_intrinsic",
    "end_profile_intrinsic",
    "meta_var",
    "anylist_getitem",
    "anylist_resetitem",
    "anylist_setitem_call_packed",
    "anylist_setitem_call_cpacked",
    "llvm_lookup_intrinsic_id",
    "type_annotation",
    "broadcast",
    "ramp",
    "cast",
    # tvm.tirx.expr
    "Var",
    "Reduce",
    "FloatImm",
    "IntImm",
    "StringImm",
    "Cast",
    "Add",
    "Sub",
    "Mul",
    "Div",
    "Mod",
    "FloorDiv",
    "FloorMod",
    "Min",
    "Max",
    "EQ",
    "NE",
    "LT",
    "LE",
    "GT",
    "GE",
    "And",
    "Or",
    "Not",
    "Select",
    "BufferLoad",
    "ProducerLoad",
    "Ramp",
    "Broadcast",
    "Shuffle",
    "Call",
    "CallEffectKind",
    "let",
    "Bind",
    "bind",
    "LetAnnotation",
    "LocalVectorAnnotation",
    "DtypeConstructor",
    "Let",
    "IterVar",
    "CommReducer",
    "Range",
    "vscale",
    "get_active_lane_mask",
    "call_kernel",
    "ignore_loop_partition",
]

__all__ += [
    "ComposeLayout",
    "ExecScope",
    "Iter",
    "Layout",
    "R",
    "S",
    "ScopeIdDef",
    "SwizzleLayout",
    "TensorMap",
    "TileLayout",
    "Var",
    "add_to_parent",
    "alloc_cast_frag",
    "alloc_local",
    "alloc_scalar",
    "alloc_shared",
    "alloc_tcgen05_ldst_frag",
    "cluster_id",
    "cta_id",
    "cta_id_in_cluster",
    "cta_id_in_pair",
    "decl_scalar",
    "device_entry",
    "lane_id",
    "local_scalar",
    "meta_class",
    "register_script_namespace",
    "scalar_wrapper",
    "scope_id",
    "shared_scalar",
    "smem",
    "static_assert",
    "thread_id",
    "thread_id_in_wg",
    "tmem",
    "warp_id",
    "warp_id_in_wg",
    "warpgroup_id",
    "webgpu",
]

# Shorthand dtype aliases
__all__ += ["bf16", "f16", "f32", "f64", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"]
