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-tirx.git


The following commit(s) were added to refs/heads/main by this push:
     new e9c49853 [REFACTOR] Keep layout and document data nodes nonvirtual (#8)
e9c49853 is described below

commit e9c49853ca72a06759ca7dc64db923de12a21c14
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Oct 20 20:52:15 2026 -0400

    [REFACTOR] Keep layout and document data nodes nonvirtual (#8)
    
    Keep layout and document data nodes nonvirtual. Expose layout operations
    as exported nonvirtual reference-wrapper methods with explicit FFI
    runtime-type dispatch and source-private concrete helpers, preserving
    mapping behavior and Python interfaces.
    
    Keep LayoutNode, TileLayoutNode, and ComposeLayoutNode data-only, and
    use concrete FFI destruction for document nodes.
---
 include/tvm/script/printer/doc.h                   |   3 -
 include/tvm/tirx/layout.h                          | 100 ++++++--------------
 src/backend/trn/transform/lower_trainium_layout.cc |  20 ++--
 src/tirx/ir/layout/compose_layout.cc               |  77 ++++++++-------
 src/tirx/ir/layout/layout.cc                       |  78 +++++++++++++---
 src/tirx/ir/layout/tile_canonicalize.cc            |   8 +-
 src/tirx/ir/layout/tile_core.cc                    | 104 +++++++++++----------
 src/tirx/ir/layout/tile_internal.h                 |  28 ++++++
 src/tirx/ir/layout/tile_tile_ops.cc                |  11 ++-
 src/tirx/ir/layout/utils.cc                        |   2 +-
 src/tirx/ir/tensor.cc                              |   2 +-
 src/tirx/ir/type.cc                                |   4 +-
 src/tirx/transform/flatten_tensor_utils.h          |   8 +-
 src/tirx/transform/lower_tensor_layouts.cc         |   8 +-
 14 files changed, 249 insertions(+), 204 deletions(-)

diff --git a/include/tvm/script/printer/doc.h b/include/tvm/script/printer/doc.h
index 6441b40b..16aff11d 100644
--- a/include/tvm/script/printer/doc.h
+++ b/include/tvm/script/printer/doc.h
@@ -68,9 +68,6 @@ class DocNode : public ffi::Object {
   static constexpr bool _type_mutable = true;
 
   TVM_FFI_DECLARE_OBJECT_INFO("script.printer.Doc", DocNode, ffi::Object);
-
- public:
-  virtual ~DocNode() = default;
 };
 
 /*!
diff --git a/include/tvm/tirx/layout.h b/include/tvm/tirx/layout.h
index 7c8baab9..7c2527ce 100644
--- a/include/tvm/tirx/layout.h
+++ b/include/tvm/tirx/layout.h
@@ -46,35 +46,35 @@ using ffi::Tuple;
 
 // Base class for layout
 class LayoutNode : public ffi::Object {
+ public:
+  static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
+  TVM_FFI_DECLARE_OBJECT_INFO("tirx.Layout", LayoutNode, ffi::Object);
+};
+
+class Layout : public ffi::ObjectRef {
  public:
   /*! \brief Verify if the layout is well-formed */
-  virtual void CheckWellFormed() const = 0;
+  TVM_TIRX_DLL void CheckWellFormed() const;
 
   /*! \brief Get the size of the layout (of some axis) */
-  virtual PrimExpr GetSize(ffi::Optional<ffi::String> axis_name = 
std::nullopt) const = 0;
+  TVM_TIRX_DLL PrimExpr GetSize(ffi::Optional<ffi::String> axis_name = 
std::nullopt) const;
 
   /*! \brief Get the span of the layout (of some axis) */
-  virtual PrimExpr GetSpan(ffi::Optional<ffi::String> axis_name = 
std::nullopt) const = 0;
+  TVM_TIRX_DLL PrimExpr GetSpan(ffi::Optional<ffi::String> axis_name = 
std::nullopt) const;
 
   /*! \brief Apply layout on the input coordinate and get the mapped output */
-  virtual ffi::Map<ffi::String, PrimExpr> Apply(ffi::Array<PrimExpr> coord) 
const = 0;
-  virtual ffi::Map<ffi::String, PrimExpr> Apply(PrimExpr coord) const = 0;
-  virtual ffi::Map<ffi::String, PrimExpr> Apply(const ffi::Array<PrimExpr>& 
coord,
-                                                const ffi::Array<PrimExpr>& 
shape) const;
+  TVM_TIRX_DLL ffi::Map<ffi::String, PrimExpr> Apply(ffi::Array<PrimExpr> 
coord) const;
+  TVM_TIRX_DLL ffi::Map<ffi::String, PrimExpr> Apply(PrimExpr coord) const;
+  TVM_TIRX_DLL ffi::Map<ffi::String, PrimExpr> Apply(const 
ffi::Array<PrimExpr>& coord,
+                                                     const 
ffi::Array<PrimExpr>& shape) const;
 
   /*! \brief Turn the layout to canonical form */
-  virtual Layout Canonicalize() const = 0;
+  TVM_TIRX_DLL Layout Canonicalize() const;
 
   /*! \brief Tile the current layout with a given layout */
-  virtual Layout Tile(const TileLayout& outer, const ffi::Array<PrimExpr>& 
outer_shape,
-                      const ffi::Array<PrimExpr>& inner_shape) const = 0;
+  TVM_TIRX_DLL Layout Tile(const TileLayout& outer, const 
ffi::Array<PrimExpr>& outer_shape,
+                           const ffi::Array<PrimExpr>& inner_shape) const;
 
-  static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
-  TVM_FFI_DECLARE_OBJECT_INFO("tirx.Layout", LayoutNode, ffi::Object);
-};
-
-class Layout : public ffi::ObjectRef {
- public:
   TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Layout, ffi::ObjectRef, 
LayoutNode);
 };
 
@@ -163,34 +163,16 @@ class TileLayoutNode : public LayoutNode {
         .def_ro("offset", &TileLayoutNode::offset);
   }
 
-  /*! \brief Verify if the layout is well-formed */
-  void CheckWellFormed() const final;
-
-  /*! \brief Get the size of the layout (of some axis) */
-  PrimExpr GetSize(ffi::Optional<ffi::String> axis_name = std::nullopt) const 
final;
-
-  /*! \brief Get the span of the layout (of some axis) */
-  PrimExpr GetSpan(ffi::Optional<ffi::String> axis_name = std::nullopt) const 
final;
-
-  /*! \brief Apply the input coordinate and get the mapped output */
-  ffi::Map<ffi::String, PrimExpr> Apply(ffi::Array<PrimExpr> coord) const 
final;
-  ffi::Map<ffi::String, PrimExpr> Apply(PrimExpr coord) const final;
-  /*! \brief Group-first override: if this layout can be regrouped by 
``shape``,
-   *  split each ``coord[d]`` against its group's local extents (cleaner
-   *  symbolic form than flatten+split-against-shard-shape). Otherwise fall
-   *  back to flatten+split. */
-  ffi::Map<ffi::String, PrimExpr> Apply(const ffi::Array<PrimExpr>& coord,
-                                        const ffi::Array<PrimExpr>& shape) 
const final;
-
-  /*! \brief Turn the layout to canonical form */
-  Layout Canonicalize() const final;
+  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.TileLayout", TileLayoutNode, 
LayoutNode);
+};
 
