This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new c584f233ce [IR][TVMScript] Share DataTypeImm values across script 
dialects (#20462)
c584f233ce is described below

commit c584f233ce60536c8ebe7a6099a41051b80b58d4
Author: Tianqi Chen <[email protected]>
AuthorDate: Sat Sep 26 10:03:33 2026 -0400

    [IR][TVMScript] Share DataTypeImm values across script dialects (#20462)
    
    ## Summary
    
    DataType values are ordinary expression arguments, so they now use a
    shared `DataTypeImm` constant with a typed `DLDataType` value. The node
    owns its root `AnyType` expression type and supports direct C++ value
    extraction, reflection, structural operations, and serialization.
    
    The shared `dtype` builder constructs `DataTypeImm` across the `I`, `T`,
    and `R` script namespaces. Printing uses `T.dtype(...)` by default,
    including Relax functions, and `I.dtype(...)` in shared IR module
    contexts. `R.dtype` remains accepted as input; scalar constructors keep
    their existing meaning. Non-dtype `GenericConst` payloads retain their
    behavior.
    
    Relax allocation, view, and VM paths consume typed dtype values. TIRx
    traversal admits the shared literal as an operation argument. `AnyType`
    remains in the root IR type layer, with Relax aliases preserved.
---
 docs/reference/api/python/relax/relax.rst          |  2 +-
 docs/reference/api/python/script/ir_builder.rst    |  2 +-
 include/tvm/ir/expr.h                              | 22 ++++++++-
 include/tvm/ir/expr_functor.h                      |  6 +++
 include/tvm/ir/type.h                              | 23 ++++++++++
 include/tvm/relax/expr_functor.h                   |  6 +++
 include/tvm/relax/type.h                           | 32 +++----------
 python/tvm/ir/__init__.py                          |  3 +-
 python/tvm/ir/expr.py                              | 18 ++++++++
 python/tvm/ir/type.py                              |  8 ++++
 python/tvm/relax/expr_functor.py                   | 44 +++++++++++++++++-
 python/tvm/relax/op/builtin/builtin.py             |  7 ++-
 python/tvm/relax/op/memory/memory.py               | 11 ++---
 python/tvm/relax/op/memory/view.py                 | 13 ++----
 python/tvm/relax/op/vm/vm.py                       | 11 ++---
 python/tvm/relax/script/ir_builder/ir.py           | 17 +------
 python/tvm/relax/testing/ast_printer.py            |  3 ++
 python/tvm/relax/type.py                           | 11 +----
 python/tvm/script/ir_builder/__init__.py           |  5 +-
 python/tvm/script/ir_builder/ir.py                 | 12 ++++-
 python/tvm/tirx/script/ir_builder/__init__.py      |  2 +
 src/ir/expr.cc                                     | 53 ++++++++++++++++++++++
 src/ir/expr_functor.cc                             |  9 ++++
 src/ir/type.cc                                     | 17 +++++++
 src/relax/backend/vm/codegen_vm.cc                 |  3 ++
 src/relax/backend/vm/codegen_vm_tir.cc             |  3 ++
 src/relax/backend/vm/lower_runtime_builtin.cc      |  4 +-
 src/relax/backend/vm/vm_shape_lower.cc             |  8 ++--
 src/relax/ir/block_builder.cc                      |  1 +
 src/relax/ir/dependent_type.cc                     | 35 --------------
 src/relax/ir/expr_functor.cc                       |  5 ++
 src/relax/ir/py_expr_functor.cc                    | 33 ++++++++++----
 src/relax/op/memory/view.cc                        | 12 ++---
 src/relax/op/op.cc                                 | 50 ++++++++++----------
 src/relax/op/op_common.h                           |  6 +--
 src/relax/script/ir_builder/frame.cc               |  2 +-
 src/relax/script/printer/dependent_type.cc         |  8 ++--
 src/relax/script/printer/expr.cc                   |  7 +--
 src/relax/script/printer/function.cc               |  2 +-
 src/relax/transform/allocate_workspace.cc          |  2 +-
 src/relax/transform/call_tir_rewrite.cc            |  2 +-
 src/relax/transform/fuse_ops.cc                    |  3 +-
 src/relax/transform/lower_alloc_tensor.cc          | 10 ++--
 src/relax/transform/rewrite_cuda_graph.cc          |  4 +-
 src/relax/transform/static_plan_block_memory.cc    | 18 ++++----
 src/script/printer/ir/misc.cc                      | 33 ++++++++++++++
 src/tirx/ir/function.cc                            |  4 +-
 src/tirx/ir/tir_visitor_with_path.cc               |  2 +
 src/tirx/ir/tir_visitor_with_path.h                |  6 +++
 .../relax/script/test_relax_script_printer.py      |  4 +-
 tests/python/relax/test_ast_printer.py             |  4 +-
 tests/python/relax/test_expr.py                    |  4 +-
 tests/python/script/test_basic_usage.py            | 13 +++++-
 53 files changed, 419 insertions(+), 206 deletions(-)

diff --git a/docs/reference/api/python/relax/relax.rst 
b/docs/reference/api/python/relax/relax.rst
index 7ae3359987..a20975cf7a 100644
--- a/docs/reference/api/python/relax/relax.rst
+++ b/docs/reference/api/python/relax/relax.rst
@@ -21,4 +21,4 @@ tvm.relax
 .. automodule:: tvm.relax
     :members:
     :imported-members:
-    :exclude-members: BlockBuilder, Call, Tuple, TupleGetItem, Var, Span, 
GlobalVar, SourceName, TupleType, Type, FuncType
+    :exclude-members: AnyType, ObjectType, BlockBuilder, Call, Tuple, 
TupleGetItem, Var, Span, GlobalVar, SourceName, TupleType, Type, FuncType
diff --git a/docs/reference/api/python/script/ir_builder.rst 
b/docs/reference/api/python/script/ir_builder.rst
index 53f6041e09..b17ba6de31 100644
--- a/docs/reference/api/python/script/ir_builder.rst
+++ b/docs/reference/api/python/script/ir_builder.rst
@@ -23,7 +23,7 @@ tvm.script.ir_builder
 .. automodule:: tvm.script.ir_builder
    :members:
    :imported-members:
-   :exclude-members: GenericConst, Range, StringImm, StringType
+   :exclude-members: DataTypeImm, GenericConst, Range, StringImm, StringType
 
 tvm.relax.script.ir_builder.distributed
 ***************************************
diff --git a/include/tvm/ir/expr.h b/include/tvm/ir/expr.h
index dea7902280..7fa6dbf532 100644
--- a/include/tvm/ir/expr.h
+++ b/include/tvm/ir/expr.h
@@ -517,7 +517,7 @@ class Call : public Expr {
 /*! \brief Base node for literal constants. */
 class ConstantNode : public ExprNode {
  public:
-  static constexpr uint32_t _type_child_slots = 4;
+  static constexpr uint32_t _type_child_slots = 5;
   static void RegisterReflection() { 
ffi::reflection::ObjectDef<ConstantNode>(); }
   TVM_FFI_DECLARE_OBJECT_INFO("ir.Constant", ConstantNode, ExprNode);
 };
@@ -568,6 +568,26 @@ class StringImm : public Constant {
   TVM_DEFINE_OBJECT_REF_COW_METHOD(StringImmNode);
 };
 
+/*! \brief A data type literal with AnyType as its expression type. */
+class DataTypeImmNode : public ConstantNode {
+ public:
+  DLDataType value;
+
+  static void RegisterReflection() {
+    ffi::reflection::ObjectDef<DataTypeImmNode>().def_ro("value", 
&DataTypeImmNode::value);
+  }
+  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.DataTypeImm", DataTypeImmNode, 
ConstantNode);
+};
+
+/*! \brief Managed reference to a data type literal. */
+class DataTypeImm : public Constant {
+ public:
+  TVM_DLL explicit DataTypeImm(DLDataType value, Span span = Span());
+
+  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DataTypeImm, Constant, 
DataTypeImmNode);
+  TVM_DEFINE_OBJECT_REF_COW_METHOD(DataTypeImmNode);
+};
+
 /*!
  * \brief Constant integer literals in the program.
  * \sa IntImm
diff --git a/include/tvm/ir/expr_functor.h b/include/tvm/ir/expr_functor.h
index a3b7671643..cc735b4db2 100644
--- a/include/tvm/ir/expr_functor.h
+++ b/include/tvm/ir/expr_functor.h
@@ -100,6 +100,9 @@ class ExprFunctor<R(const Expr&, Args...)> {
   virtual R Dispatch_(const GenericConstNode* node, Args... args) {
     return DispatchDefault_(node, std::forward<Args>(args)...);
   }
+  virtual R Dispatch_(const DataTypeImmNode* node, Args... args) {
+    return DispatchDefault_(node, std::forward<Args>(args)...);
+  }
   virtual R Dispatch_(const IntImmNode* node, Args... args) {
     return DispatchDefault_(node, std::forward<Args>(args)...);
   }
@@ -236,6 +239,7 @@ class ExprFunctor<R(const Expr&, Args...)> {
     SetDispatch<TSelf, GlobalVarNode>(vtable);
     SetDispatch<TSelf, CallNode>(vtable);
     SetDispatch<TSelf, GenericConstNode>(vtable);
+    SetDispatch<TSelf, DataTypeImmNode>(vtable);
     SetDispatch<TSelf, IntImmNode>(vtable);
     SetDispatch<TSelf, FloatImmNode>(vtable);
     SetDispatch<TSelf, OpNode>(vtable);
@@ -343,6 +347,7 @@ class TVM_DLL ExprVisitor : public ObjectVisitor {
   virtual ffi::Optional<VisitInterrupt> Visit_(const GlobalVarNode* node);
   virtual ffi::Optional<VisitInterrupt> Visit_(const CallNode* node);
   virtual ffi::Optional<VisitInterrupt> Visit_(const GenericConstNode* node);
+  virtual ffi::Optional<VisitInterrupt> Visit_(const DataTypeImmNode* node);
   virtual ffi::Optional<VisitInterrupt> Visit_(const IntImmNode* node);
   virtual ffi::Optional<VisitInterrupt> Visit_(const FloatImmNode* node);
   virtual ffi::Optional<VisitInterrupt> Visit_(const OpNode* node);
@@ -468,6 +473,7 @@ class TVM_DLL ExprMutator : public ObjectMutator {
   virtual UnchangedOr<Expr> Mutate_(const GlobalVarNode* node, InplaceMode 
inplace_mode);
   virtual UnchangedOr<Expr> Mutate_(const CallNode* node, InplaceMode 
inplace_mode);
   virtual UnchangedOr<Expr> Mutate_(const GenericConstNode* node, InplaceMode 
inplace_mode);
+  virtual UnchangedOr<Expr> Mutate_(const DataTypeImmNode* node, InplaceMode 
inplace_mode);
   virtual UnchangedOr<PrimExpr> Mutate_(const IntImmNode* node, InplaceMode 
inplace_mode);
   virtual UnchangedOr<PrimExpr> Mutate_(const FloatImmNode* node, InplaceMode 
inplace_mode);
   virtual UnchangedOr<Expr> Mutate_(const OpNode* node, InplaceMode 
inplace_mode);
diff --git a/include/tvm/ir/type.h b/include/tvm/ir/type.h
index 0c148f1095..21132604c4 100644
--- a/include/tvm/ir/type.h
+++ b/include/tvm/ir/type.h
@@ -34,6 +34,29 @@
 
 namespace tvm {
 
+/*!
+ * \brief The top type, which admits any value.
+ *
+ * AnyType types values that have no more specific static type, such as
+ * DataType-valued generic constants and opaque runtime objects.
+ */
+class AnyTypeNode : public TypeNode {
+ public:
+  static void RegisterReflection() { 
ffi::reflection::ObjectDef<AnyTypeNode>(); }
+  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.AnyType", AnyTypeNode, TypeNode);
+};
+
+/*!
+ * \brief Managed reference to AnyTypeNode.
+ * \sa AnyTypeNode
+ */
+class AnyType : public Type {
+ public:
+  TVM_DLL AnyType(Span span = Span());
+
+  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(AnyType, Type, AnyTypeNode);
+};
+
 /*! \brief Semantic string type; its physical representation is context 
dependent. */
 class StringTypeNode : public TypeNode {
  public:
diff --git a/include/tvm/relax/expr_functor.h b/include/tvm/relax/expr_functor.h
index 6ae7d50610..cc722a6d8d 100644
--- a/include/tvm/relax/expr_functor.h
+++ b/include/tvm/relax/expr_functor.h
@@ -163,6 +163,7 @@ class ExprFunctor<R(const Expr& n, Args...)> {
   virtual R VisitExpr_(const OpNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
   virtual R VisitExpr_(const TupleGetItemNode* op, Args... args) 
EXPR_FUNCTOR_DEFAULT;
   virtual R VisitExpr_(const StringImmNode* op, Args... args) 
EXPR_FUNCTOR_DEFAULT;
+  virtual R VisitExpr_(const DataTypeImmNode* op, Args... args) 
EXPR_FUNCTOR_DEFAULT;
   virtual R VisitExpr_(const prim::LetNode* op, Args...) EXPR_FUNCTOR_DISABLED;
   virtual R VisitExpr_(const prim::AddNode* op, Args... args) 
EXPR_FUNCTOR_DEFAULT;
   virtual R VisitExpr_(const prim::LShiftNode* op, Args... args) 
EXPR_FUNCTOR_DEFAULT;
@@ -251,6 +252,7 @@ class ExprFunctor<R(const Expr& n, Args...)> {
     RELAX_EXPR_FUNCTOR_DISPATCH(OpNode);
     RELAX_EXPR_FUNCTOR_DISPATCH(TupleGetItemNode);
     RELAX_EXPR_FUNCTOR_DISPATCH(StringImmNode);
+    RELAX_EXPR_FUNCTOR_DISPATCH(DataTypeImmNode);
     vtable.Finalize();
     return vtable;
   }
@@ -285,6 +287,7 @@ class ExprVisitor : public ExprFunctor<void(const Expr&)> {
   void VisitExpr_(const OpNode* op) override;
   void VisitExpr_(const TupleGetItemNode* op) override;
   void VisitExpr_(const StringImmNode* op) override;
+  void VisitExpr_(const DataTypeImmNode* op) override;
   void VisitExpr_(const prim::AddNode* op) override;
   void VisitExpr_(const prim::LShiftNode* op) override;
   void VisitExpr_(const prim::RShiftNode* op) override;
@@ -341,6 +344,7 @@ class ExprVisitor : public ExprFunctor<void(const Expr&)> {
   virtual void VisitBinding_(const VarBindingNode* binding, const 
TupleGetItemNode* val);
   virtual void VisitBinding_(const VarBindingNode* binding, const ExprNode* 
val);
   virtual void VisitBinding_(const VarBindingNode* binding, const 
StringImmNode* val);
+  virtual void VisitBinding_(const VarBindingNode* binding, const 
DataTypeImmNode* val);
   /*!
    * \brief Generic dispatcher for binding blocks.
    * \param block The binding block to be visited.
@@ -443,6 +447,7 @@ class ExprMutatorBase : public ExprFunctor<Expr(const 
Expr&)> {
   Expr VisitExpr_(const OpNode* op) override;
   Expr VisitExpr_(const TupleGetItemNode* op) override;
   Expr VisitExpr_(const StringImmNode* op) override;
+  Expr VisitExpr_(const DataTypeImmNode* op) override;
   Expr VisitExpr_(const prim::AddNode* op) override;
   Expr VisitExpr_(const prim::LShiftNode* op) override;
   Expr VisitExpr_(const prim::RShiftNode* op) override;
@@ -594,6 +599,7 @@ class ExprMutator : public ExprMutatorBase {
   virtual void VisitBinding_(const VarBindingNode* binding, const 
TupleGetItemNode* val);
   virtual void VisitBinding_(const VarBindingNode* binding, const ExprNode* 
val);
   virtual void VisitBinding_(const VarBindingNode* binding, const 
StringImmNode* val);
+  virtual void VisitBinding_(const VarBindingNode* binding, const 
DataTypeImmNode* val);
   /*!
    * \brief Generic dispatcher for binding blocks.
    * \param block The binding block to be visited.
diff --git a/include/tvm/relax/type.h b/include/tvm/relax/type.h
index cb3c0e8196..5b3b652024 100644
--- a/include/tvm/relax/type.h
+++ b/include/tvm/relax/type.h
@@ -46,9 +46,15 @@ class BlockBuilder;
 /*! \brief Indicates the number of dimensions of a tensor is unknown at 
compile time. */
 static constexpr int kUnknownNDim = -1;
 
+using tvm::AnyType;
+using tvm::AnyTypeNode;
 using tvm::TupleType;
 using tvm::TupleTypeNode;
 
+// Compatibility aliases for existing C++ callers.  New code should use 
AnyType.
+using ObjectTypeNode = AnyTypeNode;
+using ObjectType = AnyType;
+
 class PackedFuncTypeNode : public TypeNode {
  public:
   static void RegisterReflection() {
@@ -89,32 +95,6 @@ class PackedFuncType : public Type {
  * normalized through NormalizeArg.  This invariant is checked in constructors
  * and simplifies assumptions during type deduction.
  */
-/*!
- * \brief Any Relax value.
- */
-class AnyTypeNode : public TypeNode {
- public:
-  static void RegisterReflection() {
-    namespace refl = tvm::ffi::reflection;
-    refl::ObjectDef<AnyTypeNode>();
-  }
-  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.AnyType", AnyTypeNode, TypeNode);
-};
-
-/*!
- * \brief Managed reference to AnyTypeNode.
- * \sa AnyTypeNode
- */
-class AnyType : public Type {
- public:
-  TVM_DLL AnyType(Span span = Span());
-
-  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(AnyType, Type, AnyTypeNode);
-};
-
-// Compatibility aliases for existing C++ callers.  New code should use 
AnyType.
-using ObjectTypeNode = AnyTypeNode;
-using ObjectType = AnyType;
 
 /*!
  * \brief Type of shape value.
diff --git a/python/tvm/ir/__init__.py b/python/tvm/ir/__init__.py
index 17e2c7f147..de84cecf66 100644
--- a/python/tvm/ir/__init__.py
+++ b/python/tvm/ir/__init__.py
@@ -33,11 +33,12 @@ from .base import (
 
 # Register Type before Expr.  Expr's reflected ``ty`` field otherwise creates
 # an auto-generated Type wrapper before the concrete Python class is available.
-from .type import FuncType, OpaqueType, PointerType, PrimType, StringType, 
TupleType, Type
+from .type import AnyType, FuncType, OpaqueType, PointerType, PrimType, 
StringType, TupleType, Type
 from .expr import (
     Call,
     Constant,
     GenericConst,
+    DataTypeImm,
     StringImm,
     Expr,
     ExprOperand,
diff --git a/python/tvm/ir/expr.py b/python/tvm/ir/expr.py
index 7cd7680ea8..42b8f258da 100644
--- a/python/tvm/ir/expr.py
+++ b/python/tvm/ir/expr.py
@@ -463,6 +463,24 @@ class GenericConst(_ExprCallable, Constant):
         return True
 
 
+@tvm_ffi.register_object("ir.DataTypeImm")
+class DataTypeImm(Constant):
+    """A data type literal whose expression type is AnyType.
+
+    Parameters
+    ----------
+    value : str or tvm.DataType
+        The represented data type.
+    span : Span, optional
+        The source span of the literal.
+    """
+
+    value: tvm.DataType
+
+    def __init__(self, value: str | tvm.DataType, span: Span | None = None) -> 
None:
+        self.__init_handle_by_constructor__(_ffi_api.DataTypeImm, value, span)
+
+
 @tvm_ffi.register_object("ir.StringImm")
 class StringImm(Constant):
     """A string literal with StringType."""
diff --git a/python/tvm/ir/type.py b/python/tvm/ir/type.py
index 43060f95a6..e0fbf6cd09 100644
--- a/python/tvm/ir/type.py
+++ b/python/tvm/ir/type.py
@@ -54,6 +54,14 @@ class Type(Node, Scriptable):
         return self.is_(other)
 
 
+@tvm_ffi.register_object("ir.AnyType")
+class AnyType(Type):
+    """The top type, which admits any value."""
+
+    def __init__(self, span=None) -> None:
+        self.__init_handle_by_constructor__(_ffi_api.AnyType, span)
+
+
 @tvm_ffi.register_object("ir.OpaqueType")
 class OpaqueType(Type):
     """Type marker for opaque values that must be removed from finished IR."""
diff --git a/python/tvm/relax/expr_functor.py b/python/tvm/relax/expr_functor.py
index ec7a773958..a2ba021939 100644
--- a/python/tvm/relax/expr_functor.py
+++ b/python/tvm/relax/expr_functor.py
@@ -23,7 +23,7 @@ from collections.abc import Callable
 import tvm_ffi
 
 from tvm import tirx as _tirx
-from tvm.ir import Call, GenericConst, Op, StringImm, TensorLoad, is_prim_expr
+from tvm.ir import Call, DataTypeImm, GenericConst, Op, StringImm, TensorLoad, 
is_prim_expr
 from tvm.ir.utils import derived_object
 from tvm.runtime import Object
 
@@ -221,6 +221,8 @@ class ExprFunctor:
             ret = self.visit_op_(expr)
         elif isinstance(expr, TupleGetItem):
             ret = self.visit_tuple_getitem_(expr)
+        elif isinstance(expr, DataTypeImm):
+            ret = self.visit_data_type_imm_(expr)
         elif isinstance(expr, StringImm):
             ret = self.visit_string_imm_(expr)
         elif isinstance(expr, _tirx.Let | _tirx.Reduce):
@@ -375,6 +377,9 @@ class ExprFunctor:
     def visit_string_imm_(self, op: StringImm):
         raise NotImplementedError()
 
+    def visit_data_type_imm_(self, op: DataTypeImm):
+        raise NotImplementedError()
+
     def visit_var_binding_(self, binding: VarBinding):
         raise NotImplementedError()
 
@@ -447,6 +452,7 @@ class _PyExprVisitor(tvm_ffi.core.Object):
         f_visit_tuple_getitem_: Callable | None = None,
         f_visit_expr_fallback_: Callable | None = None,
         f_visit_string_imm_: Callable | None = None,
+        f_visit_data_type_imm_: Callable | None = None,
         f_visit_binding: Callable | None = None,
         f_visit_var_binding_: Callable | None = None,
         f_visit_match_cast_: Callable | None = None,
@@ -478,6 +484,7 @@ class _PyExprVisitor(tvm_ffi.core.Object):
             f_visit_tuple_getitem_,
             f_visit_expr_fallback_,
             f_visit_string_imm_,
+            f_visit_data_type_imm_,
             f_visit_binding,
             f_visit_var_binding_,
             f_visit_match_cast_,
@@ -572,6 +579,7 @@ class PyExprVisitor:
             "visit_tuple_getitem_",
             "visit_expr_fallback_",
             "visit_string_imm_",
+            "visit_data_type_imm_",
             "visit_binding",
             "visit_var_binding_",
             "visit_match_cast_",
@@ -829,6 +837,19 @@ class PyExprVisitor:
         # Using self._outer() to ref _PyExprVisitor
         return _ffi_api.ExprVisitorVisitExpr(self._outer(), op)  # type: ignore
 
+    def visit_data_type_imm_(self, op: DataTypeImm) -> None:
+        """Visit DataTypeImm.
+        Users can customized this function to overwrite VisitExpr_(const 
DataTypeImmNode* op)
+        on the C++ side.
+
+        Parameters
+        ----------
+        op : DataTypeImm
+            The DataTypeImm to be visited.
+        """
+        # Using self._outer() to ref _PyExprVisitor
+        return _ffi_api.ExprVisitorVisitExpr(self._outer(), op)  # type: ignore
+
     def visit_var_binding_(self, binding: VarBinding) -> None:
         """Visit VarBinding.
         Users can customized this function to overwrite VisitBinding_(const 
VarBindingNode* binding)
@@ -950,6 +971,7 @@ class _PyExprMutator(Object):
         f_visit_tuple_getitem_: Callable | None = None,
         f_visit_expr_fallback_: Callable | None = None,
         f_visit_string_imm_: Callable | None = None,
+        f_visit_data_type_imm_: Callable | None = None,
         f_visit_binding: Callable | None = None,
         f_visit_var_binding_: Callable | None = None,
         f_visit_match_cast_: Callable | None = None,
@@ -982,6 +1004,7 @@ class _PyExprMutator(Object):
             f_visit_tuple_getitem_,
             f_visit_expr_fallback_,
             f_visit_string_imm_,
+            f_visit_data_type_imm_,
             f_visit_binding,
             f_visit_var_binding_,
             f_visit_match_cast_,
@@ -1092,6 +1115,7 @@ class PyExprMutator:
             "visit_tuple_getitem_",
             "visit_expr_fallback_",
             "visit_string_imm_",
+            "visit_data_type_imm_",
             "visit_binding",
             "visit_var_binding_",
             "visit_match_cast_",
@@ -1443,6 +1467,24 @@ class PyExprMutator:
         # Using self._outer() to ref _PyExprMutator
         return _ffi_api.ExprMutatorVisitExpr(self._outer(), op)  # type: ignore
 
+    def visit_data_type_imm_(self, op: DataTypeImm) -> Expr:
+        """Visit DataTypeImm.
+        Users can customized this function to overwrite VisitExpr_(const 
DataTypeImmNode* op)
+        on the C++ side.
+
+        Parameters
+        ----------
+        op : DataTypeImm
+            The DataTypeImm to be visited.
+
+        Returns
+        -------
+        result : Expr
+            The Expr after transformation
+        """
+        # Using self._outer() to ref _PyExprMutator
+        return _ffi_api.ExprMutatorVisitExpr(self._outer(), op)  # type: ignore
+
     def visit_var_binding_(self, binding: VarBinding) -> None:
         """Visit VarBinding.
         Users can customized this function to overwrite VisitBinding_(const 
VarBindingNode* binding)
diff --git a/python/tvm/relax/op/builtin/builtin.py 
b/python/tvm/relax/op/builtin/builtin.py
index bc5d0db2d7..5592e0d402 100644
--- a/python/tvm/relax/op/builtin/builtin.py
+++ b/python/tvm/relax/op/builtin/builtin.py
@@ -15,8 +15,7 @@
 # specific language governing permissions and limitations
 """The builtin Relax operators."""
 
-import tvm
-from tvm.ir import Call, GenericConst, StringImm
+from tvm.ir import Call, DataTypeImm, GenericConst, StringImm
 
 from ...expr import Expr, prim_value
 from ...utils import convert_to_expr
@@ -53,8 +52,8 @@ def alloc_tensor(
     """
     if not isinstance(shape, Expr):
         shape = convert_to_expr(shape)
-    if isinstance(dtype, str):
-        dtype = GenericConst(tvm.DataType(dtype), tvm.relax.AnyType())
+    if isinstance(dtype, str | GenericConst):
+        dtype = DataTypeImm(dtype.value if isinstance(dtype, GenericConst) 
else dtype)
     if isinstance(runtime_device_index, int):
         runtime_device_index = prim_value(runtime_device_index)
     if isinstance(storage_scope, str):
diff --git a/python/tvm/relax/op/memory/memory.py 
b/python/tvm/relax/op/memory/memory.py
index 7b0e22f78f..2cdcbdfe7b 100644
--- a/python/tvm/relax/op/memory/memory.py
+++ b/python/tvm/relax/op/memory/memory.py
@@ -15,8 +15,7 @@
 # specific language governing permissions and limitations
 """Relax memory primitives."""
 
-import tvm
-from tvm.ir import Call, GenericConst, StringImm
+from tvm.ir import Call, DataTypeImm, GenericConst, StringImm
 
 from ...expr import Expr, prim_value
 from ...utils import convert_to_expr
@@ -53,8 +52,8 @@ def alloc_storage(
         A relax Call, which gets the allocated storage.
     """
     size = convert_to_expr(size)
-    if isinstance(dtype, str):
-        dtype = GenericConst(tvm.DataType(dtype), tvm.relax.AnyType())
+    if isinstance(dtype, str | GenericConst):
+        dtype = DataTypeImm(dtype.value if isinstance(dtype, GenericConst) 
else dtype)
     if isinstance(storage_scope, str):
         storage_scope = StringImm(storage_scope)
     if isinstance(virtual_device_index, int):
@@ -97,8 +96,8 @@ def alloc_tensor(
     if isinstance(offset, int):
         offset = prim_value(offset)
     shape = convert_to_expr(shape)
-    if isinstance(dtype, str):
-        dtype = GenericConst(tvm.DataType(dtype), tvm.relax.AnyType())
+    if isinstance(dtype, str | GenericConst):
+        dtype = DataTypeImm(dtype.value if isinstance(dtype, GenericConst) 
else dtype)
     if isinstance(runtime_device_ind, int):
         runtime_device_ind = prim_value(runtime_device_ind)
     return _ffi_api.alloc_tensor(storage, offset, shape, dtype, 
runtime_device_ind)  # type: ignore
diff --git a/python/tvm/relax/op/memory/view.py 
b/python/tvm/relax/op/memory/view.py
index 5874c1e5e9..b3ecc57842 100644
--- a/python/tvm/relax/op/memory/view.py
+++ b/python/tvm/relax/op/memory/view.py
@@ -28,9 +28,8 @@ while keeping the same underlying data.
 
 from collections.abc import Sequence
 
-from tvm import DataType
-from tvm.ir import GenericConst
-from tvm.relax import AnyType, Expr, ShapeExpr
+from tvm.ir import DataTypeImm, GenericConst
+from tvm.relax import Expr, ShapeExpr
 from tvm.relax.expr import prim_value
 
 from ..base import null_value
@@ -91,11 +90,9 @@ def view(
             return relax_cls(expr)
 
     shape = _normalize(shape, ShapeExpr)
-    dtype = (
-        null_value()
-        if dtype is None
-        else _normalize(dtype, lambda value: GenericConst(DataType(value), 
AnyType()))
-    )
+    if isinstance(dtype, GenericConst):
+        dtype = DataTypeImm(dtype.value)
+    dtype = null_value() if dtype is None else _normalize(dtype, DataTypeImm)
     relative_byte_offset = (
         relative_byte_offset
         if relative_byte_offset is None or isinstance(relative_byte_offset, 
Expr)
diff --git a/python/tvm/relax/op/vm/vm.py b/python/tvm/relax/op/vm/vm.py
index d5c3bf88a6..51c9b4bdda 100644
--- a/python/tvm/relax/op/vm/vm.py
+++ b/python/tvm/relax/op/vm/vm.py
@@ -15,8 +15,7 @@
 # specific language governing permissions and limitations
 """Relax vm primitives."""
 
-import tvm
-from tvm.ir import Call, GenericConst, StringImm
+from tvm.ir import Call, DataTypeImm, GenericConst, StringImm
 
 from ...expr import Expr, Tuple, prim_value
 from ...utils import convert_to_expr
@@ -53,8 +52,8 @@ def alloc_storage(
         A relax Call, which gets the allocated storage.
     """
     shape = convert_to_expr(shape)
-    if isinstance(dtype, str):
-        dtype = GenericConst(tvm.DataType(dtype), tvm.relax.AnyType())
+    if isinstance(dtype, str | GenericConst):
+        dtype = DataTypeImm(dtype.value if isinstance(dtype, GenericConst) 
else dtype)
     if isinstance(storage_scope, str):
         storage_scope = StringImm(storage_scope)
     if isinstance(runtime_device_index, int):
@@ -97,8 +96,8 @@ def alloc_tensor(
     if isinstance(offset, int):
         offset = prim_value(offset)
     shape = convert_to_expr(shape)
-    if isinstance(dtype, str):
-        dtype = GenericConst(tvm.DataType(dtype), tvm.relax.AnyType())
+    if isinstance(dtype, str | GenericConst):
+        dtype = DataTypeImm(dtype.value if isinstance(dtype, GenericConst) 
else dtype)
     if isinstance(runtime_device_ind, int):
         runtime_device_ind = prim_value(runtime_device_ind)
     return _ffi_api.alloc_tensor(storage, offset, shape, dtype, 
runtime_device_ind)  # type: ignore
diff --git a/python/tvm/relax/script/ir_builder/ir.py 
b/python/tvm/relax/script/ir_builder/ir.py
index 481fc74afc..b560176f34 100644
--- a/python/tvm/relax/script/ir_builder/ir.py
+++ b/python/tvm/relax/script/ir_builder/ir.py
@@ -25,8 +25,8 @@ import numbers as _numbers
 import re as _re
 
 import tvm
-from tvm import DataType, relax
 from tvm import ir as _ir
+from tvm import relax
 from tvm import relax as _relax
 from tvm.ir import IRModule
 from tvm.relax import Expr, ExternFunc, ShapeExpr, TupleGetItem, const
@@ -52,6 +52,7 @@ from tvm.runtime._tensor import (
 from tvm.script.ir_builder.base import SpanEntry as _SpanEntry
 from tvm.script.ir_builder.base import resolve_global_info_args as 
_resolve_global_info_args
 from tvm.script.ir_builder.ir import _global_infos
+from tvm.script.ir_builder.ir import dtype as dtype
 from tvm.script.ir_builder.parser_protocol import resolve_global_info_ as 
_resolve_global_info
 
 from . import _ffi_api
@@ -291,20 +292,6 @@ def str(value: py_str) -> Expr:
     return tvm.ir.StringImm(value)  # type: ignore[attr-defined] # pylint: 
disable=no-member
 
 
-def dtype(value: py_str | DataType) -> Expr:
-    """Create a dtype imm expression.
-    Parameters
-    ----------
-    value : dtype
-        The value of the dtype.
-    Returns
-    -------
-    res : Expr
-        The result dtype.
-    """
-    return tvm.ir.GenericConst(tvm.DataType(value), tvm.relax.AnyType())  # 
type: ignore[attr-defined] # pylint: disable=no-member
-
-
 @_resolve_global_info_args("vdevice", resolver=resolve_global_info_)
 def Tensor(shape=None, dtype=None, vdevice=None, ndim=-1, *, span=None):
     """Construct a Relax tensor type.
diff --git a/python/tvm/relax/testing/ast_printer.py 
b/python/tvm/relax/testing/ast_printer.py
index 5d7fdcb55d..9a467eb577 100644
--- a/python/tvm/relax/testing/ast_printer.py
+++ b/python/tvm/relax/testing/ast_printer.py
@@ -210,6 +210,9 @@ class ASTPrinter(ExprFunctor):
     def visit_string_imm_(self, op: tvm.ir.StringImm) -> str:
         return self.build_expr(op, "StringImm", value=wrap_quotes(op.value))
 
+    def visit_data_type_imm_(self, op: tvm.ir.DataTypeImm) -> str:
+        return self.build_expr(op, "DataTypeImm", value=str(op.value))
+
     def visit_op_(self, op: tvm.ir.Op) -> str:
         # TODO: List other attributes?
         # op is not actually a Relax expr and does not have
diff --git a/python/tvm/relax/type.py b/python/tvm/relax/type.py
index b5018ec7ea..4ed8e7a2ff 100644
--- a/python/tvm/relax/type.py
+++ b/python/tvm/relax/type.py
@@ -21,22 +21,13 @@
 import tvm_ffi
 from tvm_ffi import Array
 
-from tvm.ir import EnvFunc, PrimType, Span, TupleType
+from tvm.ir import AnyType, EnvFunc, PrimType, Span, TupleType
 from tvm.relax.global_info import VDevice
 
 from . import _ffi_api
 from .expr import Expr, ShapeExpr, Type
 from .ty import PackedFuncType
 
-
-@tvm_ffi.register_object("relax.AnyType")
-class AnyType(Type):
-    """Type of any Relax value."""
-
-    def __init__(self, span: Span = None) -> None:
-        self.__init_handle_by_constructor__(_ffi_api.AnyType, span)  # type: 
ignore
-
-
 ObjectType = AnyType
 
 
diff --git a/python/tvm/script/ir_builder/__init__.py 
b/python/tvm/script/ir_builder/__init__.py
index ecaecca8e6..f1faadf35e 100644
--- a/python/tvm/script/ir_builder/__init__.py
+++ b/python/tvm/script/ir_builder/__init__.py
@@ -16,7 +16,7 @@
 # under the License.
 """Shared TVMScript construction APIs and lazy language variant builders."""
 
-from tvm.ir import GenericConst, Range, StringImm, StringType
+from tvm.ir import DataTypeImm, GenericConst, Range, StringImm, StringType
 
 from .base import (
     MISSING,
@@ -30,6 +30,7 @@ from .frame import IRModuleFrame
 from .ir import _get_dialect_builder as __getattr__
 from .ir import (
     constexpr,
+    dtype,
     dynamic,
     meta_var,
     module_attrs,
@@ -49,6 +50,7 @@ from .parser_protocol import (
 __all__ = [
     "MISSING",
     "AlreadyEmitted",
+    "DataTypeImm",
     "GenericConst",
     "IRBuilder",
     "IRModuleFrame",
@@ -59,6 +61,7 @@ __all__ = [
     "check_well_formed_",
     "constexpr",
     "decl_function",
+    "dtype",
     "dynamic",
     "ir_module",
     "meta_var",
diff --git a/python/tvm/script/ir_builder/ir.py 
b/python/tvm/script/ir_builder/ir.py
index 8afe2567be..6b31d7b174 100644
--- a/python/tvm/script/ir_builder/ir.py
+++ b/python/tvm/script/ir_builder/ir.py
@@ -18,7 +18,8 @@
 
 from typing import NoReturn, TypeVar
 
-from tvm.ir import GlobalInfo, Span, Var
+from tvm import DataType
+from tvm.ir import DataTypeImm, GlobalInfo, Span, Var
 from tvm.runtime import Object as tvm_Object
 
 from . import _ffi_api
@@ -28,6 +29,15 @@ from .frame import IRModuleFrame
 T = TypeVar("T")
 
 
+def dtype(value: str | DataType) -> DataTypeImm:
+    """Construct a DataType-valued constant for an ordinary operation argument.
+
+    Unlike scalar constructors such as ``T.float32``, this constructs a dtype
+    value, rather than a scalar value or variable of that dtype.
+    """
+    return DataTypeImm(value)
+
+
 def dynamic(name: str, dtype: str = "int64", *, span: SpanEntry | Span | None 
= None) -> Var:
     """Create a fresh primitive symbolic variable, independently of builder 
scope.
 
diff --git a/python/tvm/tirx/script/ir_builder/__init__.py 
b/python/tvm/tirx/script/ir_builder/__init__.py
index 34b2815f20..c4eb0558ea 100644
--- a/python/tvm/tirx/script/ir_builder/__init__.py
+++ b/python/tvm/tirx/script/ir_builder/__init__.py
@@ -17,6 +17,7 @@
 """Concrete TIRx construction operations over the shared native IRBuilder 
stack."""
 
 from tvm.script.ir_builder.ir import constexpr as constexpr
+from tvm.script.ir_builder.ir import dtype as dtype
 from tvm.script.ir_builder.ir import dynamic as dynamic
 from tvm.tirx.lang.alloc_pool import SMEMPool as SMEMPool
 from tvm.tirx.lang.alloc_pool import TMEMPool as TMEMPool
@@ -66,6 +67,7 @@ __all__ = [
     *_op_exports,
     *_protocol_exports,
     "dynamic",
+    "dtype",
     "constexpr",
     "SMEMPool",
     "TMEMPool",
diff --git a/src/ir/expr.cc b/src/ir/expr.cc
index b5218cc51c..fca78bafc0 100644
--- a/src/ir/expr.cc
+++ b/src/ir/expr.cc
@@ -49,6 +49,43 @@ TVMFFIAny ConstantVisit(ffi::StructuralVisitorObj* visitor, 
ffi::AnyView value)
   return ffi::AnyView(nullptr).CopyToTVMFFIAny();
 }
 
+TVMFFIAny DataTypeImmVisit(ffi::StructuralVisitorObj* visitor, ffi::AnyView 
value) noexcept {
+  const DataTypeImmNode* self =
+      ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
DataTypeImmNode>(value);
+  TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(self->ty));
+  TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(self->value));
+  return ffi::AnyView(nullptr).CopyToTVMFFIAny();
+}
+
+TVMFFIAny DataTypeImmMutate(ffi::StructuralMutatorObj* mutator, ffi::AnyView 
value) noexcept {
+  const DataTypeImmNode* self =
+      ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
DataTypeImmNode>(value);
+  TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ffi::UnchangedOr<Type>, mapped_ty,
+                                    mutator->MutateExpected(self->ty));
+  TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ffi::UnchangedOr<DLDataType>, mapped_value,
+                                    mutator->MutateExpected(self->value));
+  if (mapped_ty.UnchangedOrSameAs(self->ty) && 
mapped_value.UnchangedOrSameAs(self->value)) {
+    return ffi::Unchanged().CopyToTVMFFIAny();
+  }
+  ffi::ObjectPtr<DataTypeImmNode> copy = 
ffi::make_object<DataTypeImmNode>(*self);
+  copy->ty = std::move(mapped_ty).ValueOrUnchanged(std::move(copy->ty));
+  copy->value = std::move(mapped_value).ValueOrUnchanged(copy->value);
+  return 
ffi::details::AnyUnsafe::MoveAnyToTVMFFIAny(ffi::Any(std::move(copy)));
+}
+
+TVMFFIAny DataTypeImmMaybeInplaceMutate(ffi::StructuralMutatorObj* mutator,
+                                        ffi::AnyView value) noexcept {
+  DataTypeImmNode* self = const_cast<DataTypeImmNode*>(
+      ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
DataTypeImmNode>(value));
+  TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ffi::UnchangedOr<Type>, mapped_ty,
+                                    mutator->MutateExpected(self->ty, 
ffi::InplaceMode::kAllow));
+  TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ffi::UnchangedOr<DLDataType>, mapped_value,
+                                    mutator->MutateExpected(self->value, 
ffi::InplaceMode::kAllow));
+  if (!mapped_ty.IsUnchanged()) self->ty = 
std::move(mapped_ty).ValueUnchecked();
+  if (!mapped_value.IsUnchanged()) self->value = 
std::move(mapped_value).ValueUnchecked();
+  return ffi::Unchanged().CopyToTVMFFIAny();
+}
+
 template <typename TNode>
 TVMFFIAny ConstantMutate(ffi::StructuralMutatorObj* mutator, ffi::AnyView 
value) noexcept {
   const TNode* self =
@@ -764,6 +801,14 @@ StringImm::StringImm(ffi::String value, Span span) {
   data_ = std::move(node);
 }
 
+DataTypeImm::DataTypeImm(DLDataType value, Span span) {
+  auto node = ffi::make_object<DataTypeImmNode>();
+  node->value = std::move(value);
+  node->ty = AnyType();
+  node->span = std::move(span);
+  data_ = std::move(node);
+}
+
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   ConstantNode::RegisterReflection();
@@ -783,11 +828,19 @@ TVM_FFI_STATIC_INIT_BLOCK() {
             reinterpret_cast<void*>(&ConstantMutate<StringImmNode>))
       .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
             
reinterpret_cast<void*>(&ConstantMaybeInplaceMutate<StringImmNode>));
+  DataTypeImmNode::RegisterReflection();
+  refl::TypeAttrDef<DataTypeImmNode>()
+      .attr(refl::type_attr::kStructuralVisit, 
reinterpret_cast<void*>(&DataTypeImmVisit))
+      .attr(refl::type_attr::kStructuralMutate, 
reinterpret_cast<void*>(&DataTypeImmMutate))
+      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
+            reinterpret_cast<void*>(&DataTypeImmMaybeInplaceMutate));
   refl::GlobalDef()
       .def("ir.GenericConst",
            [](ffi::Any value, Type ty, Span span) {
              return GenericConst(std::move(value), std::move(ty), 
std::move(span));
            })
+      .def("ir.DataTypeImm",
+           [](DLDataType value, Span span) { return DataTypeImm(value, 
std::move(span)); })
       .def("ir.StringImm", [](ffi::String value, Span span) {
         return StringImm(std::move(value), std::move(span));
       });
diff --git a/src/ir/expr_functor.cc b/src/ir/expr_functor.cc
index e076b5c566..046508e987 100644
--- a/src/ir/expr_functor.cc
+++ b/src/ir/expr_functor.cc
@@ -35,6 +35,7 @@ void ExprVisitor::InitVTable(VTable* vtable) {
   SetDispatch<ExprVisitor, FloatImmNode>(vtable);
   SetDispatch<ExprVisitor, OpNode>(vtable);
   SetDispatch<ExprVisitor, StringImmNode>(vtable);
+  SetDispatch<ExprVisitor, DataTypeImmNode>(vtable);
   SetDispatch<ExprVisitor, prim::CastNode>(vtable);
   SetDispatch<ExprVisitor, prim::AddNode>(vtable);
   SetDispatch<ExprVisitor, prim::LShiftNode>(vtable);
@@ -147,6 +148,9 @@ ffi::Optional<VisitInterrupt> ExprVisitor::Visit_(const 
OpNode* node) { return s
 ffi::Optional<VisitInterrupt> ExprVisitor::Visit_(const StringImmNode* node) {
   return std::nullopt;
 }
+ffi::Optional<VisitInterrupt> ExprVisitor::Visit_(const DataTypeImmNode* node) 
{
+  return std::nullopt;
+}
 
 ffi::Optional<VisitInterrupt> ExprVisitor::Visit_(const prim::CastNode* node) {
   TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(this->Visit(node->value));
@@ -344,6 +348,7 @@ void ExprMutator::InitVTable(VTable* vtable) {
   SetDispatch<ExprMutator, FloatImmNode>(vtable);
   SetDispatch<ExprMutator, OpNode>(vtable);
   SetDispatch<ExprMutator, StringImmNode>(vtable);
+  SetDispatch<ExprMutator, DataTypeImmNode>(vtable);
   SetDispatch<ExprMutator, prim::CastNode>(vtable);
   SetDispatch<ExprMutator, prim::AddNode>(vtable);
   SetDispatch<ExprMutator, prim::LShiftNode>(vtable);
@@ -535,6 +540,10 @@ UnchangedOr<Expr> ExprMutator::Mutate_(const 
StringImmNode* node, InplaceMode in
   // Registry atoms and constant leaves do not descend into metadata or types.
   return ffi::Unchanged();
 }
+UnchangedOr<Expr> ExprMutator::Mutate_(const DataTypeImmNode* node, 
InplaceMode inplace_mode) {
+  // Registry atoms and constant leaves do not descend into metadata or types.
+  return ffi::Unchanged();
+}
 
 UnchangedOr<PrimExpr> ExprMutator::Mutate_(const prim::CastNode* node, 
InplaceMode inplace_mode) {
   auto value_u = Mutate(node->value, inplace_mode);
diff --git a/src/ir/type.cc b/src/ir/type.cc
index 525ba4fe8d..ba5996858d 100644
--- a/src/ir/type.cc
+++ b/src/ir/type.cc
@@ -255,6 +255,23 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 
 bool Type::IsMissing() const { return this->same_as(Type::Missing()); }
 
+AnyType::AnyType(Span span) : Type(ffi::UnsafeInit{}) {
+  ffi::ObjectPtr<AnyTypeNode> n = ffi::make_object<AnyTypeNode>();
+  n->span = std::move(span);
+  data_ = std::move(n);
+}
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+  namespace refl = tvm::ffi::reflection;
+  AnyTypeNode::RegisterReflection();
+  refl::TypeAttrDef<AnyTypeNode>()
+      .attr(refl::type_attr::kStructuralVisit, 
reinterpret_cast<void*>(&TypeVisit))
+      .attr(refl::type_attr::kStructuralMutate, 
reinterpret_cast<void*>(&TypeMutate))
+      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
+            reinterpret_cast<void*>(&TypeMaybeInplaceMutate));
+  refl::GlobalDef().def("ir.AnyType", [](Span span) { return AnyType(span); });
+}
+
 OpaqueType::OpaqueType() : Type(ffi::UnsafeInit{}) { data_ = 
ffi::make_object<OpaqueTypeNode>(); }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/relax/backend/vm/codegen_vm.cc 
b/src/relax/backend/vm/codegen_vm.cc
index 87c4d69b74..53d01f3f31 100644
--- a/src/relax/backend/vm/codegen_vm.cc
+++ b/src/relax/backend/vm/codegen_vm.cc
@@ -260,6 +260,9 @@ class CodeGenVM : public ExprFunctor<Instruction::Arg(const 
Expr&)> {
   Instruction::Arg VisitExpr_(const StringImmNode* op) final {
     return builder_->ConvertConstant(op->value);
   }
+  Instruction::Arg VisitExpr_(const DataTypeImmNode* op) final {
+    return builder_->ConvertConstant(op->value);
+  }
 
   Instruction::Arg VisitExpr_(const TupleNode* op) final {
     Tuple tuple = ffi::GetRef<Tuple>(op);
diff --git a/src/relax/backend/vm/codegen_vm_tir.cc 
b/src/relax/backend/vm/codegen_vm_tir.cc
index d2571780eb..2537f9b440 100644
--- a/src/relax/backend/vm/codegen_vm_tir.cc
+++ b/src/relax/backend/vm/codegen_vm_tir.cc
@@ -358,6 +358,9 @@ class CodeGenVMTIR : public 
ExprFunctor<ffi::Optional<Expr>(const Expr&)> {
   ffi::Optional<Expr> VisitExpr_(const StringImmNode* op) final {
     return ConstListGet(builder_->ConvertConstant(op->value).value());
   }
+  ffi::Optional<Expr> VisitExpr_(const DataTypeImmNode* op) final {
+    return ConstListGet(builder_->ConvertConstant(op->value).value());
+  }
 
   ffi::Optional<Expr> VisitExpr_(const TupleNode* op) final {
     Tuple tuple = ffi::GetRef<Tuple>(op);
diff --git a/src/relax/backend/vm/lower_runtime_builtin.cc 
b/src/relax/backend/vm/lower_runtime_builtin.cc
index 1e0d7bb696..f96cd1a8a2 100644
--- a/src/relax/backend/vm/lower_runtime_builtin.cc
+++ b/src/relax/backend/vm/lower_runtime_builtin.cc
@@ -86,14 +86,14 @@ class LowerRuntimeBuiltinMutator : public ExprMutator {
   Expr MakeMemAllocStorage(const Call& call) {
     PrimExpr runtime_device_index = call->args[1].as_or_throw<PrimExpr>();
     StringImm storage_scope = call->args[2].as_or_throw<StringImm>();
-    GenericConst output_dtype = GenericConst((DLDataType{kDLUInt, 8, 1}), 
AnyType());
+    DataTypeImm output_dtype = DataTypeImm((DLDataType{kDLUInt, 8, 1}));
     return Call(Type::Missing(), vm_alloc_storage_op_,
                 {call->args[0], runtime_device_index, output_dtype, 
storage_scope}, Attrs());
   }
 
   Expr MakeMemAllocTensor(const Call& call) {
     PrimExpr offset = call->args[1].as_or_throw<PrimExpr>();
-    GenericConst dtype = call->args[3].as_or_throw<GenericConst>();
+    DataTypeImm dtype = call->args[3].as_or_throw<DataTypeImm>();
 
     ffi::Array<Expr> call_args = {call->args[0], offset, call->args[2], dtype};
     if (5 == call->args.size()) {
diff --git a/src/relax/backend/vm/vm_shape_lower.cc 
b/src/relax/backend/vm/vm_shape_lower.cc
index 0469dff885..a8807e2728 100644
--- a/src/relax/backend/vm/vm_shape_lower.cc
+++ b/src/relax/backend/vm/vm_shape_lower.cc
@@ -804,8 +804,7 @@ class VMShapeLowerMutator
     if (always_check || !IsBaseOf(PrimType(op->dtype), GetType(value))) {
       // check_shape_info(value, ndim, err_ctx)
       Call call(Type::Missing(), builtin_check_prim_value_info_,
-                {value, GenericConst(op->dtype, AnyType()), 
GetErrContext(err_ctx)}, Attrs(),
-                {void_ty_});
+                {value, DataTypeImm(op->dtype), GetErrContext(err_ctx)}, 
Attrs(), {void_ty_});
       builder_->Emit(call, "_");
     }
   }
@@ -840,9 +839,8 @@ class VMShapeLowerMutator
     }
     if (always_check || !IsBaseOf(TensorType(op->dtype, op->ndim), 
GetType(value))) {
       // check_tensor_info(value, ndim, dtype, err_ctx)
-      Expr dtype_arg = op->IsUnknownDtype()
-                           ? Expr(Call(Type::Missing(), null_value_op_, {}))
-                           : Expr(GenericConst(op->dtype.value()->dtype, 
AnyType()));
+      Expr dtype_arg = op->IsUnknownDtype() ? Expr(Call(Type::Missing(), 
null_value_op_, {}))
+                                            : 
Expr(DataTypeImm(op->dtype.value()->dtype));
       Call call(Type::Missing(), builtin_check_tensor_info_,
                 {value, IntImm::Int64(op->ndim), dtype_arg, 
GetErrContext(err_ctx)}, Attrs(),
                 {void_ty_});
diff --git a/src/relax/ir/block_builder.cc b/src/relax/ir/block_builder.cc
index 413f41e5bd..14f92b7f8d 100644
--- a/src/relax/ir/block_builder.cc
+++ b/src/relax/ir/block_builder.cc
@@ -578,6 +578,7 @@ class Normalizer : public BlockBuilderImpl, private 
ExprFunctor<Expr(const Expr&
   RELAX_EXPR_NORMALIZER_LEAF(GenericConstNode);
   RELAX_EXPR_NORMALIZER_LEAF(ShapeExprNode);
   RELAX_EXPR_NORMALIZER_LEAF(StringImmNode);
+  RELAX_EXPR_NORMALIZER_LEAF(DataTypeImmNode);
 
   Expr VisitExprDefault_(const ffi::Object* op) final {
     Expr expr = ffi::GetRef<Expr>(static_cast<const ExprNode*>(op));
diff --git a/src/relax/ir/dependent_type.cc b/src/relax/ir/dependent_type.cc
index 3b7ed49017..406ca9c654 100644
--- a/src/relax/ir/dependent_type.cc
+++ b/src/relax/ir/dependent_type.cc
@@ -35,18 +35,6 @@ using namespace tvm::prim;
 
 namespace {
 
-TVMFFIAny AnyTypeVisit(ffi::StructuralVisitorObj*, ffi::AnyView) noexcept {
-  return ffi::AnyView(nullptr).CopyToTVMFFIAny();
-}
-
-TVMFFIAny AnyTypeMutate(ffi::StructuralMutatorObj*, ffi::AnyView) noexcept {
-  return ffi::Unchanged().CopyToTVMFFIAny();
-}
-
-TVMFFIAny AnyTypeMaybeInplaceMutate(ffi::StructuralMutatorObj*, ffi::AnyView) 
noexcept {
-  return ffi::Unchanged().CopyToTVMFFIAny();
-}
-
 TVMFFIAny ShapeTypeVisit(ffi::StructuralVisitorObj* visitor, ffi::AnyView 
value) noexcept {
   // skips: ndim (scalar)
   const ShapeTypeNode* self =
@@ -188,29 +176,6 @@ TVMFFIAny 
FuncTypeMaybeInplaceMutate(ffi::StructuralMutatorObj* mutator,
 
 }  // namespace
 
-AnyType::AnyType(Span span) : Type(ffi::UnsafeInit{}) {
-  ffi::ObjectPtr<AnyTypeNode> n = ffi::make_object<AnyTypeNode>();
-  n->span = span;
-  data_ = std::move(n);
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
-  namespace refl = tvm::ffi::reflection;
-  AnyTypeNode::RegisterReflection();
-  refl::TypeAttrDef<AnyTypeNode>()
-      .attr(refl::type_attr::kStructuralVisit, 
reinterpret_cast<void*>(&AnyTypeVisit))
-      .attr(refl::type_attr::kStructuralMutate, 
reinterpret_cast<void*>(&AnyTypeMutate))
-      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
-            reinterpret_cast<void*>(&AnyTypeMaybeInplaceMutate));
-
-  refl::GlobalDef().def("relax.AnyType", [](Span span) { return AnyType(span); 
});
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
-  namespace refl = tvm::ffi::reflection;
-  refl::GlobalDef().def("relax.ObjectType", [](Span span) { return 
AnyType(span); });
-}
-
 // Shape
 ShapeType::ShapeType(ffi::Array<PrimExpr> values, Span span) : 
Type(ffi::UnsafeInit{}) {
   ffi::ObjectPtr<ShapeTypeNode> n = ffi::make_object<ShapeTypeNode>();
diff --git a/src/relax/ir/expr_functor.cc b/src/relax/ir/expr_functor.cc
index f7ec9d1816..63dab4874f 100644
--- a/src/relax/ir/expr_functor.cc
+++ b/src/relax/ir/expr_functor.cc
@@ -55,6 +55,7 @@
     RELAX_VISIT_BINDING_DISPATCH(OpNode);                                      
         \
     RELAX_VISIT_BINDING_DISPATCH(TupleGetItemNode);                            
         \
     RELAX_VISIT_BINDING_DISPATCH(StringImmNode);                               
         \
+    RELAX_VISIT_BINDING_DISPATCH(DataTypeImmNode);                             
         \
     return vtable;                                                             
         \
   }                                                                            
         \
   void Type::VisitBinding_(const VarBindingNode* binding) {                    
         \
@@ -320,6 +321,7 @@ void ExprVisitor::VisitExprFallback_(const ExprNode* op) {
 }
 
 void ExprVisitor::VisitExpr_(const StringImmNode* op) { 
this->VisitSpan(op->span); }
+void ExprVisitor::VisitExpr_(const DataTypeImmNode* op) { 
this->VisitSpan(op->span); }
 
 void ExprVisitor::VisitSpan(const Span& span) {}
 
@@ -342,6 +344,7 @@ RELAX_EXPR_VISITOR_VISIT_BINDING_IMPL(OpNode);
 RELAX_EXPR_VISITOR_VISIT_BINDING_IMPL(TupleGetItemNode);
 RELAX_EXPR_VISITOR_VISIT_BINDING_IMPL(ExprNode);
 RELAX_EXPR_VISITOR_VISIT_BINDING_IMPL(StringImmNode);
+RELAX_EXPR_VISITOR_VISIT_BINDING_IMPL(DataTypeImmNode);
 
 void ExprVisitor::VisitBinding_(const MatchCastNode* binding) {
   this->VisitExpr(binding->value);
@@ -675,6 +678,7 @@ Expr ExprMutatorBase::VisitExprFallback_(const ExprNode* 
op) {
 }
 
 Expr ExprMutatorBase::VisitExpr_(const StringImmNode* op) { return 
ffi::GetRef<Expr>(op); }
+Expr ExprMutatorBase::VisitExpr_(const DataTypeImmNode* op) { return 
ffi::GetRef<Expr>(op); }
 
 Expr ExprMutatorBase::VisitExpr_(const ShapeExprNode* op) {
   auto values = op->values.Map(
@@ -859,6 +863,7 @@ RELAX_EXPR_MUTATOR_VISIT_BINDING_IMPL(OpNode);
 RELAX_EXPR_MUTATOR_VISIT_BINDING_IMPL(TupleGetItemNode);
 RELAX_EXPR_MUTATOR_VISIT_BINDING_IMPL(ExprNode);
 RELAX_EXPR_MUTATOR_VISIT_BINDING_IMPL(StringImmNode);
+RELAX_EXPR_MUTATOR_VISIT_BINDING_IMPL(DataTypeImmNode);
 
 void ExprMutator::ReEmitBinding(const VarBindingNode* binding, Expr new_value) 
{
   Var new_var = this->VisitVarDef(binding->var);
diff --git a/src/relax/ir/py_expr_functor.cc b/src/relax/ir/py_expr_functor.cc
index 45d014ee11..a31a31820f 100644
--- a/src/relax/ir/py_expr_functor.cc
+++ b/src/relax/ir/py_expr_functor.cc
@@ -69,6 +69,8 @@ class PyExprVisitorNode : public ffi::Object, public 
ExprVisitor {
   ffi::Function f_visit_expr_fallback_{nullptr};
   /*! \brief The packed function to the `VisitExpr_(const StringImmNode* op)` 
function. */
   ffi::Function f_visit_string_imm_{nullptr};
+  /*! \brief The packed function to the `VisitExpr_(const DataTypeImmNode* 
op)` function. */
+  ffi::Function f_visit_data_type_imm_{nullptr};
   /*! \brief The packed function to the `VisitBinding(const Binding& binding)` 
function. */
   ffi::Function f_visit_binding{nullptr};
   /*! \brief The packed function to the `VisitBinding_(const VarBindingNode* 
binding)`
@@ -171,6 +173,7 @@ class PyExprVisitorNode : public ffi::Object, public 
ExprVisitor {
     PY_EXPR_VISITOR_DISPATCH(OpNode, f_visit_op_);
     PY_EXPR_VISITOR_DISPATCH(TupleGetItemNode, f_visit_tuple_getitem_);
     PY_EXPR_VISITOR_DISPATCH(StringImmNode, f_visit_string_imm_);
+    PY_EXPR_VISITOR_DISPATCH(DataTypeImmNode, f_visit_data_type_imm_);
     vtable.Finalize();
     return vtable;
   }
@@ -203,6 +206,7 @@ class PyExprVisitor : public ffi::ObjectRef {
    * \param f_visit_tuple_getitem_ The packed function of `VisitExpr_(const 
TupleGetItemNode* op)`.
    * \param f_visit_expr_fallback_ The packed function of the generic 
expression fallback.
    * \param f_visit_string_imm_ The packed function of `VisitExpr_(const 
StringImmNode* op)`.
+   * \param f_visit_data_type_imm_ The packed function of `VisitExpr_(const 
DataTypeImmNode* op)`.
    * \param f_visit_binding The packed function of `VisitBinding(const 
Binding& binding)`.
    * \param f_visit_var_binding_ The packed function of `VisitBinding_(const 
VarBindingNode*
    * binding)`.
@@ -229,11 +233,12 @@ class PyExprVisitor : public ffi::ObjectRef {
       ffi::Function f_visit_call_, ffi::Function f_visit_seq_expr_, 
ffi::Function f_visit_if_,
       ffi::Function f_visit_op_, ffi::Function f_visit_tuple_getitem_,
       ffi::Function f_visit_expr_fallback_, ffi::Function f_visit_string_imm_,
-      ffi::Function f_visit_binding, ffi::Function f_visit_var_binding_,
-      ffi::Function f_visit_match_cast_, ffi::Function f_visit_binding_block,
-      ffi::Function f_visit_binding_block_, ffi::Function 
f_visit_dataflow_block_,
-      ffi::Function f_visit_var_def, ffi::Function f_visit_var_def_,
-      ffi::Function f_visit_dataflow_var_def_, ffi::Function f_visit_span) {
+      ffi::Function f_visit_data_type_imm_, ffi::Function f_visit_binding,
+      ffi::Function f_visit_var_binding_, ffi::Function f_visit_match_cast_,
+      ffi::Function f_visit_binding_block, ffi::Function 
f_visit_binding_block_,
+      ffi::Function f_visit_dataflow_block_, ffi::Function f_visit_var_def,
+      ffi::Function f_visit_var_def_, ffi::Function f_visit_dataflow_var_def_,
+      ffi::Function f_visit_span) {
     ffi::ObjectPtr<PyExprVisitorNode> n = 
ffi::make_object<PyExprVisitorNode>();
     n->f_visit_expr = f_visit_expr;
     n->f_visit_binding = f_visit_binding;
@@ -255,6 +260,7 @@ class PyExprVisitor : public ffi::ObjectRef {
     n->f_visit_tuple_getitem_ = f_visit_tuple_getitem_;
     n->f_visit_expr_fallback_ = f_visit_expr_fallback_;
     n->f_visit_string_imm_ = f_visit_string_imm_;
+    n->f_visit_data_type_imm_ = f_visit_data_type_imm_;
     n->f_visit_var_binding_ = f_visit_var_binding_;
     n->f_visit_match_cast_ = f_visit_match_cast_;
     n->f_visit_binding_block_ = f_visit_binding_block_;
@@ -308,6 +314,8 @@ class PyExprMutatorNode : public ffi::Object, public 
ExprMutator {
   ffi::Function f_visit_expr_fallback_{nullptr};
   /*! \brief The packed function to the `VisitExpr_(const StringImmNode* op)` 
function. */
   ffi::Function f_visit_string_imm_{nullptr};
+  /*! \brief The packed function to the `VisitExpr_(const DataTypeImmNode* 
op)` function. */
+  ffi::Function f_visit_data_type_imm_{nullptr};
   /*! \brief The packed function to the `VisitBinding(const Binding& binding)` 
function. */
   ffi::Function f_visit_binding{nullptr};
   /*! \brief The packed function to the `VisitBinding_(const VarBindingNode* 
binding)`
@@ -437,6 +445,7 @@ class PyExprMutatorNode : public ffi::Object, public 
ExprMutator {
     PY_EXPR_MUTATOR_DISPATCH(OpNode, f_visit_op_);
     PY_EXPR_MUTATOR_DISPATCH(TupleGetItemNode, f_visit_tuple_getitem_);
     PY_EXPR_MUTATOR_DISPATCH(StringImmNode, f_visit_string_imm_);
+    PY_EXPR_MUTATOR_DISPATCH(DataTypeImmNode, f_visit_data_type_imm_);
     vtable.Finalize();
     return vtable;
   }
@@ -459,6 +468,7 @@ class PyExprMutatorNode : public ffi::Object, public 
ExprMutator {
     PY_EXPR_MUTATOR_VISIT_EXPR_POST_ORDER_DISPATCH(OpNode);
     PY_EXPR_MUTATOR_VISIT_EXPR_POST_ORDER_DISPATCH(TupleGetItemNode);
     PY_EXPR_MUTATOR_VISIT_EXPR_POST_ORDER_DISPATCH(StringImmNode);
+    PY_EXPR_MUTATOR_VISIT_EXPR_POST_ORDER_DISPATCH(DataTypeImmNode);
     post_order_vtable.Finalize();
     return post_order_vtable;
   }
@@ -491,6 +501,7 @@ class PyExprMutator : public ffi::ObjectRef {
    * \param f_visit_tuple_getitem_ The packed function of `VisitExpr_(const 
TupleGetItemNode* op)`.
    * \param f_visit_expr_fallback_ The packed function of the generic 
expression fallback.
    * \param f_visit_string_imm_ The packed function of `VisitExpr_(const 
StringImmNode* op)`.
+   * \param f_visit_data_type_imm_ The packed function of `VisitExpr_(const 
DataTypeImmNode* op)`.
    * \param f_visit_binding The packed function of `VisitBinding(const 
Binding& binding)`.
    * \param f_visit_var_binding_ The packed function of `VisitBinding_(const 
VarBindingNode*
    * binding)`.
@@ -517,11 +528,12 @@ class PyExprMutator : public ffi::ObjectRef {
       ffi::Function f_visit_call_, ffi::Function f_visit_seq_expr_, 
ffi::Function f_visit_if_,
       ffi::Function f_visit_op_, ffi::Function f_visit_tuple_getitem_,
       ffi::Function f_visit_expr_fallback_, ffi::Function f_visit_string_imm_,
-      ffi::Function f_visit_binding, ffi::Function f_visit_var_binding_,
-      ffi::Function f_visit_match_cast_, ffi::Function f_visit_binding_block,
-      ffi::Function f_visit_binding_block_, ffi::Function 
f_visit_dataflow_block_,
-      ffi::Function f_visit_var_def, ffi::Function f_visit_var_def_,
-      ffi::Function f_visit_dataflow_var_def_, ffi::Function f_visit_span) {
+      ffi::Function f_visit_data_type_imm_, ffi::Function f_visit_binding,
+      ffi::Function f_visit_var_binding_, ffi::Function f_visit_match_cast_,
+      ffi::Function f_visit_binding_block, ffi::Function 
f_visit_binding_block_,
+      ffi::Function f_visit_dataflow_block_, ffi::Function f_visit_var_def,
+      ffi::Function f_visit_var_def_, ffi::Function f_visit_dataflow_var_def_,
+      ffi::Function f_visit_span) {
     ffi::ObjectPtr<PyExprMutatorNode> n = 
ffi::make_object<PyExprMutatorNode>();
     n->builder_ = builder_;
     n->f_visit_expr = f_visit_expr;
@@ -540,6 +552,7 @@ class PyExprMutator : public ffi::ObjectRef {
     n->f_visit_tuple_getitem_ = f_visit_tuple_getitem_;
     n->f_visit_expr_fallback_ = f_visit_expr_fallback_;
     n->f_visit_string_imm_ = f_visit_string_imm_;
+    n->f_visit_data_type_imm_ = f_visit_data_type_imm_;
     n->f_visit_binding = f_visit_binding;
     n->f_visit_var_binding_ = f_visit_var_binding_;
     n->f_visit_match_cast_ = f_visit_match_cast_;
diff --git a/src/relax/op/memory/view.cc b/src/relax/op/memory/view.cc
index 3b2a83ff04..39353d2a35 100644
--- a/src/relax/op/memory/view.cc
+++ b/src/relax/op/memory/view.cc
@@ -121,23 +121,23 @@ Type InferTypeView(const Call& call, const BlockBuilder& 
ctx) {
 
     // In general, Type inference should only depend on the
     // Type of the arguments, and not on the arguments
-    // themselves.  However, dtype GenericConst uses
+    // themselves.  However, dtype DataTypeImm uses
     // `AnyType`, so we need to inspect the argument itself
     // in this case.
-    if (auto dtype_imm = arg_value.as<GenericConstNode>()) {
+    if (auto dtype_imm = arg_value.as<DataTypeImmNode>()) {
       // We know the datatype for the view.
-      TVM_FFI_CHECK(!PrimType(dtype_imm->value.cast<DLDataType>()).IsVoid(), 
TypeError)
+      TVM_FFI_CHECK(!PrimType(dtype_imm->value).IsVoid(), TypeError)
           << "Operator " << call->op
           << " expects the dtype argument to be a concrete tensor datatype, "
           << "but received void.  Use relax.null_value() if no dtype change is 
requested.";
-      return PrimType(dtype_imm->value.cast<DLDataType>());
+      return PrimType(dtype_imm->value);
     } else if (ty.as<AnyTypeNode>()) {
       // The view changes the datatype, but we don't know what it is
       // being changed into.
       return std::nullopt;
     } else {
       TVM_FFI_THROW(TypeError) << "Operator " << call->op
-                               << " expects the dtype argument to be a dtype 
GenericConst, "
+                               << " expects the dtype argument to be a dtype 
DataTypeImm, "
                                << "but received " << arg_dtype << " with type 
" << ty;
     }
   }();
@@ -376,7 +376,7 @@ Expr LowerBuiltinView(const BlockBuilder& bb, const Call& 
call) {
         << "or the input dtype is known.  "
         << "However, in expression " << call << ", no output dtype is 
specified, "
         << "and the input " << data << " of type " << data->ty << " has 
unknown dtype.";
-    dtype = GenericConst(data_tensor_ty->dtype.value()->dtype, AnyType());
+    dtype = DataTypeImm(data_tensor_ty->dtype.value()->dtype);
   }
 
   if (HasVoidType(relative_byte_offset)) {
diff --git a/src/relax/op/op.cc b/src/relax/op/op.cc
index 3826a83e5a..4c344c18ca 100644
--- a/src/relax/op/op.cc
+++ b/src/relax/op/op.cc
@@ -1120,12 +1120,12 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 Type InferTypeAllocateTensor(const Call& call, const BlockBuilder& ctx) {
   TVM_FFI_ICHECK(call->args[0].as<ShapeExprNode>())
       << "must be ShapeExpr, but got " << call->args[0]->GetTypeKey();
-  TVM_FFI_ICHECK(call->args[1].as<GenericConstNode>())
-      << "must be GenericConst, but got " << call->args[1]->GetTypeKey();
+  TVM_FFI_ICHECK(call->args[1].as<DataTypeImmNode>())
+      << "must be DataTypeImm, but got " << call->args[1]->GetTypeKey();
   PrimType out_dtype = PrimType::Void();
-  if (const auto* dtype_node = call->args[1].as<GenericConstNode>()) {
-    const GenericConst dtype_imm = ffi::GetRef<GenericConst>(dtype_node);
-    out_dtype = PrimType(dtype_imm->value.cast<DLDataType>());
+  if (const auto* dtype_node = call->args[1].as<DataTypeImmNode>()) {
+    const DataTypeImm dtype_imm = ffi::GetRef<DataTypeImm>(dtype_node);
+    out_dtype = PrimType(dtype_imm->value);
   }
   int64_t vdevice_index = -1;
   if (const auto* int_imm = call->args[2].as<IntImmNode>()) {
@@ -1142,7 +1142,7 @@ Type InferTypeAllocateTensor(const Call& call, const 
BlockBuilder& ctx) {
 TVM_REGISTER_OP("relax.builtin.alloc_tensor")
     .set_num_inputs(4)
     .add_argument("shape", "Expr", "The shape of the tensor to allocate.")
-    .add_argument("dtype", "GenericConst", "The dtype of the tensor to 
allocate.")
+    .add_argument("dtype", "DataTypeImm", "The dtype of the tensor to 
allocate.")
     .add_argument("runtime_device_index", "PrimExpr",
                   "The device index indicating on which device the tensor is 
to be "
                   "allocated at runtime. Index -1 is reserved for the host 
device.")
@@ -1153,7 +1153,7 @@ TVM_REGISTER_OP("relax.builtin.alloc_tensor")
     .set_attr<bool>("FPurity", true)
     .set_attr<bool>("TAllocator", true);
 
-Expr MakeAllocTensor(Expr shape, GenericConst dtype, PrimExpr 
runtime_device_index,
+Expr MakeAllocTensor(Expr shape, DataTypeImm dtype, PrimExpr 
runtime_device_index,
                      StringImm storage_scope) {
   static const Op& op = Op::Get("relax.builtin.alloc_tensor");
   return Call(Type::Missing(), op, {shape, dtype, runtime_device_index, 
storage_scope}, Attrs(),
@@ -1176,14 +1176,14 @@ TVM_REGISTER_OP("relax.memory.alloc_storage")
         "Index -1 is reserved for the host device.")
     .add_argument("storage_scope", "StringImm",
                   "The storage scope of the storage to allocate. Default is 
global.")
-    .add_argument("dtype", "GenericConst", "The dtype of the tensor to 
allocate.")
+    .add_argument("dtype", "DataTypeImm", "The dtype of the tensor to 
allocate.")
     .set_attr<FInferType>("FInferType", ReturnAnyType)
     // memory allocation isn't considered a "visible effect" as far as purity 
is concerned
     .set_attr<bool>("FPurity", true)
     .set_attr<bool>("TAllocator", true);
 
 Expr MakeAllocStorage(Expr size, PrimExpr virtual_device_index, StringImm 
storage_scope,
-                      GenericConst dtype) {
+                      DataTypeImm dtype) {
   static const Op& op = Op::Get("relax.memory.alloc_storage");
   return Call(Type::Missing(), op, {size, virtual_device_index, storage_scope, 
dtype}, Attrs(), {});
 }
@@ -1199,9 +1199,9 @@ Type InferTypeMemAllocTensor(const Call& call, const 
BlockBuilder& ctx) {
   TVM_FFI_ICHECK(GetTypeAs<ShapeTypeNode>(call->args[2]))
       << "must be a Expr of ShapeType, but got " << 
call->args[1]->GetTypeKey();
   PrimType out_dtype = PrimType::Void();
-  if (const auto* dtype_node = call->args[3].as<GenericConstNode>()) {
-    const GenericConst dtype_imm = ffi::GetRef<GenericConst>(dtype_node);
-    out_dtype = PrimType(dtype_imm->value.cast<DLDataType>());
+  if (const auto* dtype_node = call->args[3].as<DataTypeImmNode>()) {
+    const DataTypeImm dtype_imm = ffi::GetRef<DataTypeImm>(dtype_node);
+    out_dtype = PrimType(dtype_imm->value);
   }
 
   if (call->args.size() == 5) {
@@ -1223,7 +1223,7 @@ TVM_REGISTER_OP("relax.memory.alloc_tensor")
     .add_argument("storage", "Expr", "The storage to allocate the tensor to.")
     .add_argument("offset", "PrimExpr", "Storage offset to allocate the 
tensor.")
     .add_argument("shape", "Expr", "The shape of the tensor to allocate.")
-    .add_argument("dtype", "GenericConst", "The dtype of the tensor to 
allocate.")
+    .add_argument("dtype", "DataTypeImm", "The dtype of the tensor to 
allocate.")
     .add_argument("runtime_device_index", "PrimExpr",
                   "The device index indicating on which device the tensor is 
to be "
                   "allocated at runtime. Index -1 is reserved for the host 
device.")
@@ -1232,7 +1232,7 @@ TVM_REGISTER_OP("relax.memory.alloc_tensor")
     .set_attr<bool>("FPurity", true)
     .set_attr<bool>("TAllocator", true);
 
-Expr MakeMemAllocTensor(Expr storage, PrimExpr offset, Expr shape, 
GenericConst dtype,
+Expr MakeMemAllocTensor(Expr storage, PrimExpr offset, Expr shape, DataTypeImm 
dtype,
                         PrimExpr virtual_device_index) {
   static const Op& op = Op::Get("relax.memory.alloc_tensor");
   return Call(Type::Missing(), op, {storage, offset, shape, dtype, 
virtual_device_index}, Attrs(),
@@ -1245,11 +1245,11 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       "relax.op.memory.alloc_tensor", [](ffi::PackedArgs args, ffi::Any* ret) {
         if (args.size() == 5) {
           *ret = MakeMemAllocTensor(args[0].cast<Expr>(), 
args[1].cast<PrimExpr>(),
-                                    args[2].cast<Expr>(), 
args[3].cast<GenericConst>(),
+                                    args[2].cast<Expr>(), 
args[3].cast<DataTypeImm>(),
                                     args[4].cast<PrimExpr>());
         } else {
           *ret = MakeMemAllocTensor(args[0].cast<Expr>(), 
args[1].cast<PrimExpr>(),
-                                    args[2].cast<Expr>(), 
args[3].cast<GenericConst>(),
+                                    args[2].cast<Expr>(), 
args[3].cast<DataTypeImm>(),
                                     IntImm::Int64(0));
         }
       });
@@ -1298,7 +1298,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 TVM_REGISTER_OP("relax.vm.alloc_storage")
     .set_num_inputs(4)
     .add_argument("size", "Expr", "The size of the storage to allocate.")
-    .add_argument("dtype", "GenericConst", "The dtype of the tensor to 
allocate.")
+    .add_argument("dtype", "DataTypeImm", "The dtype of the tensor to 
allocate.")
     .add_argument("runtime_device_index", "PrimExpr",
                   "The device index indicating on which device the tensor is "
                   "to be allocated at runtime.")
@@ -1309,7 +1309,7 @@ TVM_REGISTER_OP("relax.vm.alloc_storage")
     .set_attr<bool>("FPurity", true)
     .set_attr<bool>("TAllocator", true);
 
-Expr MakeVMAllocStorage(Expr size, PrimExpr runtime_device_index, GenericConst 
dtype,
+Expr MakeVMAllocStorage(Expr size, PrimExpr runtime_device_index, DataTypeImm 
dtype,
                         StringImm storage_scope) {
   static const Op& op = Op::Get("relax.vm.alloc_storage");
   return Call(Type::Missing(), op, {size, runtime_device_index, dtype, 
storage_scope}, Attrs(), {});
@@ -1324,9 +1324,9 @@ TVM_FFI_STATIC_INIT_BLOCK() {
 
 Type InferTypeVMAllocTensor(const Call& call, const BlockBuilder& ctx) {
   PrimType out_dtype = PrimType::Void();
-  if (const auto* dtype_node = call->args[3].as<GenericConstNode>()) {
-    const GenericConst dtype_imm = ffi::GetRef<GenericConst>(dtype_node);
-    out_dtype = PrimType(dtype_imm->value.cast<DLDataType>());
+  if (const auto* dtype_node = call->args[3].as<DataTypeImmNode>()) {
+    const DataTypeImm dtype_imm = ffi::GetRef<DataTypeImm>(dtype_node);
+    out_dtype = PrimType(dtype_imm->value);
   }
   int64_t vdevice_index = -1;
   if (const auto* int_imm = call->args[4].as<IntImmNode>()) {
@@ -1351,7 +1351,7 @@ TVM_REGISTER_OP("relax.vm.alloc_tensor")
     .add_argument("storage", "Expr", "The storage to allocate the tensor to.")
     .add_argument("offset", "PrimExpr", "Storage offset to allocate the 
tensor.")
     .add_argument("shape", "Expr", "The shape of the tensor to allocate.")
-    .add_argument("dtype", "GenericConst", "The dtype of the tensor to 
allocate.")
+    .add_argument("dtype", "DataTypeImm", "The dtype of the tensor to 
allocate.")
     .add_argument("runtime_device_index", "PrimExpr",
                   "The device index indicating on which device the tensor is "
                   "to be allocated at runtime.")
@@ -1360,7 +1360,7 @@ TVM_REGISTER_OP("relax.vm.alloc_tensor")
     .set_attr<bool>("FPurity", true)
     .set_attr<bool>("TAllocator", true);
 
-Expr MakeVMAllocTensor(Expr storage, PrimExpr offset, Expr shape, GenericConst 
dtype,
+Expr MakeVMAllocTensor(Expr storage, PrimExpr offset, Expr shape, DataTypeImm 
dtype,
                        PrimExpr runtime_device_index) {
   static const Op& op = Op::Get("relax.vm.alloc_tensor");
   return Call(Type::Missing(), op, {storage, offset, shape, dtype, 
runtime_device_index}, Attrs(),
@@ -1372,10 +1372,10 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   refl::GlobalDef().def_packed("relax.op.vm.alloc_tensor", [](ffi::PackedArgs 
args, ffi::Any* ret) {
     if (args.size() == 5) {
       *ret = MakeVMAllocTensor(args[0].cast<Expr>(), args[1].cast<PrimExpr>(), 
args[2].cast<Expr>(),
-                               args[3].cast<GenericConst>(), 
args[4].cast<PrimExpr>());
+                               args[3].cast<DataTypeImm>(), 
args[4].cast<PrimExpr>());
     } else {
       *ret = MakeVMAllocTensor(args[0].cast<Expr>(), args[1].cast<PrimExpr>(), 
args[2].cast<Expr>(),
-                               args[3].cast<GenericConst>(), IntImm::Int64(0));
+                               args[3].cast<DataTypeImm>(), IntImm::Int64(0));
     }
   });
 }
diff --git a/src/relax/op/op_common.h b/src/relax/op/op_common.h
index da4013e899..953892c1f1 100644
--- a/src/relax/op/op_common.h
+++ b/src/relax/op/op_common.h
@@ -606,12 +606,12 @@ inline ffi::Optional<ShapeExpr> 
CheckNdimPerLayoutAndGetShape(const Call& call,
   return std::nullopt;
 }
 
-Expr MakeVMAllocStorage(Expr size, PrimExpr runtime_device_index, GenericConst 
dtype,
+Expr MakeVMAllocStorage(Expr size, PrimExpr runtime_device_index, DataTypeImm 
dtype,
                         StringImm storage_scope = StringImm("global"));
-Expr MakeVMAllocTensor(Expr storage, PrimExpr offset, Expr shape, GenericConst 
dtype,
+Expr MakeVMAllocTensor(Expr storage, PrimExpr offset, Expr shape, DataTypeImm 
dtype,
                        PrimExpr runtime_device_index);
 
-Expr MakeAllocTensor(Expr shape, GenericConst dtype, PrimExpr 
runtime_device_index,
+Expr MakeAllocTensor(Expr shape, DataTypeImm dtype, PrimExpr 
runtime_device_index,
                      StringImm storage_scope = StringImm("global"));
 
 /**
diff --git a/src/relax/script/ir_builder/frame.cc 
b/src/relax/script/ir_builder/frame.cc
index 0c5ec90534..fb47653d2f 100644
--- a/src/relax/script/ir_builder/frame.cc
+++ b/src/relax/script/ir_builder/frame.cc
@@ -86,7 +86,7 @@ void FunctionFrameNode::ExitWithScope() {
     RelaxFrameNode::ExitWithScope();
     block_builder->EndScope();
     function =
-        tvm::relax::Function::CreateEmpty(params, 
ret_ty.value_or(tvm::relax::AnyType()),
+        tvm::relax::Function::CreateEmpty(params, 
ret_ty.value_or(tvm::AnyType()),
                                           is_pure.value_or(true), 
DictAttrs(attrs), source_span);
     if (local) {
       auto ty = tvm::relax::GetType(function.value());
diff --git a/src/relax/script/printer/dependent_type.cc 
b/src/relax/script/printer/dependent_type.cc
index 026819dcd9..433932502a 100644
--- a/src/relax/script/printer/dependent_type.cc
+++ b/src/relax/script/printer/dependent_type.cc
@@ -26,8 +26,8 @@ namespace script {
 namespace printer {
 
 TVM_FFI_STATIC_INIT_BLOCK() {
-  IRDocsifier::vtable().set_dispatch<relax::AnyType>(  //
-      "", [](relax::AnyType n, AccessPath n_p, IRDocsifier d) -> Doc { return 
Relax(d, "Any"); });
+  IRDocsifier::vtable().set_dispatch<AnyType>(  //
+      "", [](AnyType n, AccessPath n_p, IRDocsifier d) -> Doc { return 
Relax(d, "Any"); });
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
@@ -125,7 +125,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
           ffi::Array<ffi::String> keys;
           ffi::Array<ExprDoc, void> values;
 
-          if (!n->ret->IsInstance<relax::AnyTypeNode>()) {
+          if (!n->ret->IsInstance<AnyTypeNode>()) {
             keys.push_back("ret");
             values.push_back(ret_doc);
           }
@@ -151,7 +151,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       });
 }
 
-TVM_REGISTER_SCRIPT_AS_REPR(relax::AnyTypeNode, ReprPrintRelax);
+TVM_REGISTER_SCRIPT_AS_REPR(AnyTypeNode, ReprPrintRelax);
 TVM_REGISTER_SCRIPT_AS_REPR(relax::ShapeTypeNode, ReprPrintRelax);
 TVM_REGISTER_SCRIPT_AS_REPR(relax::TensorTypeNode, ReprPrintRelax);
 TVM_REGISTER_SCRIPT_AS_REPR(relax::FuncTypeNode, ReprPrintRelax);
diff --git a/src/relax/script/printer/expr.cc b/src/relax/script/printer/expr.cc
index c1901459e5..176d01ae83 100644
--- a/src/relax/script/printer/expr.cc
+++ b/src/relax/script/printer/expr.cc
@@ -130,9 +130,9 @@ ffi::Optional<ExprDoc> SpecialScalar(const runtime::Tensor& 
n, const AccessPath&
 
 TVM_FFI_STATIC_INIT_BLOCK() {
   IRDocsifier::vtable().set_dispatch<::tvm::GenericConst>(  //
-      "", [](::tvm::GenericConst n, AccessPath n_p, IRDocsifier d) -> Doc {
-        if (auto dtype = n->value.as<DLDataType>()) {
-          return Relax(d, "dtype")->Call({LiteralDoc::DataType(*dtype, 
n_p->Attr("value"))});
+      "relax", [](::tvm::GenericConst n, AccessPath n_p, IRDocsifier d) -> Doc 
{
+        if (n->value.as<DLDataType>()) {
+          return IRDocsifier::vtable()("", n, n_p, d);
         }
         auto data = n->value.cast<runtime::Tensor>();
         if (ffi::Optional<ExprDoc> s = SpecialScalar(data, 
n_p->Attr("value"))) {
@@ -179,6 +179,7 @@ TVM_REGISTER_SCRIPT_AS_REPR(relax::ShapeExprNode, 
ReprPrintRelax);
 TVM_REGISTER_SCRIPT_AS_REPR(VarNode, ReprPrintVar);
 TVM_REGISTER_SCRIPT_AS_REPR(relax::DataflowVarNode, ReprPrintRelax);
 TVM_REGISTER_SCRIPT_AS_REPR(::tvm::GenericConstNode, ReprPrintRelax);
+TVM_REGISTER_SCRIPT_AS_REPR(::tvm::DataTypeImmNode, ReprPrintRelax);
 
 }  // namespace printer
 }  // namespace script
diff --git a/src/relax/script/printer/function.cc 
b/src/relax/script/printer/function.cc
index bd27d16ba5..7e5cc28b8f 100644
--- a/src/relax/script/printer/function.cc
+++ b/src/relax/script/printer/function.cc
@@ -27,7 +27,7 @@ namespace printer {
 static bool HasDefaultExternFuncType(const relax::ExternFunc& n) {
   const auto* ty = n->ty.as<relax::FuncTypeNode>();
   if (ty == nullptr || ty->params.has_value() || ty->purity ||
-      !ty->ret->IsInstance<relax::AnyTypeNode>()) {
+      !ty->ret->IsInstance<AnyTypeNode>()) {
     return false;
   }
   return true;
diff --git a/src/relax/transform/allocate_workspace.cc 
b/src/relax/transform/allocate_workspace.cc
index 0937dc83e1..ab80be5119 100644
--- a/src/relax/transform/allocate_workspace.cc
+++ b/src/relax/transform/allocate_workspace.cc
@@ -150,7 +150,7 @@ class WorkspaceProvider : ExprMutator {
     builder_->BeginDataflowBlock();
     if (!workspace_var_main_.defined()) {
       auto shape = ShapeExpr({IntImm::Int32(max_workspace_size_)});
-      auto ty = GenericConst((DLDataType{kDLUInt, 8, 1}), AnyType());
+      auto ty = DataTypeImm((DLDataType{kDLUInt, 8, 1}));
       auto workspace = MakeAllocTensor(shape, ty, IntImm::Int64(0));
       workspace_var_main_ = builder_->Emit(workspace, "workspace_main");
     }
diff --git a/src/relax/transform/call_tir_rewrite.cc 
b/src/relax/transform/call_tir_rewrite.cc
index 67cf180cb7..20c7027c97 100644
--- a/src/relax/transform/call_tir_rewrite.cc
+++ b/src/relax/transform/call_tir_rewrite.cc
@@ -176,7 +176,7 @@ class CallTIRMutator : public ExprMutator {
 
     return builder_->Emit(Call(Type::Missing(), alloc_tensor_op,
                                
{tensor_ty->shape.value().as_or_throw<ShapeExpr>(),
-                                GenericConst(tensor_ty->dtype.value()->dtype, 
AnyType()),
+                                DataTypeImm(tensor_ty->dtype.value()->dtype),
                                 IntImm::Int64(dev_index), StringImm(scope)},
                                Attrs(), {tensor_ty}),
                           "alloc");
diff --git a/src/relax/transform/fuse_ops.cc b/src/relax/transform/fuse_ops.cc
index 354b08fe8f..937f4cd7c6 100644
--- a/src/relax/transform/fuse_ops.cc
+++ b/src/relax/transform/fuse_ops.cc
@@ -279,7 +279,7 @@ class GraphCreator : public ExprVisitor {
 
     if (!leaf_expr.as<ShapeExprNode>() && !leaf_expr.as<VarNode>() &&
         !leaf_expr.as<GenericConstNode>() && !leaf_expr.as<PrimExpr>() &&
-        !leaf_expr.as<StringImmNode>()) {
+        !leaf_expr.as<StringImmNode>() && !leaf_expr.as<DataTypeImmNode>()) {
       // Skip GlobalVar, ExternFunc, OpNode.
       return;
     }
@@ -1213,6 +1213,7 @@ class PatternBasedPartitioner : ExprVisitor {
   }
 
   void VisitExpr_(const GenericConstNode* op) final { group_map_[op] = 
arena_->make<Group>(); }
+  void VisitExpr_(const DataTypeImmNode* op) final { group_map_[op] = 
arena_->make<Group>(); }
 
   void VisitBinding_(const VarBindingNode* binding, const CallNode* call) 
final {
     VisitVarDef(binding->var);
diff --git a/src/relax/transform/lower_alloc_tensor.cc 
b/src/relax/transform/lower_alloc_tensor.cc
index dd46331bf8..ff382f4de6 100644
--- a/src/relax/transform/lower_alloc_tensor.cc
+++ b/src/relax/transform/lower_alloc_tensor.cc
@@ -49,7 +49,7 @@ class Mutator : public ExprMutator {
           << "However, received " << ffi::GetRef<Call>(op);
 
       auto shape_arg = op->args[0];
-      auto dtype = op->args[1].as_or_throw<GenericConst>();
+      auto dtype = op->args[1].as_or_throw<DataTypeImm>();
       PrimExpr runtime_device_index = op->args[2].as_or_throw<PrimExpr>();
       StringImm storage_scope = op->args[3].as_or_throw<StringImm>();
 
@@ -73,7 +73,7 @@ class Mutator : public ExprMutator {
       }();
 
       PrimExpr nbytes = [&]() -> PrimExpr {
-        PrimType dtype_ty(dtype->value.cast<DLDataType>());
+        PrimType dtype_ty(dtype->value);
         TVM_FFI_ICHECK(!dtype_ty.IsScalableVector())
             << "Cannot statically compute allocation size for scalable vector 
dtype " << dtype_ty;
         PrimExpr nbytes = 
IntImm::Int64(static_cast<int64_t>(dtype_ty.StorageBytes()));
@@ -115,9 +115,9 @@ class Mutator : public ExprMutator {
 
       auto offset = IntImm::Int64(0);
 
-      Expr storage = Call(Type::Missing(), mem_alloc_storage_op,
-                          {size, runtime_device_index, storage_scope,
-                           GenericConst((DLDataType{kDLUInt, 8, 1}), 
AnyType())});
+      Expr storage = Call(
+          Type::Missing(), mem_alloc_storage_op,
+          {size, runtime_device_index, storage_scope, 
DataTypeImm((DLDataType{kDLUInt, 8, 1}))});
       storage = builder_->Emit(storage, "storage");
       Expr tensor = Call(Type::Missing(), mem_alloc_tensor_op,
                          {storage, offset, shape_arg, dtype, op->args[2]});
diff --git a/src/relax/transform/rewrite_cuda_graph.cc 
b/src/relax/transform/rewrite_cuda_graph.cc
index ba6a6e9e63..a2995ea0f7 100644
--- a/src/relax/transform/rewrite_cuda_graph.cc
+++ b/src/relax/transform/rewrite_cuda_graph.cc
@@ -505,8 +505,8 @@ class CUDAGraphRewritePlanner : public ExprVisitor {
 
   bool IsStatic(const Expr& expr, std::vector<const VarNode*>* vars_collector 
= nullptr,
                 std::vector<PrimVar>* tir_vars_collector = nullptr) {
-    if (expr->IsInstance<GenericConstNode>() || 
expr->IsInstance<StringImmNode>() ||
-        expr->IsInstance<GlobalVarNode>()) {
+    if (expr->IsInstance<GenericConstNode>() || 
expr->IsInstance<DataTypeImmNode>() ||
+        expr->IsInstance<StringImmNode>() || 
expr->IsInstance<GlobalVarNode>()) {
       return true;
     }
     if (const auto* var = expr.as<VarNode>();
diff --git a/src/relax/transform/static_plan_block_memory.cc 
b/src/relax/transform/static_plan_block_memory.cc
index 1871a905a6..53059da3e6 100644
--- a/src/relax/transform/static_plan_block_memory.cc
+++ b/src/relax/transform/static_plan_block_memory.cc
@@ -653,8 +653,7 @@ class StorageAllocatorInit : public 
StorageAllocatorBaseVisitor {
     const auto* shape = ty->shape.as<ShapeExprNode>();
     TVM_FFI_ICHECK_NOTNULL(shape);
     TVM_FFI_ICHECK(!ty->IsUnknownDtype());
-    TVM_FFI_ICHECK(ty->dtype.value()->dtype ==
-                   
call->args[1].as_or_throw<GenericConst>()->value.cast<DLDataType>());
+    TVM_FFI_ICHECK(ty->dtype.value()->dtype == 
call->args[1].as_or_throw<DataTypeImm>()->value);
     TVM_FFI_ICHECK(!token_map_.count(call));
 
     // Use the upper bounds of TIR vars as their values. The upper bound shape 
can still be dynamic
@@ -960,7 +959,7 @@ class StorageAllocationRewriter : public ExprMutator {
         DLDataType dtype = token->dtype;
         Call alloc_storage(Type::Missing(), mem_alloc_storage,
                            {std::move(size), virtual_device_index, 
StringImm(token->storage_scope),
-                            GenericConst(dtype, AnyType())},
+                            DataTypeImm(dtype)},
                            Attrs());
         storage_var = builder_->Emit(alloc_storage, "storage");
         token2storage_var_[token.get()] = storage_var;
@@ -971,10 +970,9 @@ class StorageAllocationRewriter : public ExprMutator {
       // And always create a `memory.alloc_tensor` for the old 
`builtin.alloc_tensor`.
       PrimExpr offset = IntImm::Int64(0);
       DLDataType dtype = ty->dtype.value()->dtype;
-      return Call(
-          Type::Missing(), mem_alloc_tensor,
-          {storage_var, offset, ty->shape.value(), GenericConst(dtype, 
AnyType()), call->args[2]},
-          Attrs());
+      return Call(Type::Missing(), mem_alloc_tensor,
+                  {storage_var, offset, ty->shape.value(), DataTypeImm(dtype), 
call->args[2]},
+                  Attrs());
     } else if (plan_dynamic_output_ && call->op.same_as(alloc_tensor_op)) {
       // Case 2. For a `alloc_tensor` that is not planned for memory reuse,
       // we would still like to allocate **static** memory for the tensor.
@@ -991,7 +989,7 @@ class StorageAllocationRewriter : public ExprMutator {
       if (!IsStaticShape(shape->values)) {
         TVM_FFI_ICHECK(!ty->IsUnknownDtype());
         TVM_FFI_ICHECK_EQ(ty->dtype.value()->dtype,
-                          
call->args[1].as_or_throw<GenericConst>()->value.cast<DLDataType>());
+                          call->args[1].as_or_throw<DataTypeImm>()->value);
         PrimExpr bytes = upper_bounded_shape[0];
         for (int i = 1; i < static_cast<int>(upper_bounded_shape.size()); ++i) 
{
           bytes *= upper_bounded_shape[i];
@@ -1005,13 +1003,13 @@ class StorageAllocationRewriter : public ExprMutator {
                            {/*size=*/ShapeExpr({bytes}),
                             
/*virtual_device_index=*/call->args[2].as_or_throw<PrimExpr>(),
                             
/*storage_scope=*/call->args[3].as_or_throw<StringImm>(),  //
-                            /*dtype=*/GenericConst(dtype, AnyType())});
+                            /*dtype=*/DataTypeImm(dtype)});
         Var storage = builder_->Emit(alloc_storage, "storage");
         return Call(Type::Missing(), mem_alloc_tensor,
                     {storage,  //
                      /*offset=*/IntImm::Int64(0),
                      /*shape=*/ffi::GetRef<ShapeExpr>(shape),  //
-                     /*dtype=*/GenericConst(dtype, AnyType()),
+                     /*dtype=*/DataTypeImm(dtype),
                      /*vdevice_index=*/call->args[2]});
       }
     }
diff --git a/src/script/printer/ir/misc.cc b/src/script/printer/ir/misc.cc
index 53e57f8afb..400cb4fe5d 100644
--- a/src/script/printer/ir/misc.cc
+++ b/src/script/printer/ir/misc.cc
@@ -17,6 +17,7 @@
  * under the License.
  */
 #include <tvm/ffi/container/shape.h>
+#include <tvm/runtime/tensor.h>
 
 #include "./utils.h"
 
@@ -24,6 +25,38 @@ namespace tvm {
 namespace script {
 namespace printer {
 
+TVM_FFI_STATIC_INIT_BLOCK() {
+  IRDocsifier::vtable().set_dispatch<DataTypeImm>(
+      "", [](DataTypeImm n, AccessPath p, IRDocsifier d) -> Doc {
+        return TIR(d, "dtype")->Call({LiteralDoc::DataType(n->value, 
p->Attr("value"))});
+      });
+  IRDocsifier::vtable().set_dispatch<DataTypeImm>(
+      "ir", [](DataTypeImm n, AccessPath p, IRDocsifier d) -> Doc {
+        return IR(d, "dtype")->Call({LiteralDoc::DataType(n->value, 
p->Attr("value"))});
+      });
+  IRDocsifier::vtable().set_dispatch<GenericConst>(
+      "", [](GenericConst n, AccessPath p, IRDocsifier d) -> Doc {
+        if (auto dtype = n->value.as<DLDataType>()) {
+          return TIR(d, "dtype")->Call({LiteralDoc::DataType(*dtype, 
p->Attr("value"))});
+        }
+        if (n->value.as<runtime::Tensor>()) {
+          // Tensor-valued constants are Relax constants and keep the Relax 
spelling.
+          return IRDocsifier::vtable()("relax", n, p, d);
+        }
+        return d->AddMetadata(n);
+      });
+}
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+  IRDocsifier::vtable().set_dispatch<GenericConst>(
+      "ir", [](GenericConst n, AccessPath p, IRDocsifier d) -> Doc {
+        if (auto dtype = n->value.as<DLDataType>()) {
+          return IR(d, "dtype")->Call({LiteralDoc::DataType(*dtype, 
p->Attr("value"))});
+        }
+        return IRDocsifier::vtable()("", n, p, d);
+      });
+}
+
 TVM_FFI_STATIC_INIT_BLOCK() {
   IRDocsifier::vtable().set_dispatch<ffi::Array<Any>>(  //
       "", [](ffi::Array<Any> array, AccessPath p, IRDocsifier d) -> Doc {
diff --git a/src/tirx/ir/function.cc b/src/tirx/ir/function.cc
index 3b87c1a62c..ccc2b25228 100644
--- a/src/tirx/ir/function.cc
+++ b/src/tirx/ir/function.cc
@@ -53,7 +53,7 @@ tvm::Type InferType(const PrimFunc& prim_func) {
       // same Relax-facing wildcard semantics that opaque handle parameters had
       // before pointers became exact IR types.
       if (param->ty.as<PointerTypeNode>()) {
-        return relax::AnyType();
+        return AnyType();
       }
 
       return param->ty;
@@ -67,7 +67,7 @@ tvm::Type InferType(const PrimFunc& prim_func) {
     } else if (IsVoidType(prim_func->ret_type)) {
       return relax::TupleType(ffi::Array<tvm::Type>{});
     } else {
-      return relax::AnyType();
+      return AnyType();
     }
   }();
 
diff --git a/src/tirx/ir/tir_visitor_with_path.cc 
b/src/tirx/ir/tir_visitor_with_path.cc
index 6757e1b01a..95ad243f88 100644
--- a/src/tirx/ir/tir_visitor_with_path.cc
+++ b/src/tirx/ir/tir_visitor_with_path.cc
@@ -364,6 +364,8 @@ DEFINE_BINOP_VISIT_(prim::BitwiseXorNode);
 void TIRVisitorWithPath::Dispatch_(const IntImmNode* op, AccessPath path) {}
 void TIRVisitorWithPath::Dispatch_(const FloatImmNode* op, AccessPath path) {}
 void TIRVisitorWithPath::Dispatch_(const StringImmNode* op, AccessPath path) {}
+void TIRVisitorWithPath::Dispatch_(const DataTypeImmNode* op, AccessPath path) 
{}
+void TIRVisitorWithPath::Dispatch_(const GenericConstNode* op, AccessPath 
path) {}
 
 void TIRVisitorWithPath::Dispatch_(const prim::CastNode* op, AccessPath path) {
   Visit(op->value, path->Attr("value"));
diff --git a/src/tirx/ir/tir_visitor_with_path.h 
b/src/tirx/ir/tir_visitor_with_path.h
index 56305d8698..101951a77b 100644
--- a/src/tirx/ir/tir_visitor_with_path.h
+++ b/src/tirx/ir/tir_visitor_with_path.h
@@ -64,8 +64,12 @@ class TIRVisitorWithPath : protected ExprFunctor<void(const 
Expr&, ffi::reflecti
   virtual inline void Visit(const Expr& obj, ffi::reflection::AccessPath path) 
{
     if (auto prim = obj.as<PrimExpr>()) {
       Visit(prim.value(), path);
+    } else if (auto* constant = obj.as<GenericConstNode>()) {
+      Dispatch_(constant, path);
     } else if (auto* str = obj.as<StringImmNode>()) {
       Dispatch_(str, path);
+    } else if (auto* str = obj.as<DataTypeImmNode>()) {
+      Dispatch_(str, path);
     } else if (auto* var = obj.as<VarNode>()) {
       Dispatch_(var, path);
     } else if (auto* call = obj.as<CallNode>()) {
@@ -167,6 +171,8 @@ class TIRVisitorWithPath : protected ExprFunctor<void(const 
Expr&, ffi::reflecti
   void Dispatch_(const IntImmNode* op, ffi::reflection::AccessPath path) 
override;
   void Dispatch_(const FloatImmNode* op, ffi::reflection::AccessPath path) 
override;
   void Dispatch_(const StringImmNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const DataTypeImmNode* op, ffi::reflection::AccessPath path) 
override;
+  void Dispatch_(const GenericConstNode* op, ffi::reflection::AccessPath path) 
override;
   void Dispatch_(const prim::LetNode* op, ffi::reflection::AccessPath path) 
override;
   void Dispatch_(const prim::AddNode* op, ffi::reflection::AccessPath path) 
override;
   void Dispatch_(const prim::SubNode* op, ffi::reflection::AccessPath path) 
override;
diff --git a/tests/python/relax/script/test_relax_script_printer.py 
b/tests/python/relax/script/test_relax_script_printer.py
index 184969514d..89fbc969c1 100644
--- a/tests/python/relax/script/test_relax_script_printer.py
+++ b/tests/python/relax/script/test_relax_script_printer.py
@@ -554,8 +554,8 @@ def 
test_primitive_bindings_roundtrip_without_prim_value_marker():
 
 
 def test_data_type_imm():
-    obj = tvm.ir.GenericConst(tvm.DataType("float32"), tvm.relax.AnyType())
-    _assert_print_lines(obj, 'R.dtype("float32")')
+    obj = tvm.ir.DataTypeImm(tvm.DataType("float32"))
+    _assert_print_lines(obj, 'T.dtype("float32")')
 
 
 def test_var():
diff --git a/tests/python/relax/test_ast_printer.py 
b/tests/python/relax/test_ast_printer.py
index 97a6960b9b..efea39ea82 100644
--- a/tests/python/relax/test_ast_printer.py
+++ b/tests/python/relax/test_ast_printer.py
@@ -681,11 +681,11 @@ def test_string_imm():
 
 
 def test_datatype_imm():
-    data_type_imm = tvm.ir.GenericConst(tvm.DataType("int32"), 
tvm.relax.AnyType())
+    data_type_imm = tvm.ir.DataTypeImm(tvm.DataType("int32"))
     data_type_str = strip_whitespace(dump_ast(data_type_imm))
     assert data_type_str == strip_whitespace(
         """
-        GenericConst(
+        DataTypeImm(
             value=int32,
             ty=AnyType()
         )
diff --git a/tests/python/relax/test_expr.py b/tests/python/relax/test_expr.py
index 93d554424e..4eaa2162ff 100644
--- a/tests/python/relax/test_expr.py
+++ b/tests/python/relax/test_expr.py
@@ -343,8 +343,8 @@ def test_string_imm():
 
 
 def test_datatype_imm():
-    d0 = tvm.ir.GenericConst(tvm.DataType("int32"), tvm.relax.AnyType())
-    d1 = tvm.ir.GenericConst(tvm.DataType("int32"), tvm.relax.AnyType())
+    d0 = tvm.ir.DataTypeImm(tvm.DataType("int32"))
+    d1 = tvm.ir.DataTypeImm(tvm.DataType("int32"))
     assert d0.value == "int32"
     _check_equal(d0, d1)
     _check_json_roundtrip(d0)
diff --git a/tests/python/script/test_basic_usage.py 
b/tests/python/script/test_basic_usage.py
index a1787e14fa..75bc8208a5 100644
--- a/tests/python/script/test_basic_usage.py
+++ b/tests/python/script/test_basic_usage.py
@@ -26,7 +26,7 @@ import pytest
 # ruff: noqa: F841
 from minilang import Value
 
-from tvm import ir
+from tvm import DataType, ir
 from tvm.ir import prim
 from tvm.ir._overload_prim_expr import EqualOp
 from tvm.script import ir as I
@@ -460,6 +460,17 @@ def 
test_ir_branch_incoming_read_and_rebinding_obeys_python_scope(language):
             return y
 
 
+def test_datatype_value_roundtrip(language):
+    # A DataType value needs only shared IR: the minimal language parses its 
shared spelling
+    # into a root-typed constant, and the shared printer reproduces that 
spelling.
+    spelling = 'I.dtype("float32")'
+    source = f"@M.function\ndef main():\n    M.record({spelling})\n"
+    value = entry.parse(source, extra_vars={"M": language.M, "I": 
I}).body[0][1]
+    ir.assert_structural_equal(value, ir.DataTypeImm(DataType("float32")))
+    printed = ir.IRModule(attrs={"dtype": value}).script()
+    assert f'I.module_attrs({{"dtype": {spelling}}})' in printed
+
+
 def test_mutating_stores_and_loop_control_keep_effect_order(language):
     # Stores evaluate RHS before target/index; augmented stores load the 
target before their RHS.
     M = language.M

Reply via email to