Files
Bohan Hou 859498dc01 [TIRx] Bringup TIRx Infrastructure (#19581)
## Summary

This PR adds the initial TIRx support needed for low-level programming
of Blackwell-class GPU architectures. As part of the ongoing TIRx
refactor, it introduces TVMScript support for directly scripting
advanced hardware features without relying on scheduling as the primary
programming interface.

The change keeps existing `s_tir` script support intact while making
direct scripting a first-class path for TIRx programs.

## Main Changes

- Add TIRx operator dispatch and layout infrastructure.
- Add TVMScript support for new low-level TIRx operations.
- Add analysis, transform, and lowering support for TIRx IR nodes.
- Add CUDA/Blackwell-oriented codegen and intrinsic coverage.
- Add Python and C++ integration points for TIRx scripting and runtime
support.

## Validation

- `pre-commit run --all-files`
- `ninja -C build -j32`
- `CUDA_VISIBLE_DEVICES=2 pytest tests/python/tirx/ -n 16`
  - `1723 passed, 47 skipped, 32 warnings`
- `CUDA_VISIBLE_DEVICES=2 python -m pytest -v
tests/python/all-platform-minimal-test`
  - `37 passed, 105 skipped`
- `TVM_TEST_TARGETS=llvm python -m pytest -v tests/python/tirx-analysis
tests/python/tirx-base tests/python/tirx-transform -n 16`
  - `664 passed, 25 skipped, 9 xfailed, 1 xpassed`

## Local CI Notes

Some full CI-equivalent jobs were not locally reproducible because this
machine is missing parts of the Apache TVM CI environment, including
`llvm-config-15/17`, Vulkan, ROCm, Maven, Sphinx, Doxygen, Emscripten,
and ARM/QEMU cross-toolchain components. Metal-specific tests were
skipped locally because no Metal runtime is available.
2026-05-18 16:44:43 -07:00

521 lines
15 KiB
Python

# 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.
"""Doc types for TVMScript Unified Printer"""
from collections.abc import Sequence
from enum import IntEnum, unique
from typing import Union
from tvm_ffi import register_object
from tvm_ffi.access_path import AccessPath
from tvm.runtime import Object
from tvm.tirx import FloatImm, IntImm
from . import _ffi_api
@register_object("script.printer.Doc")
class Doc(Object):
"""Base class of all Docs"""
@register_object("script.printer.ExprDoc")
class ExprDoc(Doc):
"""Base class of all expression Docs"""
def attr(self, name: str) -> "AttrAccessDoc":
"""
Create a doc that represents attribute access on self.
Parameters
----------
name : str
The attribute name to access
Returns
-------
doc : AttrAccessDoc
"""
return _ffi_api.ExprDocAttr(self, name) # type: ignore # pylint: disable=no-member
def call(self, *args: tuple["ExprDoc"], **kwargs: dict[str, "ExprDoc"]) -> "CallDoc":
"""
Create a doc that represents function call, with self as callee.
Parameters
----------
*args : ExprDoc
The positional arguments of the function call.
**kwargs
The keyword arguments of the function call.
Returns
-------
doc : CallDoc
"""
kwargs_keys = list(kwargs.keys())
kwargs_values = list(kwargs.values())
return _ffi_api.ExprDocCall(self, args, kwargs_keys, kwargs_values) # type: ignore # pylint: disable=no-member
_IndexType = Union["ExprDoc", "SliceDoc"]
def __getitem__(self, indices: tuple[_IndexType] | _IndexType) -> "IndexDoc":
"""
Create a doc that represents index access on self.
Parameters
----------
indices : Union[Tuple[Union["ExprDoc", "SliceDoc"]], Union["ExprDoc", "SliceDoc"]]
The indices to access
Returns
-------
doc : IndexDoc
"""
if not isinstance(indices, tuple):
indices = (indices,)
return _ffi_api.ExprDocIndex(self, indices) # type: ignore # pylint: disable=no-member
def __iter__(self):
"""
This is implemented to prevent confusing error message when trying to use ExprDoc
as iterable. According to PEP-234, An object can be iterated over if it
implements __iter__() or __getitem__(). If an object has only __getitem__
but not __iter__, interpreter will iterate the object by calling
__getitem__ with 0, 1, 2, ..., until an IndexError is raised.
https://peps.python.org/pep-0234/#python-api-specification
"""
raise RuntimeError(f"{self.__class__} cannot be used as iterable.")
@register_object("script.printer.StmtDoc")
class StmtDoc(Doc):
"""Base class of statement doc"""
@register_object("script.printer.StmtBlockDoc")
class StmtBlockDoc(Doc):
"""The container doc that holds a list of StmtDoc.
Note: `StmtBlockDoc` is never used in the IR, but a temporary container that allows holding a
list of StmtDoc.
"""
stmts: Sequence[StmtDoc]
def __init__(self, stmts: list[StmtDoc]):
self.__init_handle_by_constructor__(_ffi_api.StmtBlockDoc, stmts) # type: ignore # pylint: disable=no-member
@register_object("script.printer.LiteralDoc")
class LiteralDoc(ExprDoc):
"""Doc that represents literal value"""
value: str | IntImm | FloatImm | None
def __init__(
self,
value: str | float | bool | int | None,
path: AccessPath | None = None,
):
if value is None:
self.__init_handle_by_constructor__(_ffi_api.LiteralDocNone, path) # type: ignore # pylint: disable=no-member
elif isinstance(value, str):
self.__init_handle_by_constructor__(
_ffi_api.LiteralDocStr, # type: ignore # pylint: disable=no-member
value,
path,
)
elif isinstance(value, float):
self.__init_handle_by_constructor__(
_ffi_api.LiteralDocFloat, # type: ignore # pylint: disable=no-member
value,
path,
)
elif isinstance(value, bool):
self.__init_handle_by_constructor__(
_ffi_api.LiteralDocBoolean, # type: ignore # pylint: disable=no-member
value,
path,
)
elif isinstance(value, int):
self.__init_handle_by_constructor__(
_ffi_api.LiteralDocInt, # type: ignore # pylint: disable=no-member
value,
path,
)
else:
raise TypeError(f"Unsupported type {type(value)} for LiteralDoc")
@register_object("script.printer.IdDoc")
class IdDoc(ExprDoc):
"""Doc that represents identifier"""
name: str
def __init__(self, name: str):
self.__init_handle_by_constructor__(_ffi_api.IdDoc, name) # type: ignore # pylint: disable=no-member
@register_object("script.printer.AttrAccessDoc")
class AttrAccessDoc(ExprDoc):
"""Doc that represents attribute access on an expression"""
value: ExprDoc
name: str
def __init__(self, value: ExprDoc, name: str):
self.__init_handle_by_constructor__(_ffi_api.AttrAccessDoc, value, name) # type: ignore # pylint: disable=no-member
@register_object("script.printer.IndexDoc")
class IndexDoc(ExprDoc):
"""Doc that represents index access on an expression"""
value: ExprDoc
indices: Sequence[Union[ExprDoc, "SliceDoc"]]
def __init__(self, value: ExprDoc, indices: list[Union[ExprDoc, "SliceDoc"]]):
self.__init_handle_by_constructor__(_ffi_api.IndexDoc, value, indices) # type: ignore # pylint: disable=no-member
@register_object("script.printer.CallDoc")
class CallDoc(ExprDoc):
"""Doc that represents function call"""
callee: ExprDoc
args: Sequence[ExprDoc]
kwargs_keys: Sequence[str]
kwargs_values: Sequence[ExprDoc]
def __init__(self, callee: ExprDoc, *args: tuple[ExprDoc], **kwargs: dict[str, ExprDoc]):
kwargs_keys = list(kwargs.keys())
kwargs_values = list(kwargs.values())
self.__init_handle_by_constructor__(
_ffi_api.CallDoc, # type: ignore # pylint: disable=no-member
callee,
args,
kwargs_keys,
kwargs_values,
)
@unique
class OperationKind(IntEnum):
"""
This enum represents the kind of operation (operator) in OperationDoc
It's mirrored from OperationDocNode::Kind at include/tvm/script/printer/doc.h
"""
# The name convention follows https://docs.python.org/3/library/ast.html
# pylint: disable=invalid-name
_UnaryStart = 0
USub = 1
Invert = 2
Not = 3
_UnaryEnd = 4
_BinaryStart = 5
Add = 6
Sub = 7
Mult = 8
Div = 9
FloorDiv = 10
Mod = 11
Pow = 12
LShift = 13
RShift = 14
BitAnd = 15
BitOr = 16
BitXor = 17
Lt = 18
LtE = 19
Eq = 20
NotEq = 21
Gt = 22
GtE = 23
And = 24
Or = 25
MatMul = 26
_BinaryEnd = 27
_SpecialStart = 28
IfThenElse = 29
_SpecialEnd = 30
# pylint: enable=invalid-name
@register_object("script.printer.OperationDoc")
class OperationDoc(ExprDoc):
"""
Doc that represents operation
It can be unary, binary and other special operators (for example, the
if-then-else expression).
"""
kind: OperationKind
operands: Sequence[ExprDoc]
def __init__(self, kind: OperationKind, operands: list[ExprDoc]):
self.__init_handle_by_constructor__(_ffi_api.OperationDoc, kind, operands) # type: ignore # pylint: disable=no-member
@register_object("script.printer.LambdaDoc")
class LambdaDoc(ExprDoc):
"""Doc that represents lambda function"""
args: Sequence[IdDoc]
body: ExprDoc
def __init__(self, args: list[IdDoc], body: ExprDoc):
self.__init_handle_by_constructor__(_ffi_api.LambdaDoc, args, body) # type: ignore # pylint: disable=no-member
@register_object("script.printer.TupleDoc")
class TupleDoc(ExprDoc):
"""Doc that represents tuple literal"""
elements: Sequence[ExprDoc]
def __init__(self, elements: list[ExprDoc]):
self.__init_handle_by_constructor__(_ffi_api.TupleDoc, elements) # type: ignore # pylint: disable=no-member
@register_object("script.printer.ListDoc")
class ListDoc(ExprDoc):
"""Doc that represents list literal"""
elements: Sequence[ExprDoc]
def __init__(self, elements: list[ExprDoc]):
self.__init_handle_by_constructor__(_ffi_api.ListDoc, elements) # type: ignore # pylint: disable=no-member
@register_object("script.printer.DictDoc")
class DictDoc(ExprDoc):
"""Doc that represents dict literal"""
keys: Sequence[ExprDoc]
values: Sequence[ExprDoc]
def __init__(self, content: dict[ExprDoc, ExprDoc]):
keys = list(content.keys())
values = list(content.values())
self.__init_handle_by_constructor__(_ffi_api.DictDoc, keys, values) # type: ignore # pylint: disable=no-member
@register_object("script.printer.SliceDoc")
class SliceDoc(ExprDoc):
"""
Doc that represents slice in Index expression
This doc can only appear in `IndexDoc.indices`.
"""
start: ExprDoc | None
stop: ExprDoc | None
step: ExprDoc | None
def __init__(
self,
start: ExprDoc | None = None,
stop: ExprDoc | None = None,
step: ExprDoc | None = None,
):
self.__init_handle_by_constructor__(_ffi_api.SliceDoc, start, stop, step) # type: ignore # pylint: disable=no-member
@register_object("script.printer.AssignDoc")
class AssignDoc(StmtDoc):
"""Doc that represents assign statement."""
lhs: ExprDoc
rhs: ExprDoc | None
annotation: ExprDoc | None
def __init__(self, lhs: ExprDoc, rhs: ExprDoc | None, annotation: ExprDoc | None = None):
self.__init_handle_by_constructor__(
_ffi_api.AssignDoc, # type: ignore # pylint: disable=no-member
lhs,
rhs,
annotation,
)
@register_object("script.printer.IfDoc")
class IfDoc(StmtDoc):
"""Doc that represent if-then-else statement."""
predicate: ExprDoc
then_branch: Sequence[StmtDoc]
else_branch: Sequence[StmtDoc]
def __init__(self, predicate: ExprDoc, then_branch: list[StmtDoc], else_branch: list[StmtDoc]):
self.__init_handle_by_constructor__(
_ffi_api.IfDoc, # type: ignore # pylint: disable=no-member
predicate,
then_branch,
else_branch,
)
@register_object("script.printer.WhileDoc")
class WhileDoc(StmtDoc):
"""Doc that represents while statement."""
predicate: ExprDoc
body: Sequence[StmtDoc]
def __init__(self, predicate: ExprDoc, body: list[StmtDoc]):
self.__init_handle_by_constructor__(_ffi_api.WhileDoc, predicate, body) # type: ignore # pylint: disable=no-member
@register_object("script.printer.ForDoc")
class ForDoc(StmtDoc):
"""Doc that represents for statement."""
lhs: ExprDoc
rhs: ExprDoc
body: Sequence[StmtDoc]
def __init__(self, lhs: ExprDoc, rhs: ExprDoc, body: list[StmtDoc]):
self.__init_handle_by_constructor__(_ffi_api.ForDoc, lhs, rhs, body) # type: ignore # pylint: disable=no-member
@register_object("script.printer.ScopeDoc")
class ScopeDoc(StmtDoc):
"""
Doc that represents special scopes.
Specifically, this means the with statement in Python:
with <rhs> as <lhs>:
<body...>
"""
lhs: ExprDoc | None
rhs: ExprDoc
body: Sequence[StmtDoc]
def __init__(self, lhs: ExprDoc | None, rhs: ExprDoc, body: list[StmtDoc]):
self.__init_handle_by_constructor__(_ffi_api.ScopeDoc, lhs, rhs, body) # type: ignore # pylint: disable=no-member
@register_object("script.printer.ExprStmtDoc")
class ExprStmtDoc(StmtDoc):
"""Doc that represents an expression as statement."""
expr: ExprDoc
def __init__(self, expr: ExprDoc):
self.__init_handle_by_constructor__(_ffi_api.ExprStmtDoc, expr) # type: ignore # pylint: disable=no-member
@register_object("script.printer.AssertDoc")
class AssertDoc(StmtDoc):
"""Doc that represents assert statement."""
test: ExprDoc
msg: ExprDoc | None
def __init__(self, test: ExprDoc, msg: ExprDoc | None = None):
self.__init_handle_by_constructor__(_ffi_api.AssertDoc, test, msg) # type: ignore # pylint: disable=no-member
@register_object("script.printer.ReturnDoc")
class ReturnDoc(StmtDoc):
"""Doc that represents return statement."""
value: ExprDoc
def __init__(self, value: ExprDoc):
self.__init_handle_by_constructor__(_ffi_api.ReturnDoc, value) # type: ignore # pylint: disable=no-member
@register_object("script.printer.FunctionDoc")
class FunctionDoc(StmtDoc):
"""Doc that represents function definition."""
name: IdDoc
args: Sequence[AssignDoc]
decorators: Sequence[ExprDoc]
return_type: ExprDoc | None
body: Sequence[StmtDoc]
def __init__(
self,
name: IdDoc,
args: list[AssignDoc],
decorators: list[ExprDoc],
return_type: ExprDoc | None,
body: list[StmtDoc],
):
self.__init_handle_by_constructor__(
_ffi_api.FunctionDoc, # type: ignore # pylint: disable=no-member
name,
args,
decorators,
return_type,
body,
)
@register_object("script.printer.ClassDoc")
class ClassDoc(StmtDoc):
"""Doc that represents class definition."""
name: IdDoc
decorators: Sequence[ExprDoc]
body: Sequence[StmtDoc]
def __init__(self, name: IdDoc, decorators: list[ExprDoc], body: list[StmtDoc]):
self.__init_handle_by_constructor__(
_ffi_api.ClassDoc, # type: ignore # pylint: disable=no-member
name,
decorators,
body,
)
@register_object("script.printer.CommentDoc")
class CommentDoc(StmtDoc):
"""Doc that represents comment."""
def __init__(self, comment: str):
self.__init_handle_by_constructor__(
_ffi_api.CommentDoc,
comment, # type: ignore # pylint: disable=no-member
)
@register_object("script.printer.DocStringDoc")
class DocStringDoc(StmtDoc):
"""Doc that represents docstring."""
def __init__(self, docs: str):
self.__init_handle_by_constructor__(
_ffi_api.DocStringDoc,
docs, # type: ignore # pylint: disable=no-member
)