-  /*! \brief Tile the layout with an outer layout */
-  Layout Tile(const TileLayout& outer, const ffi::Array<PrimExpr>& outer_shape,
-              const ffi::Array<PrimExpr>& inner_shape) const final;
+class TileLayout : public Layout {
+ public:
+  TVM_TIRX_DLL explicit TileLayout(ffi::Array<Iter> shard, ffi::Array<Iter> 
replica,
+                                   ffi::Map<Axis, PrimExpr> offset);
 
   /*! \brief Get the shape of the shard */
-  ffi::Array<PrimExpr> GetShardShape() const;
+  TVM_TIRX_DLL ffi::Array<PrimExpr> GetShardShape() const;
 
   /*!
    * \brief Whether the tile describes contiguous row-major memory strides.
@@ -202,25 +184,17 @@ class TileLayoutNode : public LayoutNode {
   TVM_TIRX_DLL bool IsContiguous(ffi::Optional<sym::Analyzer> analyzer = 
std::nullopt) const;
 
   /*! \brief Is the layout trivial (pure memory, identical mapping) */
-  bool IsTrivial() const;
+  TVM_TIRX_DLL bool IsTrivial() const;
 
   /*! \brief Has Thread Axis */
-  bool HasThreadAxis() const;
+  TVM_TIRX_DLL bool HasThreadAxis() const;
 
   /*! \brief Get the scope pair of the layout */
-  ffi::Optional<Tuple<ThreadScope, ThreadScope>> GetScope() const;
+  TVM_TIRX_DLL ffi::Optional<Tuple<ThreadScope, ThreadScope>> GetScope() const;
 
   /*! \brief Get the default layout for the shape */
   TVM_TIRX_DLL static TileLayout DefaultLayout(ffi::Array<PrimExpr> shape);
 
-  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.TileLayout", TileLayoutNode, 
LayoutNode);
-};
-
-class TileLayout : public Layout {
- public:
-  TVM_TIRX_DLL explicit TileLayout(ffi::Array<Iter> shard, ffi::Array<Iter> 
replica,
-                                   ffi::Map<Axis, PrimExpr> offset);
-
   TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(TileLayout, Layout, 
TileLayoutNode);
   TVM_DEFINE_OBJECT_REF_COW_METHOD(TileLayoutNode);
 };
@@ -253,28 +227,6 @@ class ComposeLayoutNode : public LayoutNode {
         .def_ro("tile_layout", &ComposeLayoutNode::tile_layout);
   }
 
-  /*! \brief Verify if the layout is well-formed */
-  void CheckWellFormed() const final;
-
-  /*! \brief Get the size (of some axis) of the layout */
-  PrimExpr GetSize(ffi::Optional<ffi::String> axis_name = std::nullopt) const 
final;
-
-  /*! \brief Get the span (of some axis) of the layout */
-  PrimExpr GetSpan(ffi::Optional<ffi::String> axis_name = std::nullopt) const 
final;
-
-  /*! \brief Apply the input coordinate and get the mapped output */
-  ffi::Map<ffi::String, PrimExpr> Apply(ffi::Array<PrimExpr> coord) const 
final;
-  ffi::Map<ffi::String, PrimExpr> Apply(PrimExpr coord) const final;
-  ffi::Map<ffi::String, PrimExpr> Apply(const ffi::Array<PrimExpr>& coord,
-                                        const ffi::Array<PrimExpr>& shape) 
const final;
-
-  /*! \brief Turn the layout to canonical form */
-  Layout Canonicalize() const final;
-
-  /*! \brief Tile the layout with an outer layout */
-  Layout Tile(const TileLayout& outer, const ffi::Array<PrimExpr>& outer_shape,
-              const ffi::Array<PrimExpr>& inner_shape) const final;
-
   TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.ComposeLayout", ComposeLayoutNode, 
LayoutNode);
 };
 
