blob: 43cf0ccc2e2ea4216ba310c94725e7bc2cf9f5d6 [file]
.. 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.
.. _tvmscript-arch:
TVMScript
=========
TVMScript expresses TVM IR with Python syntax. A shared frontend translates source
into a Python builder program; executing that program constructs IR. A separate
printer converts IR back into readable, parseable source.
.. code-block:: text
Source + definition context
→ Python AST → syntax transpiler → Python builder program → TVM IR
TVM IR
→ DocTranslator → Doc tree + origins → diagnostic paths → Python text
Source and frontend
-------------------
``tvm.script.parser`` accepts Python functions, classes and source strings through
public decorators, ``tvm.script.parse`` and ``tvm.script.from_source``. The frontend
acquires source and locations, captures the definition's globals and closure bindings,
and composes the generated callable with that environment. String callers provide
external bindings through ``extra_vars``.
Standalone function decorators construct IR immediately. Within an IR-module class,
function decorators retain definitions until module construction, which can declare
signatures before building bodies. Captured Python values belong to the source
context; symbolic IR values are created and resolved by builders.
Function construction, JIT and macro decorators require ``@`` application at the
function definition site. Later application to an existing function is rejected.
Use a registered namespace, such as ``@T.function`` or ``@Ts.function(private=True)``.
Python definitions support namespace aliases such as ``Alias = T``; bare callable
aliases and preconfigured decorator aliases are unsupported. Source strings resolve
namespace aliases from imports or ``extra_vars``, not executable prefix assignments.
``GeneratedBuilder`` records connect generated body helpers to original functions and
identify bindings preserved during recomposition. Original-name wrapper parameters
capture definition values as defaults. Execution globals remain separate from the
locals that evaluate those defaults. Declaration and annotation helpers bind snapshot
values under their original names, so Python handles parameter and local shadowing.
Missing snapshot values raise only when their annotation expressions read them.
Class setup and declaration snapshots execute in source order before function bodies.
Body globals, closures and local shadowing retain their Python scope. Missing values
are read only when needed, including conditional and optional annotations.
Annotations read concrete values from their definition scope, preserving missing-name
errors when a value is used. Create external symbols with ``n = I.dynamic("n")``
or the identical ``T.dynamic`` and ``Ts.dynamic`` constructors. Each call creates a
fresh native variable, defaulting to int32. Reuse that object in ordinary Python shape,
stride and offset expressions to share identity; strings in those fields are not
parsed as expressions. On Python 3.12+, explicit headers such as
``def f[n, k: T.int64](...)`` declare local symbols; ``n: int`` retains the int32
default. Relax symbolic shapes require explicit int64 symbols, such as
``I.dynamic("n", "int64")`` or ``def f[n: T.int64](...)``; Relax integer shape
constants continue to normalize to int64. Use ``from __future__ import annotations``
to defer eager Python evaluation of header symbols. Explicit whole quoted annotations are rejected in script source;
normal string arguments inside annotation constructors remain valid. Captured runtime ``typing.TypeVar``
objects are not script symbols; ordinary Python typing uses remain unaffected.
An explicit scalar annotation ``n: n`` preserves a captured native symbol's identity.
An independently typed parameter such as ``n: T.int32`` and ordinary body locals
shadow definition captures normally. Annotation classes, Python unions and deferred
return-constructor evaluation retain their builder behavior.
Syntax and construction protocol
--------------------------------
The syntax transpiler rewrites a fresh Python AST using scope and declaration facts
from a prescan. A registered decorator selects the construction namespace. Assignments
call binding hooks, expression statements call emission hooks, and control flow opens
builder frames. The namespace owns the meaning of these operations and the supported
IR constructs. Standalone decorators pass already-evaluated ``root_function_kwargs``
so option expressions execute once. Nested functions and module members use their own
decorator options. Builder hooks own option defaults; ``check_well_formed`` remains a
separate parser setting for standalone functions. Set it to ``False`` on a function
decorator to skip that function's validation. ``@I.ir_module`` constructs a module
without automatic whole-module validation. Validate completed modules explicitly
with the language's ``analysis.check_well_formed(module)`` API. These checks raise on
invalid IR by default; TIRx also supports ``assert_mode=False`` for a boolean result.
``tvm.script.parser.protocol_registry`` records syntax policies under registered
canonical namespace paths rooted at ``tirx``, ``relax``, ``ir`` and ``s_tir``.
Source code selects its own aliases through imports or explicit environments; the parser
does not provide implicit ``T``, ``R``, ``I`` or ``Ts`` bindings. For example,
``from tvm.script import tirx as X`` makes ``X.function`` resolve to
``tirx.function`` without changing the callable. Printed scripts show suggested imports
as comments: include those imports in executable source or pass equivalent
``extra_vars`` when parsing a printed script. The public registration helpers
identify scalar annotations and mutable
declarations. Entry factories populate ``DEFINITION_KIND`` with ``DefinitionKind``
values: ``FUNCTION`` for regular and JIT IR definitions, ``MACRO`` for inline and macro
expansion, and ``PYTHON`` for retained ``I.pyfunc`` runtime callables. Macro bodies
construct IR in the caller's context; Python bodies retain ordinary execution and do
not need builder loc context.
All calls retain their source context, with binding hooks owning result attachment.
Symbolic shapes use concrete expressions. Source aliases resolve to those paths;
ordinary Python calls remain calls in the generated program. Explicit ``constexpr``
markers from the shared builder select host control flow during construction.
JIT supplies ``const_args``, a mapping from parameter names to fixed values. Explicit
``None`` values mark absent optional arguments and skip their annotation evaluation.
Generated code reads the mapping through a fresh ``_const_args`` name. An empty map
still selects root JIT construction; an absent map selects ordinary parsing. Syntax
translation does not inspect these values to select a branch.
Generated operations retain source locations. Syntax restrictions raise source-located
``SyntaxError`` exceptions; builder and Python helper errors retain their original
exception types. Temporary parse state is released when construction finishes or fails.
Frame-based construction
------------------------
The IR builder maintains an active stack of frames, such as module, function and loop
frames. Entering a frame establishes its scope; exiting finalizes its IR and attaches it
to its parent. Frames own parameters, symbols and construction state. Builder hooks own
value binding, type rules and validation of completed IR. The shared
``resolve_global_info_args`` decorator resolves named metadata arguments after
ordinary Python argument evaluation; dialect callbacks own selector syntax and
lookup in the active module.
Public script namespaces expose decorators and construction operations. The same
underlying builders can also be used directly from Python. The transpiler therefore
needs no separate mutable IR representation: it generates calls to this construction
protocol. ``script.namespace`` records the construction namespace for printing
and dialect lowering. S-TIR functions use ``"s_tir"``; their builders own block
completion and validation.
Ordinary IR constructors can be used directly in parsed source. Shared exports
such as ``I.Call`` and ``T.Range`` use the same constructor contracts as
``tvm.ir.Call`` and ``tvm.ir.Range``, including keyword arguments, source locations
and validation. ``Call(op, args, ..., ty=ty)`` preserves an explicit result type, including
``Type.missing()``. Omitting ``ty`` or passing ``ty=None`` uses available inference and retains
``Type.missing()`` when no deduction is available; inference errors propagate.
Construction permits provisional IR, and ``call.validate()`` checks the registered
operator contract separately. Raw printed calls use ``I.Call`` with an explicit
stored result type to preserve every field. Canonical named operations use the same
``args``, ``attrs``, ``ty_args`` and ``ty`` construction contract. Concrete printer
hooks handle representation-changing syntax; ordinary calls share one formatter.
Operations likewise retain their normal argument contracts. A dtype inferred from
operands is not an extra ``dtype`` keyword; operations with an explicit dtype
parameter accept it normally. Annotation shorthand, module metadata selectors,
frame construction and variadic dtype positioning remain explicit builder adapters.
They do not change the underlying IR constructor's validation or consult builder
state from ordinary construction.
Printing and round trips
------------------------
``DocTranslator`` invokes native type and operation hooks to produce a ``Doc`` tree
of expressions and statements. The translation engine tracks scopes, names and each
Doc's original IR object. The script entry point maps these origins to diagnostic
paths; the private Doc printer formats the tree, annotations and underlines as Python
text. Printer configuration stays read-only throughout. The Python document printer
helper also formats an existing Doc directly for document-level tests. This tree is
separate from the parser's Python AST.
The public ``tvm::Script`` text entry point is declared in
``tvm/script/printer/printer.h``. Its orchestration and diagnostic path mapping
live in ``src/script/printer/printer.cc``; ``doc_translator.h`` exposes the
IR-to-Doc translation protocol.
For example, a small function can be authored, printed and parsed again:
.. code-block:: python
import tvm
from tvm.script import tirx as T
@T.function
def increment(A: T.Tensor((4,), "float32")):
for i in T.serial(4):
A[i] = A[i] + 1.0
text = increment.script()
reparsed = tvm.script.from_source(text, extra_vars={"T": T})
tvm.ir.assert_structural_equal(increment, reparsed)
Printed source uses canonical forms rather than preserving the original spelling.
Round trips require printable IR and any external objects needed by the source;
the text does not serialize arbitrary Python state.
Namespace extension
-------------------
A language variant supplies construction hooks and a public script namespace, then
registers its aliases with ``register_namespace``. ``register_namespace_initializer``
supports lazy setup. ``tvm.script.register_dialect`` exposes a package through the
public script namespace. Syntax policies are registered beside the operations that
need them. IR printing uses ``FDocTranslate`` hooks from
``tvm/script/printer/doc_translator.h``, registered through the existing type or
operation attributes. A hook returns an expression Doc or emits completed statements
through its translator context.
This division keeps source acquisition, syntax translation and formatting shared.
Language-specific construction and validation remain with the namespace and its
builders.