diff --git a/src/backend/trn/transform/lower_trainium_layout.cc 
b/src/backend/trn/transform/lower_trainium_layout.cc
index ef2739e6..9166d4a5 100644
--- a/src/backend/trn/transform/lower_trainium_layout.cc
+++ b/src/backend/trn/transform/lower_trainium_layout.cc
@@ -175,20 +175,20 @@ class TrainiumLayoutApplier : public 
tirx::StmtExprMutatorWithAnalyzer {
     TensorVar flattened = buf;
     ffi::ObjectPtr<TensorTypeNode> type;
     if (IsTrainiumLayout(trn_layout)) {
+      const Layout& layout = buf.ty()->layout.value();
       ffi::Array<PrimExpr> new_shape =
-          buf.scope() == scope_trn_psum
-              ? ffi::Array<PrimExpr>{trn_layout->GetSpan(ffi::String("Bank")),
-                                     trn_layout->GetSize(ffi::String("P")),
-                                     trn_layout->GetSpan(ffi::String("F"))}
-              : ffi::Array<PrimExpr>{trn_layout->GetSize(ffi::String("P")),
-                                     trn_layout->GetSpan(ffi::String("F"))};
+          buf.scope() == scope_trn_psum ? 
ffi::Array<PrimExpr>{layout.GetSpan(ffi::String("Bank")),
+                                                               
layout.GetSize(ffi::String("P")),
+                                                               
layout.GetSpan(ffi::String("F"))}
+                                        : 
ffi::Array<PrimExpr>{layout.GetSize(ffi::String("P")),
+                                                               
layout.GetSpan(ffi::String("F"))};
       flattened = buf;
       type = ffi::make_object<TensorTypeNode>(*flattened.ty().get());
       type->shape = new_shape;
       type->layout = std::nullopt;
     } else if (is_alloc) {
       if (auto tile_layout = buf.ty()->layout.as<TileLayoutNode>();
-          tile_layout && tile_layout->HasThreadAxis()) {
+          tile_layout && ffi::GetRef<TileLayout>(tile_layout).HasThreadAxis()) 
{
         sym::Analyzer ana;
         PrimExpr mem_span = IntImm::Int32(1);
         for (const auto& iter : tile_layout->shard) {
@@ -281,7 +281,7 @@ class TrainiumLayoutApplier : public 
tirx::StmtExprMutatorWithAnalyzer {
                    .as_or_throw<Layout>();
       auto tile_layout = layout.as<TileLayoutNode>();
       if (IsTrainiumLayout(tile_layout)) {
-        auto coord = layout->Apply(indices, tensor.shape());
+        auto coord = layout.Apply(indices, tensor.shape());
         std::vector<PrimExpr> res;
         for (const auto& axis : tensor.scope() == scope_trn_psum
                                     ? ffi::Array<ffi::String>{"Bank", "P", "F"}
@@ -295,12 +295,12 @@ class TrainiumLayoutApplier : public 
tirx::StmtExprMutatorWithAnalyzer {
         }
         return res;
       }
-      if (tile_layout && tile_layout->HasThreadAxis()) {
+      if (tile_layout && ffi::GetRef<TileLayout>(tile_layout).HasThreadAxis()) 
{
         LOG(FATAL) << "Cannot lower direct TensorLoad/TensorStore on a tensor 
with thread-axis "
                    << "layout: unable to verify that the coordinate matches 
the current thread. "
                    << "Use .view() + .local() to decompose thread and memory 
axes.";
       }
-      auto res = layout->Canonicalize()->Apply(indices, tensor.shape());
+      auto res = layout.Canonicalize().Apply(indices, tensor.shape());
       TVM_FFI_ICHECK_EQ(res.size(), 1) << "Expected a single element offset";
       return {analyzer_->Simplify((*res.begin()).second)};
     }
diff --git a/src/tirx/ir/layout/compose_layout.cc 
b/src/tirx/ir/layout/compose_layout.cc
index b09095aa..1f91214f 100644
--- a/src/tirx/ir/layout/compose_layout.cc
+++ b/src/tirx/ir/layout/compose_layout.cc
@@ -153,7 +153,7 @@ ffi::Map<ffi::String, PrimExpr> ApplyStructured(const 
ComposeLayoutNode* layout,
     }
   }
 
-  auto mapped = tile->Apply(coord);
+  auto mapped = tile.Apply(coord);
   TVM_FFI_ICHECK(mapped.size() == 1 && mapped.find("m") != mapped.end());
   PrimExpr m = mapped["m"];
   auto fallback = [&]() {
@@ -308,7 +308,7 @@ ComposeLayout::ComposeLayout(int per_element, int 
swizzle_len, int atom_len, Til
   n->swizzle_len = swizzle_len;
   n->atom_len = atom_len;
   n->swizzle_inner = swizzle_inner;
-  n->CheckWellFormed();
+  layout_details::CheckWellFormed(n.get());
   int swizzle_mask = (1 << swizzle_len) - 1;
   n->inner_mask = swizzle_mask;
   n->outer_mask = swizzle_mask << atom_len;
@@ -323,56 +323,64 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   });
 }
 
-void ComposeLayoutNode::CheckWellFormed() const {
-  TVM_FFI_CHECK(per_element >= 0 && swizzle_len >= 0 && atom_len >= 
swizzle_len, ValueError)
+void layout_details::CheckWellFormed(const ComposeLayoutNode* layout) {
+  TVM_FFI_CHECK(layout->per_element >= 0 && layout->swizzle_len >= 0 &&
+                    layout->atom_len >= layout->swizzle_len,
+                ValueError)
       << "A compose layout requires nonnegative per_element and swizzle_len, 
and "
       << "atom_len >= swizzle_len";
-  tile_layout->CheckWellFormed();
+  layout->tile_layout.CheckWellFormed();
 }
 
-PrimExpr ComposeLayoutNode::GetSize(ffi::Optional<ffi::String> axis_name) 
const {
+PrimExpr layout_details::GetSize(const ComposeLayoutNode* layout,
+                                 ffi::Optional<ffi::String> axis_name) {
   TVM_FFI_ICHECK(!axis_name.has_value())
       << "ValueError: axis_name is not supported for compose layout";
-  return tile_layout->GetSize(axis_name);
+  return layout->tile_layout.GetSize(axis_name);
 }
 
-PrimExpr ComposeLayoutNode::GetSpan(ffi::Optional<ffi::String> axis_name) 
const {
+PrimExpr layout_details::GetSpan(const ComposeLayoutNode* layout,
+                                 ffi::Optional<ffi::String> axis_name) {
   TVM_FFI_ICHECK(!axis_name.has_value())
       << "ValueError: axis_name is not supported for compose layout";
-  return tile_layout->GetSpan(axis_name);
+  return layout->tile_layout.GetSpan(axis_name);
 }
 
-ffi::Map<ffi::String, PrimExpr> ComposeLayoutNode::Apply(ffi::Array<PrimExpr> 
coord) const {
-  return ApplyStructured(this, tile_layout, std::move(coord));
+ffi::Map<ffi::String, PrimExpr> layout_details::Apply(const ComposeLayoutNode* 
layout,
+                                                      ffi::Array<PrimExpr> 
coord) {
+  return ApplyStructured(layout, layout->tile_layout, std::move(coord));
 }
 
-ffi::Map<ffi::String, PrimExpr> ComposeLayoutNode::Apply(PrimExpr coord) const 
{
-  auto res = tile_layout->Apply(coord);
+ffi::Map<ffi::String, PrimExpr> layout_details::Apply(const ComposeLayoutNode* 
layout,
+                                                      PrimExpr coord) {
+  auto res = layout->tile_layout.Apply(coord);
   TVM_FFI_ICHECK(res.size() == 1 && res.find("m") != res.end());
   auto m = res["m"];
   // Inline the swizzle XOR (formerly SwizzleLayoutNode::Apply): the swizzle
   // operates on the tile-mapped coordinate ``m``.
   auto f = [&](const PrimExpr& x) -> PrimExpr {
-    if (swizzle_inner) {
-      return x ^ ((x & outer_mask) >> atom_len);
+    if (layout->swizzle_inner) {
+      return x ^ ((x & layout->outer_mask) >> layout->atom_len);
     } else {
-      return x ^ ((x & inner_mask) << atom_len);
+      return x ^ ((x & layout->inner_mask) << layout->atom_len);
     }
   };
-  auto base = 1 << per_element;
+  auto base = 1 << layout->per_element;
   sym::Analyzer analyzer;
-  return {{"m", analyzer->Simplify((f(floordiv(m, base)) << per_element) + 
floormod(m, base))}};
+  return {
+      {"m", analyzer->Simplify((f(floordiv(m, base)) << layout->per_element) + 
floormod(m, base))}};
 }
 
-ffi::Map<ffi::String, PrimExpr> ComposeLayoutNode::Apply(const 
ffi::Array<PrimExpr>& coord,
-                                                         const 
ffi::Array<PrimExpr>& shape) const {
+ffi::Map<ffi::String, PrimExpr> layout_details::Apply(const ComposeLayoutNode* 
layout,
+                                                      const 
ffi::Array<PrimExpr>& coord,
+                                                      const 
ffi::Array<PrimExpr>& shape) {
   TVM_FFI_ICHECK_EQ(coord.size(), shape.size())
       << "ValueError: The size of coord and shape should be equal";
-  auto grouped = TryGroup(tile_layout, shape);
+  auto grouped = TryGroup(layout->tile_layout, shape);
   if (!grouped.has_value()) {
-    auto mapped = tile_layout->Apply(coord, shape);
+    auto mapped = layout->tile_layout.Apply(coord, shape);
     TVM_FFI_ICHECK(mapped.size() == 1 && mapped.find("m") != mapped.end());
-    return {{"m", ApplyFullSwizzle(this, mapped["m"])}};
+    return {{"m", ApplyFullSwizzle(layout, mapped["m"])}};
   }
 
   const auto& [grouped_tile, separators] = grouped.value();
@@ -394,25 +402,28 @@ ffi::Map<ffi::String, PrimExpr> 
ComposeLayoutNode::Apply(const ffi::Array<PrimEx
       shard_coord.Set(i, split[j]);
     }
   }
-  return ApplyStructured(this, grouped_tile, std::move(shard_coord));
+  return ApplyStructured(layout, grouped_tile, std::move(shard_coord));
 }
 
-Layout ComposeLayoutNode::Canonicalize() const {
-  auto tile_normalized = tile_layout->Canonicalize().as<TileLayout>().value();
-  return ComposeLayout(per_element, swizzle_len, atom_len, tile_normalized, 
swizzle_inner);
+Layout layout_details::Canonicalize(const ComposeLayoutNode* layout) {
+  auto tile_normalized = 
layout->tile_layout.Canonicalize().as<TileLayout>().value();
+  return ComposeLayout(layout->per_element, layout->swizzle_len, 
layout->atom_len, tile_normalized,
+                       layout->swizzle_inner);
 }
 
-Layout ComposeLayoutNode::Tile(const TileLayout& outer, const 
ffi::Array<PrimExpr>& outer_shape,
-                               const ffi::Array<PrimExpr>& inner_shape) const {
+Layout layout_details::Tile(const ComposeLayoutNode* layout, const TileLayout& 
outer,
+                            const ffi::Array<PrimExpr>& outer_shape,
+                            const ffi::Array<PrimExpr>& inner_shape) {
   // A bare swizzle (ComposeLayout with a trivial tile) carries only the 
swizzle
   // period, not a tile matching `inner_shape`; substitute an identity tile 
over
   // the inner product first, matching the former SwizzleLayoutNode::Tile.
-  TileLayout base = this->tile_layout;
-  if (base->IsTrivial()) {
+  TileLayout base = layout->tile_layout;
+  if (base.IsTrivial()) {
     base = IdentityTileLayout(inner_shape);
   }
-  auto tiled_B = base->Tile(outer, outer_shape, 
inner_shape).as<TileLayout>().value();
-  return ComposeLayout(per_element, swizzle_len, atom_len, tiled_B, 
swizzle_inner);
+  auto tiled_B = base.Tile(outer, outer_shape, 
inner_shape).as<TileLayout>().value();
+  return ComposeLayout(layout->per_element, layout->swizzle_len, 
layout->atom_len, tiled_B,
+                       layout->swizzle_inner);
 }
 
 }  // namespace tirx
diff --git a/src/tirx/ir/layout/layout.cc b/src/tirx/ir/layout/layout.cc
index e1135fb4..6dc91ef1 100644
--- a/src/tirx/ir/layout/layout.cc
+++ b/src/tirx/ir/layout/layout.cc
@@ -17,41 +17,89 @@
  * under the License.
  */
 
-#include "utils.h"
+#include "tile_internal.h"
 
 namespace tvm {
 namespace tirx {
 
-/**************** Layout ****************/
-ffi::Map<ffi::String, PrimExpr> LayoutNode::Apply(const ffi::Array<PrimExpr>& 
coord,
-                                                  const ffi::Array<PrimExpr>& 
shape) const {
-  TVM_FFI_ICHECK_EQ(coord.size(), shape.size())
-      << "ValueError: The size of coord and shape should be equal";
-  return Apply(FlattenCoord(coord, shape));
+namespace {
+
+template <typename F>
+decltype(auto) DispatchLayout(const Layout& layout, F&& f) {
+  if (const auto* tile = layout.as<TileLayoutNode>()) {
+    return f(tile);
+  }
+  if (const auto* compose = layout.as<ComposeLayoutNode>()) {
+    return f(compose);
+  }
+  TVM_FFI_THROW(TypeError) << "Unsupported layout type: " << 
layout->GetTypeKey();
+}
+
+}  // namespace
+
+void Layout::CheckWellFormed() const {
+  DispatchLayout(*this, [](const auto* node) { 
layout_details::CheckWellFormed(node); });
+}
+
+PrimExpr Layout::GetSize(ffi::Optional<ffi::String> axis_name) const {
+  return DispatchLayout(*this,
+                        [&](const auto* node) { return 
layout_details::GetSize(node, axis_name); });
+}
+
+PrimExpr Layout::GetSpan(ffi::Optional<ffi::String> axis_name) const {
+  return DispatchLayout(*this,
+                        [&](const auto* node) { return 
layout_details::GetSpan(node, axis_name); });
+}
+
+ffi::Map<ffi::String, PrimExpr> Layout::Apply(ffi::Array<PrimExpr> coord) 
const {
+  return DispatchLayout(
+      *this, [&](const auto* node) { return layout_details::Apply(node, 
std::move(coord)); });
+}
+
+ffi::Map<ffi::String, PrimExpr> Layout::Apply(PrimExpr coord) const {
+  return DispatchLayout(
+      *this, [&](const auto* node) { return layout_details::Apply(node, 
std::move(coord)); });
+}
+
+ffi::Map<ffi::String, PrimExpr> Layout::Apply(const ffi::Array<PrimExpr>& 
coord,
+                                              const ffi::Array<PrimExpr>& 
shape) const {
+  return DispatchLayout(
+      *this, [&](const auto* node) { return layout_details::Apply(node, coord, 
shape); });
+}
+
+Layout Layout::Canonicalize() const {
+  return DispatchLayout(*this, [](const auto* node) { return 
layout_details::Canonicalize(node); });
+}
+
+Layout Layout::Tile(const TileLayout& outer, const ffi::Array<PrimExpr>& 
outer_shape,
+                    const ffi::Array<PrimExpr>& inner_shape) const {
+  return DispatchLayout(*this, [&](const auto* node) {
+    return layout_details::Tile(node, outer, outer_shape, inner_shape);
+  });
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   auto def = refl::GlobalDef();
-  def.def("tirx.LayoutCheckWellFormed", [](Layout layout) { return 
layout->CheckWellFormed(); });
+  def.def("tirx.LayoutCheckWellFormed", [](Layout layout) { return 
layout.CheckWellFormed(); });
   def.def("tirx.LayoutGetSize", [](Layout layout, ffi::Optional<ffi::String> 
axis_name) {
-    return layout->GetSize(axis_name);
+    return layout.GetSize(axis_name);
   });
   def.def("tirx.LayoutGetSpan", [](Layout layout, ffi::Optional<ffi::String> 
axis_name) {
-    return layout->GetSpan(axis_name);
+    return layout.GetSpan(axis_name);
   });
   def.def("tirx.LayoutApplyWithShape",
           [](Layout layout, ffi::Array<PrimExpr> coord, ffi::Array<PrimExpr> 
shape) {
-            return layout->Apply(coord, shape);
+            return layout.Apply(coord, shape);
           });
   def.def("tirx.LayoutApply",
-          [](Layout layout, ffi::Array<PrimExpr> coord) { return 
layout->Apply(coord); });
+          [](Layout layout, ffi::Array<PrimExpr> coord) { return 
layout.Apply(coord); });
   def.def("tirx.LayoutApplyLinear",
-          [](Layout layout, PrimExpr coord) { return layout->Apply(coord); });
-  def.def("tirx.LayoutCanonicalize", [](Layout layout) { return 
layout->Canonicalize(); });
+          [](Layout layout, PrimExpr coord) { return layout.Apply(coord); });
+  def.def("tirx.LayoutCanonicalize", [](Layout layout) { return 
layout.Canonicalize(); });
   def.def("tirx.LayoutTile", [](Layout layout, TileLayout outer, 
ffi::Array<PrimExpr> outer_shape,
                                 ffi::Array<PrimExpr> inner_shape) {
-    return layout->Tile(outer, outer_shape, inner_shape);
+    return layout.Tile(outer, outer_shape, inner_shape);
   });
 }
 
diff --git a/src/tirx/ir/layout/tile_canonicalize.cc 
b/src/tirx/ir/layout/tile_canonicalize.cc
index 37687556..f0be540d 100644
--- a/src/tirx/ir/layout/tile_canonicalize.cc
+++ b/src/tirx/ir/layout/tile_canonicalize.cc
@@ -20,7 +20,7 @@
 /*
  * Canonicalization routines for TileLayout.
  */
-#include "utils.h"
+#include "tile_internal.h"
 
 namespace tvm {
 namespace tirx {
@@ -81,7 +81,7 @@ TileLayout FuseContiguousShardIters(TileLayout layout) {
 
 TileLayout FuseAxesByScope(TileLayout layout) {
   // Step 1: Get the target and scope information
-  auto scope_pair_opt = layout->GetScope();
+  auto scope_pair_opt = layout.GetScope();
   ffi::Optional<Target> target = Target::Current();
   if (!scope_pair_opt.has_value() || !target.has_value()) {
     return layout;
@@ -119,9 +119,9 @@ TileLayout FuseAxesByScope(TileLayout layout) {
   return result;
 }
 
-Layout TileLayoutNode::Canonicalize() const {
+Layout layout_details::Canonicalize(const TileLayoutNode* layout) {
   // 0. Remove unit iters in shard
-  TileLayout res = RemoveUnitIters(ffi::GetRef<TileLayout>(this));
+  TileLayout res = RemoveUnitIters(ffi::GetRef<TileLayout>(layout));
   // 1. Remove zero offset
   res = RemoveZeroOffsets(res);
   // 2. Try fuse axes
diff --git a/src/tirx/ir/layout/tile_core.cc b/src/tirx/ir/layout/tile_core.cc
index 12c224a7..38b0b7b4 100644
--- a/src/tirx/ir/layout/tile_core.cc
+++ b/src/tirx/ir/layout/tile_core.cc
@@ -196,12 +196,14 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   });
 }
 
-void TileLayoutNode::CheckWellFormed() const {
-  TVM_FFI_CHECK(GetScope().has_value() || !HasThreadAxis(), ValueError)
+void layout_details::CheckWellFormed(const TileLayoutNode* layout) {
+  const auto tile = ffi::GetRef<TileLayout>(layout);
+  TVM_FFI_CHECK(tile.GetScope().has_value() || !tile.HasThreadAxis(), 
ValueError)
       << "A tile layout with thread axes must have a connected scope";
 }
 
-PrimExpr TileLayoutNode::GetSize(ffi::Optional<ffi::String> axis_name) const {
+PrimExpr layout_details::GetSize(const TileLayoutNode* layout,
+                                 ffi::Optional<ffi::String> axis_name) {
   auto filter = [&](const Iter& iter, PrimExpr acc) {
     if (!axis_name.has_value() || iter->axis.name() == axis_name.value()) {
       return acc * iter->extent;
@@ -209,35 +211,38 @@ PrimExpr 
TileLayoutNode::GetSize(ffi::Optional<ffi::String> axis_name) const {
     return acc;
   };
   PrimExpr res = 1;
-  for (const auto& iter : shard) {
+  for (const auto& iter : layout->shard) {
     res = filter(iter, res);
   }
   return res;
 }
 
-PrimExpr TileLayoutNode::GetSpan(ffi::Optional<ffi::String> axis_name) const {
+PrimExpr layout_details::GetSpan(const TileLayoutNode* layout,
+                                 ffi::Optional<ffi::String> axis_name) {
   sym::Analyzer analyzer;
   PrimExpr result = 1;
   auto filter = [&](const Axis& axis) { return AxisMatchesFilter(axis, 
axis_name); };
 
-  for (const auto& iter : shard) {
+  for (const auto& iter : layout->shard) {
     if (filter(iter->axis)) result += (iter->extent - 1) * iter->stride;
   }
-  for (const auto& iter : replica) {
+  for (const auto& iter : layout->replica) {
     if (filter(iter->axis)) result += (iter->extent - 1) * iter->stride;
   }
-  for (const auto& [axis, off] : offset) {
+  for (const auto& [axis, off] : layout->offset) {
     if (filter(axis)) result += off;
   }
   return analyzer->Simplify(result);
 }
 
-ffi::Map<ffi::String, PrimExpr> TileLayoutNode::Apply(PrimExpr coord) const {
-  return Apply(SplitCoord(coord, GetShardShape()));
+ffi::Map<ffi::String, PrimExpr> layout_details::Apply(const TileLayoutNode* 
layout,
+                                                      PrimExpr coord) {
+  return Apply(layout, SplitCoord(coord, 
ffi::GetRef<TileLayout>(layout).GetShardShape()));
 }
 
-ffi::Map<ffi::String, PrimExpr> TileLayoutNode::Apply(const 
ffi::Array<PrimExpr>& coord,
-                                                      const 
ffi::Array<PrimExpr>& shape) const {
+ffi::Map<ffi::String, PrimExpr> layout_details::Apply(const TileLayoutNode* 
layout,
+                                                      const 
ffi::Array<PrimExpr>& coord,
+                                                      const 
ffi::Array<PrimExpr>& shape) {
   TVM_FFI_ICHECK_EQ(coord.size(), shape.size())
       << "ValueError: The size of coord and shape should be equal";
   // Group-first path: if this layout can be regrouped by ``shape`` (the input
@@ -247,7 +252,8 @@ ffi::Map<ffi::String, PrimExpr> TileLayoutNode::Apply(const 
ffi::Array<PrimExpr>
   // symbolic form small (local mod/divs) and avoids the cross-dim noise of the
   // flatten+split-against-shard-shape round-trip. Equivalent numerical output,
   // much friendlier for sym.Analyzer downstream.
-  if (auto grouped_opt = TryGroup(ffi::GetRef<TileLayout>(this), shape); 
grouped_opt.has_value()) {
+  if (auto grouped_opt = TryGroup(ffi::GetRef<TileLayout>(layout), shape);
+      grouped_opt.has_value()) {
     auto& [grouped, seps] = *grouped_opt;
     ffi::Array<PrimExpr> per_shard_coords;
     per_shard_coords.reserve(grouped->shard.size());
@@ -267,29 +273,30 @@ ffi::Map<ffi::String, PrimExpr> 
TileLayoutNode::Apply(const ffi::Array<PrimExpr>
         per_shard_coords.Set(i, split[j]);
       }
     }
-    return grouped->Apply(per_shard_coords);
+    return grouped.Apply(per_shard_coords);
   }
   // Fallback: flatten across input shape then split against shard shape.
-  return LayoutNode::Apply(coord, shape);
+  return Apply(layout, FlattenCoord(coord, shape));
 }
 
-ffi::Map<ffi::String, PrimExpr> TileLayoutNode::Apply(Array<PrimExpr> coord) 
const {
+ffi::Map<ffi::String, PrimExpr> layout_details::Apply(const TileLayoutNode* 
layout,
+                                                      Array<PrimExpr> coord) {
   sym::Analyzer analyzer;
-  TVM_FFI_ICHECK_EQ(coord.size(), shard.size())
+  TVM_FFI_ICHECK_EQ(coord.size(), layout->shard.size())
       << "Coordinate size must match the number of shard axes";
   std::unordered_map<ffi::String, PrimExpr> result;
-  for (size_t i = 0; i < shard.size(); ++i) {
-    auto it = result.find(shard[i]->axis.name());
+  for (size_t i = 0; i < layout->shard.size(); ++i) {
+    auto it = result.find(layout->shard[i]->axis.name());
     if (it == result.end()) {
-      result.insert_or_assign(shard[i]->axis.name(),
-                              analyzer->Simplify(coord[i] * shard[i]->stride));
+      result.insert_or_assign(layout->shard[i]->axis.name(),
+                              analyzer->Simplify(coord[i] * 
layout->shard[i]->stride));
     } else {
-      result.insert_or_assign(shard[i]->axis.name(),
-                              analyzer->Simplify(it->second + coord[i] * 
shard[i]->stride));
+      result.insert_or_assign(layout->shard[i]->axis.name(),
+                              analyzer->Simplify(it->second + coord[i] * 
layout->shard[i]->stride));
     }
   }
   // Add offset to the result
-  for (const auto& [axis, off] : offset) {
+  for (const auto& [axis, off] : layout->offset) {
     auto it = result.find(axis.name());
     if (it == result.end()) {
       result.insert_or_assign(axis.name(), analyzer->Simplify(off));
@@ -300,17 +307,17 @@ ffi::Map<ffi::String, PrimExpr> 
TileLayoutNode::Apply(Array<PrimExpr> coord) con
   return result;
 }
 
-ffi::Array<PrimExpr> TileLayoutNode::GetShardShape() const {
-  return shard.Map([](const Iter& iter) { return iter->extent; });
+ffi::Array<PrimExpr> TileLayout::GetShardShape() const {
+  return get()->shard.Map([](const Iter& iter) { return iter->extent; });
 }
 
-bool TileLayoutNode::IsContiguous(ffi::Optional<sym::Analyzer> analyzer) const 
{
-  if (!replica.empty()) return false;
+bool TileLayout::IsContiguous(ffi::Optional<sym::Analyzer> analyzer) const {
+  if (!get()->replica.empty()) return false;
   static const Axis memory_axis = Axis::Get("m");
-  for (const auto& [axis, origin] : offset) {
+  for (const auto& [axis, origin] : get()->offset) {
     if (axis != memory_axis) return false;
   }
-  for (const auto& iter : shard) {
+  for (const auto& iter : get()->shard) {
     if (iter->axis != memory_axis) return false;
   }
   prim::ExprDeepEqual deep_equal;
@@ -319,40 +326,41 @@ bool 
TileLayoutNode::IsContiguous(ffi::Optional<sym::Analyzer> analyzer) const {
     if (!analyzer.has_value()) analyzer = sym::Analyzer();
     return analyzer.value()->CanProveEqual(lhs, rhs);
   };
-  PrimExpr stride = IntImm(shard.empty() ? PrimType::Int(64) : 
shard.back()->stride.ty(), 1);
-  for (size_t i = shard.size(); i > 0; --i) {
-    const auto& iter = shard[i - 1];
+  PrimExpr stride =
+      IntImm(get()->shard.empty() ? PrimType::Int(64) : 
get()->shard.back()->stride.ty(), 1);
+  for (size_t i = get()->shard.size(); i > 0; --i) {
+    const auto& iter = get()->shard[i - 1];
     if (!prim::IsOne(iter->extent) && !equal(iter->stride, stride)) return 
false;
     stride = stride * iter->extent;
   }
   return true;
 }
 
-bool TileLayoutNode::IsTrivial() const {
-  if (shard.size() > 1) return false;
-  if (shard.size() == 1) {
-    if (!shard[0]->axis->IsMemoryAxis() || !IsOne(shard[0]->stride)) return 
false;
+bool TileLayout::IsTrivial() const {
+  if (get()->shard.size() > 1) return false;
+  if (get()->shard.size() == 1) {
+    if (!get()->shard[0]->axis->IsMemoryAxis() || 
!IsOne(get()->shard[0]->stride)) return false;
   }
-  return replica.size() == 0 && offset.size() == 0;
+  return get()->replica.size() == 0 && get()->offset.size() == 0;
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
   namespace refl = tvm::ffi::reflection;
   refl::GlobalDef().def("tirx.TileLayoutIsContiguous",
                         [](const TileLayout& layout, 
ffi::Optional<sym::Analyzer> analyzer) {
-                          return layout->IsContiguous(std::move(analyzer));
+                          return layout.IsContiguous(std::move(analyzer));
                         });
   refl::GlobalDef().def("tirx.TileLayoutIsTrivial", [](const TileLayout& 
layout) {
-    return layout->Canonicalize().as<TileLayout>().value()->IsTrivial();
+    return layout.Canonicalize().as<TileLayout>().value().IsTrivial();
   });
 }
 
-bool TileLayoutNode::HasThreadAxis() const {
-  return std::any_of(shard.begin(), shard.end(),
+bool TileLayout::HasThreadAxis() const {
+  return std::any_of(get()->shard.begin(), get()->shard.end(),
                      [](const Iter& iter) { return iter->axis->IsThreadAxis(); 
});
 }
 
-ffi::Optional<ffi::Tuple<ThreadScope, ThreadScope>> TileLayoutNode::GetScope() 
const {
+ffi::Optional<ffi::Tuple<ThreadScope, ThreadScope>> TileLayout::GetScope() 
const {
   if (!HasThreadAxis()) return std::nullopt;
 
   std::unordered_map<ThreadScope, ThreadScope, ffi::ObjectPtrHash, 
ffi::ObjectPtrEqual> scope_map;
@@ -382,9 +390,9 @@ ffi::Optional<ffi::Tuple<ThreadScope, ThreadScope>> 
TileLayoutNode::GetScope() c
     TVM_FFI_ICHECK(inserted || it->second.same_as(scope))
         << "Ill-formed tile layout: conflicting scopes for " << subscope;
   };
-  for (const auto& iter : shard) check_axis(iter->axis);
-  for (const auto& iter : replica) check_axis(iter->axis);
-  for (const auto& [axis, off] : offset) check_axis(axis);
+  for (const auto& iter : get()->shard) check_axis(iter->axis);
+  for (const auto& iter : get()->replica) check_axis(iter->axis);
+  for (const auto& [axis, off] : get()->offset) check_axis(axis);
 
   ThreadScope outer_most = inner_most.value();
   size_t count = 0;
@@ -398,7 +406,7 @@ ffi::Optional<ffi::Tuple<ThreadScope, ThreadScope>> 
TileLayoutNode::GetScope() c
   return Tuple<ThreadScope, ThreadScope>{inner_most.value(), outer_most};
 }
 
-TileLayout TileLayoutNode::DefaultLayout(ffi::Array<PrimExpr> shape) {
+TileLayout TileLayout::DefaultLayout(ffi::Array<PrimExpr> shape) {
   Array<Iter> shard;
   auto strides = GetDefaultStrides(shape);
   for (size_t i = 0; i < shape.size(); ++i) {
@@ -412,7 +420,7 @@ TVM_FFI_STATIC_INIT_BLOCK() {
   refl::GlobalDef().def(
       "tirx.TileLayoutGetScope",
       [](const TileLayout& layout) -> ffi::Optional<ffi::Tuple<ThreadScope, 
ThreadScope>> {
-        return layout->GetScope();
+        return layout.GetScope();
       });
 }
 
diff --git a/src/tirx/ir/layout/tile_internal.h 
b/src/tirx/ir/layout/tile_internal.h
index 1c3fbcb7..c0233840 100644
--- a/src/tirx/ir/layout/tile_internal.h
+++ b/src/tirx/ir/layout/tile_internal.h
@@ -30,6 +30,34 @@
 namespace tvm {
 namespace tirx {
 
+namespace layout_details {
+
+void CheckWellFormed(const TileLayoutNode* layout);
+PrimExpr GetSize(const TileLayoutNode* layout, ffi::Optional<ffi::String> 
axis_name);
+PrimExpr GetSpan(const TileLayoutNode* layout, ffi::Optional<ffi::String> 
axis_name);
+ffi::Map<ffi::String, PrimExpr> Apply(const TileLayoutNode* layout, 
ffi::Array<PrimExpr> coord);
+ffi::Map<ffi::String, PrimExpr> Apply(const TileLayoutNode* layout, PrimExpr 
coord);
+ffi::Map<ffi::String, PrimExpr> Apply(const TileLayoutNode* layout,
+                                      const ffi::Array<PrimExpr>& coord,
+                                      const ffi::Array<PrimExpr>& shape);
+Layout Canonicalize(const TileLayoutNode* layout);
+Layout Tile(const TileLayoutNode* layout, const TileLayout& outer,
+            const ffi::Array<PrimExpr>& outer_shape, const 
ffi::Array<PrimExpr>& inner_shape);
+
+void CheckWellFormed(const ComposeLayoutNode* layout);
+PrimExpr GetSize(const ComposeLayoutNode* layout, ffi::Optional<ffi::String> 
axis_name);
+PrimExpr GetSpan(const ComposeLayoutNode* layout, ffi::Optional<ffi::String> 
axis_name);
+ffi::Map<ffi::String, PrimExpr> Apply(const ComposeLayoutNode* layout, 
ffi::Array<PrimExpr> coord);
+ffi::Map<ffi::String, PrimExpr> Apply(const ComposeLayoutNode* layout, 
PrimExpr coord);
+ffi::Map<ffi::String, PrimExpr> Apply(const ComposeLayoutNode* layout,
+                                      const ffi::Array<PrimExpr>& coord,
+                                      const ffi::Array<PrimExpr>& shape);
+Layout Canonicalize(const ComposeLayoutNode* layout);
+Layout Tile(const ComposeLayoutNode* layout, const TileLayout& outer,
+            const ffi::Array<PrimExpr>& outer_shape, const 
ffi::Array<PrimExpr>& inner_shape);
+
+}  // namespace layout_details
+
 // Group a tile layout's shard by a logical shape, returning the grouped 
layout and separators.
 std::pair<TileLayout, std::vector<int64_t>> Group(TileLayout layout,
                                                   const ffi::Array<PrimExpr>& 
shape);
diff --git a/src/tirx/ir/layout/tile_tile_ops.cc 
b/src/tirx/ir/layout/tile_tile_ops.cc
index 961365b7..b430527b 100644
--- a/src/tirx/ir/layout/tile_tile_ops.cc
+++ b/src/tirx/ir/layout/tile_tile_ops.cc
@@ -122,10 +122,11 @@ TVM_FFI_STATIC_INIT_BLOCK() {
       });
 }
 
-Layout TileLayoutNode::Tile(const TileLayout& outer_in, const Array<PrimExpr>& 
outer_shape,
-                            const Array<PrimExpr>& inner_shape) const {
-  auto outer = outer_in->Canonicalize().as<TileLayout>().value();
-  auto inner = 
ffi::GetRef<TileLayout>(this)->Canonicalize().as<TileLayout>().value();
+Layout layout_details::Tile(const TileLayoutNode* layout, const TileLayout& 
outer_in,
+                            const Array<PrimExpr>& outer_shape,
+                            const Array<PrimExpr>& inner_shape) {
+  auto outer = outer_in.Canonicalize().as<TileLayout>().value();
+  auto inner = 
ffi::GetRef<TileLayout>(layout).Canonicalize().as<TileLayout>().value();
 
   TVM_FFI_ICHECK_EQ(outer_shape.size(), inner_shape.size())
       << "Outer and inner shape size must match";
@@ -184,7 +185,7 @@ Layout TileLayoutNode::Tile(const TileLayout& outer_in, 
const Array<PrimExpr>& o
     }
   }
 
-  return TileLayout(tile_shard, tile_rep, tile_offset)->Canonicalize();
+  return TileLayout(tile_shard, tile_rep, tile_offset).Canonicalize();
 }
 
 }  // namespace tirx
diff --git a/src/tirx/ir/layout/utils.cc b/src/tirx/ir/layout/utils.cc
index 56b4dd0f..785a1b47 100644
--- a/src/tirx/ir/layout/utils.cc
+++ b/src/tirx/ir/layout/utils.cc
@@ -45,7 +45,7 @@ ffi::Map<ffi::String, PrimExpr> BuildSpanMap(const 
TileLayout& layout) {
   ffi::Map<ffi::String, PrimExpr> span_map;
   for (const auto& iter : layout->shard) {
     if (span_map.find(iter->axis.name()) == span_map.end()) {
-      span_map.Set(iter->axis.name(), layout->GetSpan(iter->axis.name()));
+      span_map.Set(iter->axis.name(), layout.GetSpan(iter->axis.name()));
     }
   }
   return span_map;
diff --git a/src/tirx/ir/tensor.cc b/src/tirx/ir/tensor.cc
index 00d08bfe..2537e867 100644
--- a/src/tirx/ir/tensor.cc
+++ b/src/tirx/ir/tensor.cc
@@ -400,7 +400,7 @@ ffi::Array<PrimExpr> 
TensorTypeNode::ElemOffset(ffi::Array<PrimExpr> input_indic
 
   sym::Analyzer ana;
   if (layout.has_value()) {
-    auto mapped = layout.value()->Apply(input_indices, shape);
+    auto mapped = layout.value().Apply(input_indices, shape);
     if (mapped.empty() && shape.empty()) {
       return {IntImm(DefaultIndexType(), 0)};
     }
diff --git a/src/tirx/ir/type.cc b/src/tirx/ir/type.cc
index 13a62ab2..e8754ad4 100644
--- a/src/tirx/ir/type.cc
+++ b/src/tirx/ir/type.cc
@@ -57,7 +57,7 @@ bool 
TensorTypeNode::HasDefaultLayout(ffi::Optional<sym::Analyzer> analyzer) con
   for (size_t i = shape.size(); i > 0; --i) size = size * shape[i - 1];
   if (!equal(extent, size)) return false;
   // Delegate last so any analyzer created above is reused by the stride proof.
-  return tile->IsContiguous(std::move(analyzer));
+  return ffi::GetRef<TileLayout>(tile).IsContiguous(std::move(analyzer));
 }
 
 bool TensorTypeNode::IsScalar(bool alloc_or_decl) const {
@@ -65,7 +65,7 @@ bool TensorTypeNode::IsScalar(bool alloc_or_decl) const {
   return shape.size() == 1 && tvm::prim::IsOne(shape[0]) && data_alignment == 
64 &&
          (!layout.has_value() ||
           (layout.as<TileLayoutNode>() &&
-           
layout.value()->Canonicalize().as_or_throw<TileLayout>()->IsTrivial()));
+           
layout.value().Canonicalize().as_or_throw<TileLayout>().IsTrivial()));
 }
 
 std::optional<int64_t> TensorTypeNode::ConstantAllocationSize() const {
diff --git a/src/tirx/transform/flatten_tensor_utils.h 
b/src/tirx/transform/flatten_tensor_utils.h
index 5e001b10..ffc406e9 100644
--- a/src/tirx/transform/flatten_tensor_utils.h
+++ b/src/tirx/transform/flatten_tensor_utils.h
@@ -73,7 +73,7 @@ TVM_FFI_INLINE TensorVar FlattenedTensor(const TensorVar& 
tensor,
     for (const auto& iter : tile->shard) {
       span = span + (iter->extent - 1) * iter->stride;
     }
-    if (tile->IsContiguous(ana)) {
+    if (ffi::GetRef<TileLayout>(tile).IsContiguous(ana)) {
       for (const auto& extent : tensor.shape()) output_shape.Set(0, 
output_shape[0] * extent);
     } else {
       output_shape.Set(0, tvm::max(tile->shard[0]->stride * tensor.shape()[0], 
span));
@@ -86,10 +86,10 @@ TVM_FFI_INLINE TensorVar FlattenedTensor(const TensorVar& 
tensor,
       }
     }
   } else if (tile) {
-    output_shape.Set(0, ana->Simplify(tile->GetSpan(ffi::String("m"))));
+    output_shape.Set(0, 
ana->Simplify(tensor.ty()->layout.value().GetSpan(ffi::String("m"))));
   } else if (const auto* compose = 
tensor.ty()->layout.as<ComposeLayoutNode>()) {
-    PrimExpr span = compose->tile_layout->GetSpan(ffi::String("m"));
-    if (compose->tile_layout->IsTrivial()) {
+    PrimExpr span = compose->tile_layout.GetSpan(ffi::String("m"));
+    if (compose->tile_layout.IsTrivial()) {
       span = IntImm(tensor.ty()->DefaultIndexType(), 1);
       for (const auto& extent : tensor.shape()) span = span * extent;
     }
diff --git a/src/tirx/transform/lower_tensor_layouts.cc 
b/src/tirx/transform/lower_tensor_layouts.cc
index 32fdb59c..85766f9c 100644
--- a/src/tirx/transform/lower_tensor_layouts.cc
+++ b/src/tirx/transform/lower_tensor_layouts.cc
@@ -218,8 +218,8 @@ class LayoutApplier : public StmtExprMutatorWithAnalyzer {
         // the allocation footprint of a strided view. Round the underlying
         // memory span to a block whose upper bits the swizzle cannot change.
         // A bare swizzle applies to the full tensor, not just its atom period.
-        PrimExpr mem_span = compose->tile_layout->GetSpan(ffi::String("m"));
-        if (compose->tile_layout->IsTrivial()) {
+        PrimExpr mem_span = compose->tile_layout.GetSpan(ffi::String("m"));
+        if (compose->tile_layout.IsTrivial()) {
           mem_span = IntImm::Int32(1);
           for (const auto& extent : buf.shape()) mem_span = mem_span * extent;
         }
@@ -303,7 +303,7 @@ class LayoutApplier : public StmtExprMutatorWithAnalyzer {
                                                const ffi::Array<PrimExpr>& 
indices) {
     if (tensor.ty()->layout.has_value()) {
       if (auto tile = tensor.ty()->layout.value().as<TileLayoutNode>();
-          tile && tile->HasThreadAxis()) {
+          tile && ffi::GetRef<TileLayout>(tile).HasThreadAxis()) {
         LOG(FATAL) << "Cannot lower direct TensorLoad/TensorStore on a tensor 
with thread-axis "
                    << "layout: unable to verify that the coordinate matches 
the current thread. "
                    << "Use .view() + .local() to decompose thread and memory 
axes.";
@@ -319,7 +319,7 @@ class LayoutApplier : public StmtExprMutatorWithAnalyzer {
         auto tile = layout.as_or_throw<TileLayout>();
         layout = TileLayout(tile->shard, tile->replica, {});
       }
-      auto res = layout->Canonicalize()->Apply(indices, tensor.shape());
+      auto res = layout.Canonicalize().Apply(indices, tensor.shape());
       if (res.empty() && tensor.shape().empty()) {
         return {IntImm(tensor.ty()->DefaultIndexType(), 0)};
       }

Reply via email to