From d2f3def4b78012f7ffe09609e0659ddea9c935f4 Mon Sep 17 00:00:00 2001 From: tqchen Date: Mon, 27 Apr 2026 16:03:08 +0000 Subject: [PATCH 1/5] [REFACTOR] Migrate ReprPrinter to tvm-ffi __ffi_repr__ mechanism Replace all ~169 TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable).set_dispatch dispatch sites with the tvm-ffi reflection-based __ffi_repr__ mechanism. Key changes: - Add include/tvm/node/repr.h: ostream operator<< for ObjectRef/Any/Variant delegating to ffi::ReprPrint; AccessStep/AccessPath ostream operators - Add src/node/repr.cc: Dump() helpers, node.AsRepr FFI, AccessPath kRepr - Strip repr_printer.h to a compatibility shim (forward-declares only) - Pattern A (auto-default): remove dispatch, let ffi::ReprPrint generate dataclass-style TypeKey(field=val, ...) repr for ~140 internal types - Pattern B (custom kRepr): register TypeAttrDef.def(kRepr, lambda) for user-facing types: Span, Layout, VirtualDevice, TargetKind, TensorInfo, s_tir Trace - Pattern C (TVM_SCRIPT_REPR): use existing macro for IR/TIR/Relax nodes that delegate to TVMScriptPrinter - Fix double-registration bugs introduced during migration (trace.cc, expr.cc, tirx/ir/expr.cc) - Add explicit refl::ObjectDef() calls for CostModelNode/PyCostModelNode and PyMutatorNode to preserve Python @register_object type index allocation that was implicitly provided by the removed set_dispatch side effect - Update test_tir_transform_vectorize.py: null Target now repr as "None" instead of "(nullptr)" per the new Python-compatible repr convention --- include/tvm/ir/expr.h | 2 +- include/tvm/node/functor.h | 28 +-- include/tvm/node/repr.h | 118 +++++++++++ include/tvm/node/repr_printer.h | 116 +---------- include/tvm/node/script_printer.h | 2 +- include/tvm/relax/exec_builder.h | 2 +- include/tvm/s_tir/meta_schedule/mutator.h | 2 + src/arith/canonical_simplify.cc | 35 +--- src/arith/const_int_bound.cc | 10 +- src/arith/int_constraints.cc | 22 +- src/arith/int_set.cc | 7 +- src/arith/iter_affine_map.cc | 19 +- src/arith/modular_set.cc | 8 +- src/arith/presburger_set.cc | 10 +- src/arith/rewrite_simplify.cc | 11 +- src/ir/env_func.cc | 6 +- src/ir/expr.cc | 6 +- src/ir/instrument.cc | 8 +- src/ir/op.cc | 6 +- src/ir/source_map.cc | 60 +++--- src/ir/structural_equal.cc | 2 +- src/ir/structural_hash.cc | 30 +-- src/ir/transform.cc | 57 +----- src/node/container_printing.cc | 52 +---- src/node/repr.cc | 119 +++++++++++ src/node/repr_printer.cc | 189 +----------------- src/node/script_printer.cc | 9 +- src/relax/ir/dataflow_pattern.cc | 30 ++- src/relax/ir/emit_te.cc | 7 +- src/relax/ir/expr.cc | 2 - src/relax/ir/transform.cc | 18 +- src/s_tir/data_layout.cc | 26 ++- src/s_tir/meta_schedule/arg_info.cc | 24 ++- .../meta_schedule/cost_model/cost_model.cc | 15 +- .../feature_extractor/feature_extractor.cc | 10 +- .../measure_callback/measure_callback.cc | 10 +- src/s_tir/meta_schedule/mutator/mutator.cc | 9 +- src/s_tir/meta_schedule/postproc/postproc.cc | 9 +- .../schedule_rule/schedule_rule.cc | 9 +- src/s_tir/schedule/instruction.cc | 5 +- src/s_tir/schedule/trace.cc | 6 - src/script/printer/ir/utils.h | 2 +- src/script/printer/relax/utils.h | 2 +- src/script/printer/tirx/utils.h | 2 +- src/script/printer/utils.h | 26 ++- src/target/target.cc | 5 +- src/target/target_kind.cc | 11 +- src/target/virtual_device.cc | 63 +++--- src/te/operation/compute_op.cc | 8 +- src/te/operation/extern_op.cc | 7 +- src/te/operation/placeholder_op.cc | 7 +- src/te/operation/scan_op.cc | 6 +- src/te/tensor.cc | 6 +- src/tirx/ir/expr.cc | 11 +- src/tirx/ir/transform.cc | 9 +- .../test_tir_transform_vectorize.py | 2 +- 56 files changed, 479 insertions(+), 804 deletions(-) create mode 100644 include/tvm/node/repr.h create mode 100644 src/node/repr.cc diff --git a/include/tvm/ir/expr.h b/include/tvm/ir/expr.h index 942595b53dcd..968bef0727c4 100644 --- a/include/tvm/ir/expr.h +++ b/include/tvm/ir/expr.h @@ -29,7 +29,7 @@ #include #include #include -#include +#include #include #include diff --git a/include/tvm/node/functor.h b/include/tvm/node/functor.h index b9a468153226..3f0babdbd02c 100644 --- a/include/tvm/node/functor.h +++ b/include/tvm/node/functor.h @@ -160,38 +160,32 @@ class NodeFunctor { * \brief Useful macro to set NodeFunctor dispatch in a global static field. * * \code - * // Use NodeFunctor to implement ReprPrinter similar to Visitor Pattern. + * // Use NodeFunctor to implement TVMScriptPrinter similar to Visitor Pattern. * // vtable allows easy patch of new Node types, without changing - * // interface of ReprPrinter. + * // the interface of TVMScriptPrinter. * - * class ReprPrinter { + * class TVMScriptPrinter { * public: - * std::ostream& stream; * // the dispatch function. - * void print(Expr e) { - * const static FType& f = *vtable(); - * f(e, this); + * static std::string Script(const ObjectRef& node, const PrinterConfig& cfg) { + * return vtable()(node, cfg); * } - * - * using FType = NodeFunctor; + * using FType = NodeFunctor; * // function to return global function table * static FType& vtable(); * }; * * // in cpp/cc file - * ReprPrinter::FType& ReprPrinter::vtable() { // NOLINT(*) + * TVMScriptPrinter::FType& TVMScriptPrinter::vtable() { * static FType inst; return inst; * } * - * TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - * .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - * auto* n = static_cast(ref.get()); - * p->print(n->a); - * p->stream << '+' - * p->print(n->b); + * TVM_STATIC_IR_FUNCTOR(TVMScriptPrinter, vtable) + * .set_dispatch([](const ObjectRef& ref, const PrinterConfig& cfg) { + * auto* n = static_cast(ref.get()); + * return Script(n->a, cfg) + " + " + Script(n->b, cfg); * }); * - * * \endcode * * \param ClsName The name of the class diff --git a/include/tvm/node/repr.h b/include/tvm/node/repr.h new file mode 100644 index 000000000000..48276df1ecd7 --- /dev/null +++ b/include/tvm/node/repr.h @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +/*! + * \file tvm/node/repr.h + * \brief ostream operator<< for ObjectRef, Any, and Variant, delegating to + * ffi::ReprPrint. Also re-exports the Dump() debug helpers. + * + * Include this header wherever you need `os << some_objectref` and you are + * no longer pulling in the legacy repr_printer.h. + */ +#ifndef TVM_NODE_REPR_H_ +#define TVM_NODE_REPR_H_ + +#include +#include +#include + +#include + +namespace tvm { + +/*! + * \brief Dump the node to stderr, used for debug purposes. + * \param node The input node + */ +TVM_DLL void Dump(const runtime::ObjectRef& node); + +/*! + * \brief Dump the node to stderr, used for debug purposes. + * \param node The input node + */ +TVM_DLL void Dump(const runtime::Object* node); + +} // namespace tvm + +namespace tvm { +namespace ffi { + +// ostream << ObjectRef — delegates to ffi::ReprPrint +inline std::ostream& operator<<(std::ostream& os, const ObjectRef& n) { // NOLINT(*) + return os << ffi::ReprPrint(Any(n)); +} + +// ostream << Any — delegates to ffi::ReprPrint +inline std::ostream& operator<<(std::ostream& os, const Any& n) { // NOLINT(*) + return os << ffi::ReprPrint(n); +} + +// ostream << Variant<...> — delegates to ffi::ReprPrint +template +inline std::ostream& operator<<(std::ostream& os, const ffi::Variant& n) { // NOLINT(*) + return os << ffi::ReprPrint(Any(n)); +} + +namespace reflection { + +inline std::ostream& operator<<(std::ostream& os, const AccessStep& step) { + namespace refl = ffi::reflection; + switch (step->kind) { + case refl::AccessKind::kAttr: { + os << '.' << step->key.cast(); + return os; + } + case refl::AccessKind::kArrayItem: { + os << "[" << step->key.cast() << "]"; + return os; + } + case refl::AccessKind::kMapItem: { + os << "[" << step->key << "]"; + return os; + } + case refl::AccessKind::kAttrMissing: { + os << ".key.cast() << "`>"; + return os; + } + case refl::AccessKind::kArrayItemMissing: { + os << "[key.cast() << ">]"; + return os; + } + case refl::AccessKind::kMapItemMissing: { + os << "[key << ">]"; + return os; + } + default: { + TVM_FFI_THROW(InternalError) << "Unknown access step kind: " << static_cast(step->kind); + } + } + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const AccessPath& path) { + ffi::Array steps = path->ToSteps(); + os << ""; + for (const auto& step : steps) { + os << step; + } + return os; +} +} // namespace reflection +} // namespace ffi +} // namespace tvm +#endif // TVM_NODE_REPR_H_ diff --git a/include/tvm/node/repr_printer.h b/include/tvm/node/repr_printer.h index 1c1f1ec38099..71d19f524549 100644 --- a/include/tvm/node/repr_printer.h +++ b/include/tvm/node/repr_printer.h @@ -18,121 +18,13 @@ */ /*! * \file tvm/node/repr_printer.h - * \brief Printer class to print repr string of each AST/IR nodes. + * \brief DEPRECATED: The legacy ReprPrinter has been replaced by + * ffi::ReprPrint. This header is kept as an empty shim; + * include instead. */ #ifndef TVM_NODE_REPR_PRINTER_H_ #define TVM_NODE_REPR_PRINTER_H_ -#include -#include -#include +#include -#include -#include - -namespace tvm { -/*! \brief A printer class to print the AST/IR nodes. */ -class ReprPrinter { - public: - /*! \brief The output stream */ - std::ostream& stream; - /*! \brief The indentation level. */ - int indent{0}; - - explicit ReprPrinter(std::ostream& stream) // NOLINT(*) - : stream(stream) {} - - /*! \brief The node to be printed. */ - TVM_DLL void Print(const ObjectRef& node); - /*! \brief The node to be printed. */ - TVM_DLL void Print(const ffi::Any& node); - /*! \brief Print indent to the stream */ - TVM_DLL void PrintIndent(); - // Allow registration to be printer. - using FType = NodeFunctor; - TVM_DLL static FType& vtable(); -}; - -/*! - * \brief Dump the node to stderr, used for debug purposes. - * \param node The input node - */ -TVM_DLL void Dump(const runtime::ObjectRef& node); - -/*! - * \brief Dump the node to stderr, used for debug purposes. - * \param node The input node - */ -TVM_DLL void Dump(const runtime::Object* node); - -} // namespace tvm - -namespace tvm { -namespace ffi { -// default print function for all objects -// provide in the runtime namespace as this is where objectref originally comes from. -inline std::ostream& operator<<(std::ostream& os, const ObjectRef& n) { // NOLINT(*) - ReprPrinter(os).Print(n); - return os; -} - -// default print function for any -inline std::ostream& operator<<(std::ostream& os, const Any& n) { // NOLINT(*) - ReprPrinter(os).Print(n); - return os; -} - -template -inline std::ostream& operator<<(std::ostream& os, const ffi::Variant& n) { // NOLINT(*) - ReprPrinter(os).Print(Any(n)); - return os; -} - -namespace reflection { - -inline std::ostream& operator<<(std::ostream& os, const AccessStep& step) { - namespace refl = ffi::reflection; - switch (step->kind) { - case refl::AccessKind::kAttr: { - os << '.' << step->key.cast(); - return os; - } - case refl::AccessKind::kArrayItem: { - os << "[" << step->key.cast() << "]"; - return os; - } - case refl::AccessKind::kMapItem: { - os << "[" << step->key << "]"; - return os; - } - case refl::AccessKind::kAttrMissing: { - os << ".key.cast() << "`>"; - return os; - } - case refl::AccessKind::kArrayItemMissing: { - os << "[key.cast() << ">]"; - return os; - } - case refl::AccessKind::kMapItemMissing: { - os << "[key << ">]"; - return os; - } - default: { - TVM_FFI_THROW(InternalError) << "Unknown access step kind: " << static_cast(step->kind); - } - } - return os; -} - -inline std::ostream& operator<<(std::ostream& os, const AccessPath& path) { - ffi::Array steps = path->ToSteps(); - os << ""; - for (const auto& step : steps) { - os << step; - } - return os; -} -} // namespace reflection -} // namespace ffi -} // namespace tvm #endif // TVM_NODE_REPR_PRINTER_H_ diff --git a/include/tvm/node/script_printer.h b/include/tvm/node/script_printer.h index c72ee10d2c11..5eeab2010765 100644 --- a/include/tvm/node/script_printer.h +++ b/include/tvm/node/script_printer.h @@ -161,7 +161,7 @@ class TVM_DLL PrinterConfig : public ObjectRef { PrinterConfigNode); }; -/*! \brief Legacy behavior of ReprPrinter. */ +/*! \brief TVMScript-based printer for IR nodes. */ class TVMScriptPrinter { public: /* Convert the object to TVMScript format */ diff --git a/include/tvm/relax/exec_builder.h b/include/tvm/relax/exec_builder.h index 66bae5411a16..f85b5af4602a 100644 --- a/include/tvm/relax/exec_builder.h +++ b/include/tvm/relax/exec_builder.h @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/include/tvm/s_tir/meta_schedule/mutator.h b/include/tvm/s_tir/meta_schedule/mutator.h index 76f41f998f63..42708dec57a2 100644 --- a/include/tvm/s_tir/meta_schedule/mutator.h +++ b/include/tvm/s_tir/meta_schedule/mutator.h @@ -162,6 +162,8 @@ class PyMutatorNode : public MutatorNode { FAsString f_as_string; static void RegisterReflection() { + namespace refl = tvm::ffi::reflection; + refl::ObjectDef(); // `f_initialize_with_tune_context` is not registered // `f_apply` is not registered // `f_clone` is not registered diff --git a/src/arith/canonical_simplify.cc b/src/arith/canonical_simplify.cc index 68696b9de10b..3d1d55269da4 100644 --- a/src/arith/canonical_simplify.cc +++ b/src/arith/canonical_simplify.cc @@ -535,40 +535,7 @@ void SumExprNode::AddToSelf(const SumExpr& other, int64_t scale) { this->AddToSelf(other->base * scale); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - auto factor_str = [](int64_t f) { - return f == SplitExprNode::kPosInf ? std::string("+inf") : std::to_string(f); - }; - p->stream << "split("; - p->Print(op->index); - p->stream << ", lower=" << factor_str(op->lower_factor) - << ", upper=" << factor_str(op->upper_factor) << ", scale=" << op->scale - << ", div_mode="; - switch (op->div_mode) { - // No "default", so that the compiler will emit a warning if more div modes are - // added that are not covered by the switch. - case kTruncDiv: - p->stream << "truncdiv"; - break; - case kFloorDiv: - p->stream << "floordiv"; - break; - } - p->stream << ')'; - }); - -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "sum(base=" << op->base; - for (const SplitExpr& s : op->args) { - p->stream << ", "; - p->Print(s); - } - p->stream << ')'; - }); +// Pattern A (RM): auto-default repr from reflection for SplitExprNode and SumExprNode. // Sub-class RewriteSimplifier::Impl to take benefit of // rewriter for condition simplification etc. diff --git a/src/arith/const_int_bound.cc b/src/arith/const_int_bound.cc index 8e306ba96650..4b0b57d84f21 100644 --- a/src/arith/const_int_bound.cc +++ b/src/arith/const_int_bound.cc @@ -67,15 +67,7 @@ inline void PrintBoundValue(std::ostream& os, int64_t val) { } } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "ConstIntBound["; - PrintBoundValue(p->stream, op->min_value); - p->stream << ','; - PrintBoundValue(p->stream, op->max_value); - p->stream << ']'; - }); +// Pattern A (RM): auto-default repr from reflection. // internal entry for const int bound struct ConstIntBoundAnalyzer::Entry { diff --git a/src/arith/int_constraints.cc b/src/arith/int_constraints.cc index 6d8e539357a2..be6dcdeb35a4 100644 --- a/src/arith/int_constraints.cc +++ b/src/arith/int_constraints.cc @@ -219,12 +219,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "IntGroupBounds(coef=" << op->coef << ", lower=" << op->lower - << ", equal=" << op->equal << ", upper=" << op->upper << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. IntConstraints::IntConstraints(ffi::Array variables, ffi::Map ranges, ffi::Array relations) { @@ -255,12 +250,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "IntConstraints(" << op->variables << ", " << op->ranges << ", " << op->relations - << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. IntConstraintsTransform::IntConstraintsTransform(IntConstraints src, IntConstraints dst, ffi::Map src_to_dst, @@ -302,13 +292,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "IntConstraintsTransform(" - << "\n\t" << op->src << "\n\t" << op->dst << "\n\t" << op->src_to_dst << "\n\t" - << op->dst_to_src << "\n)"; - }); +// Pattern A (RM): auto-default repr from reflection. } // namespace arith } // namespace tvm diff --git a/src/arith/int_set.cc b/src/arith/int_set.cc index c8e1d73771c0..f48a1e1e26c4 100644 --- a/src/arith/int_set.cc +++ b/src/arith/int_set.cc @@ -1227,12 +1227,7 @@ ffi::Array EstimateRegionUpperBound(const ffi::Array& region, return result; } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "IntervalSet" - << "[" << op->min_value << ", " << op->max_value << ']'; - }); +// Pattern A (RM): auto-default repr from reflection. TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; diff --git a/src/arith/iter_affine_map.cc b/src/arith/iter_affine_map.cc index a4d509716726..d42114e57905 100644 --- a/src/arith/iter_affine_map.cc +++ b/src/arith/iter_affine_map.cc @@ -61,11 +61,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { [](PrimExpr source, PrimExpr extent) { return IterMark(source, extent); }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "IterMark(" << op->source << ", extent=" << op->extent << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. IterSplitExpr::IterSplitExpr(IterMark source) { auto n = ffi::make_object(); @@ -108,12 +104,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "IterSplit(" << op->source << ", lower_factor=" << op->lower_factor - << ", extent=" << op->extent << ", scale=" << op->scale << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. IterSumExpr::IterSumExpr(ffi::Array args, PrimExpr base) { auto n = ffi::make_object(); @@ -130,11 +121,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "IterSum(" << op->args << ", " << op->base << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. /*! * \brief Collector that collects the outgoing split reference of each IterMark. diff --git a/src/arith/modular_set.cc b/src/arith/modular_set.cc index 9aaa81b1bacb..9bc45bb77f53 100644 --- a/src/arith/modular_set.cc +++ b/src/arith/modular_set.cc @@ -49,12 +49,8 @@ ModularSet::ModularSet(int64_t coeff, int64_t base) { data_ = std::move(node); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "ModularSet(" - << "coeff=" << op->coeff << ", base=" << op->base << ')'; - }); +// Pattern A (RM): auto-default repr from reflection produces +// "arith.ModularSet(coeff=..., base=...)" ModularSet MakeModularSet(int64_t coeff, int64_t base) { return ModularSet(coeff, base); } diff --git a/src/arith/presburger_set.cc b/src/arith/presburger_set.cc index 3c7bd25d860f..a62c34c525e6 100644 --- a/src/arith/presburger_set.cc +++ b/src/arith/presburger_set.cc @@ -260,15 +260,7 @@ IntSet EvalSet(const PrimExpr& e, const PresburgerSet& set) { return result; } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto set = node.as(); - TVM_FFI_ICHECK(ret) << "Unknown type:" << node->GetTypeKey(); - p->stream << "{"; - p->stream << set->GetVars() << ": "; - p->stream << node.as()->GenerateConstraint(); - p->stream << "}"; - }); +// Pattern A (RM): auto-default repr from reflection. #else // defined(TVM_MLIR_VERSION) && TVM_MLIR_VERSION >= 150 diff --git a/src/arith/rewrite_simplify.cc b/src/arith/rewrite_simplify.cc index cb96bb07f66c..8649bf96beeb 100644 --- a/src/arith/rewrite_simplify.cc +++ b/src/arith/rewrite_simplify.cc @@ -2428,16 +2428,7 @@ RewriteSimplifier::RewriteSimplifier(Analyzer* parent) : impl_(new Impl(parent)) RewriteSimplifier::~RewriteSimplifier() { delete impl_; } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* ptr = node.as(); - p->stream << "RewriteSimplifierStats(nodes_visited = " << ptr->nodes_visited - << ", constraints_entered = " << ptr->constraints_entered - << ", rewrites_attempted = " << ptr->rewrites_attempted - << ", rewrites_performed = " << ptr->rewrites_performed - << ", max_recursive_depth = " << ptr->max_recursive_depth - << ", num_recursive_rewrites = " << ptr->num_recursive_rewrites << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. } // namespace arith } // namespace tvm diff --git a/src/ir/env_func.cc b/src/ir/env_func.cc index e90ea07a0b38..5c64047c9669 100644 --- a/src/ir/env_func.cc +++ b/src/ir/env_func.cc @@ -33,11 +33,7 @@ using ffi::Any; using ffi::Function; using ffi::PackedArgs; -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "EnvFunc(" << op->name << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. ObjectPtr CreateEnvNode(const std::string& name) { auto f = tvm::ffi::Function::GetGlobal(name); diff --git a/src/ir/expr.cc b/src/ir/expr.cc index f9d6e0fc6080..b5cc09e11d6a 100644 --- a/src/ir/expr.cc +++ b/src/ir/expr.cc @@ -225,10 +225,8 @@ TVM_FFI_STATIC_INIT_BLOCK() { ss << ref; return ss.str(); }); - refl::TypeAttrDef().def( - refl::type_attr::kRepr, [](GlobalVar gvar, ffi::Function) -> ffi::String { - return "I.GlobalVar(\"" + std::string(gvar->name_hint) + "\")"; - }); + // Note: kRepr for GlobalVarNode is registered in script/printer/ir/ir.cc + // via TVM_SCRIPT_REPR(GlobalVarNode, ReprPrintIR). } } // namespace tvm diff --git a/src/ir/instrument.cc b/src/ir/instrument.cc index 8d1dd2ecf54f..ced20ba1b1e1 100644 --- a/src/ir/instrument.cc +++ b/src/ir/instrument.cc @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include @@ -190,11 +190,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - p->stream << node->name; - }); +// Pattern A (RM): auto-default repr from reflection. /*! \brief PassProfile stores profiling information for a given pass and its sub-passes. */ struct PassProfile { diff --git a/src/ir/op.cc b/src/ir/op.cc index 3a260e886a51..fb5751b63f33 100644 --- a/src/ir/op.cc +++ b/src/ir/op.cc @@ -160,10 +160,6 @@ TVM_FFI_STATIC_INIT_BLOCK() { .def("__data_from_json__", [](const ffi::String& name) -> Op { return Op::Get(name); }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - p->stream << "Op(" << node->name << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. } // namespace tvm diff --git a/src/ir/source_map.cc b/src/ir/source_map.cc index 7b94890623fd..89842cfc05e7 100644 --- a/src/ir/source_map.cc +++ b/src/ir/source_map.cc @@ -20,6 +20,7 @@ * \file source_map.cc * \brief The implementation of the source map data structure. */ +#include #include #include #include @@ -73,11 +74,15 @@ TVM_FFI_STATIC_INIT_BLOCK() { refl::GlobalDef().def("ir.SourceName", SourceName::Get); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - p->stream << "SourceName(" << node->name << ", " << node << ")"; - }); +TVM_FFI_STATIC_INIT_BLOCK() { + namespace refl = tvm::ffi::reflection; + refl::TypeAttrDef().def( + refl::type_attr::kRepr, [](SourceName sn, ffi::Function) -> ffi::String { + std::ostringstream os; + os << "SourceName(" << sn->name << ", " << static_cast(sn.get()) << ")"; + return os.str(); + }); +} Span::Span(SourceName source_name, int line, int end_line, int column, int end_column) { auto n = ffi::make_object(); @@ -150,25 +155,32 @@ TVM_FFI_STATIC_INIT_BLOCK() { .def("ir.SequentialSpan", [](tvm::ffi::Array spans) { return SequentialSpan(spans); }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - p->stream << "Span(" << node->source_name << ", " << node->line << ", " << node->end_line - << ", " << node->column << ", " << node->end_column << ")"; - }); - -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - - p->stream << "SequentailSpan([ "; - int index = 0; - const int last = node->spans.size() - 1; - while (index < last) { - p->stream << node->spans[index++] << ", "; - } - p->stream << node->spans[last] << " ])"; - }); +TVM_FFI_STATIC_INIT_BLOCK() { + namespace refl = tvm::ffi::reflection; + refl::TypeAttrDef().def( + refl::type_attr::kRepr, [](Span span, ffi::Function fn_repr) -> ffi::String { + std::ostringstream os; + os << "Span(" << fn_repr(ffi::AnyView(span->source_name)).cast() << ", " + << span->line << ", " << span->end_line << ", " << span->column << ", " + << span->end_column << ")"; + return os.str(); + }); + refl::TypeAttrDef().def( + refl::type_attr::kRepr, [](SequentialSpan seq, ffi::Function fn_repr) -> ffi::String { + // Fix typo: was "SequentailSpan", now "SequentialSpan" + std::ostringstream os; + os << "SequentialSpan([ "; + const int last = static_cast(seq->spans.size()) - 1; + for (int i = 0; i < last; ++i) { + os << fn_repr(ffi::AnyView(seq->spans[i])).cast() << ", "; + } + if (last >= 0) { + os << fn_repr(ffi::AnyView(seq->spans[last])).cast(); + } + os << " ])"; + return os.str(); + }); +} /*! \brief Construct a source from a string. */ Source::Source(SourceName src_name, std::string source) { diff --git a/src/ir/structural_equal.cc b/src/ir/structural_equal.cc index 1d7cbd23d0ca..b8f80f4d57da 100644 --- a/src/ir/structural_equal.cc +++ b/src/ir/structural_equal.cc @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/ir/structural_hash.cc b/src/ir/structural_hash.cc index ad74742e5144..b875f8662580 100644 --- a/src/ir/structural_hash.cc +++ b/src/ir/structural_hash.cc @@ -97,11 +97,7 @@ struct ReportNodeTrait { TVM_FFI_STATIC_INIT_BLOCK() { ReportNodeTrait::RegisterReflection(); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << op->AsTable(); - }); +// Pattern A (RM): auto-default repr from reflection for ReportNode. struct CountNodeTrait { static void RegisterReflection() { @@ -113,11 +109,7 @@ struct CountNodeTrait { TVM_FFI_STATIC_INIT_BLOCK() { CountNodeTrait::RegisterReflection(); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << op->GetTypeKey() << "(" << op->value << ")"; - }); +// Pattern A (RM): auto-default repr from reflection for CountNode. struct DurationNodeTrait { static void RegisterReflection() { @@ -129,11 +121,7 @@ struct DurationNodeTrait { TVM_FFI_STATIC_INIT_BLOCK() { DurationNodeTrait::RegisterReflection(); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << op->GetTypeKey() << "(" << op->microseconds << ")"; - }); +// Pattern A (RM): auto-default repr from reflection for DurationNode. struct PercentNodeTrait { static void RegisterReflection() { @@ -145,11 +133,7 @@ struct PercentNodeTrait { TVM_FFI_STATIC_INIT_BLOCK() { PercentNodeTrait::RegisterReflection(); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << op->GetTypeKey() << "(" << op->percent << ")"; - }); +// Pattern A (RM): auto-default repr from reflection for PercentNode. struct RatioNodeTrait { static void RegisterReflection() { @@ -161,10 +145,6 @@ struct RatioNodeTrait { TVM_FFI_STATIC_INIT_BLOCK() { RatioNodeTrait::RegisterReflection(); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << op->GetTypeKey() << "(" << op->ratio << ")"; - }); +// Pattern A (RM): auto-default repr from reflection for RatioNode. } // namespace tvm diff --git a/src/ir/transform.cc b/src/ir/transform.cc index 9d2dec6b1a7c..8c56f737d409 100644 --- a/src/ir/transform.cc +++ b/src/ir/transform.cc @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include @@ -35,7 +35,6 @@ namespace tvm { namespace transform { -using tvm::ReprPrinter; using tvm::ffi::Any; TVM_REGISTER_PASS_CONFIG_OPTION("testing.immutable_module", Bool); @@ -505,23 +504,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, tvm::ReprPrinter* p) { - auto* node = static_cast(ref.get()); - p->stream << "The meta data of the pass - "; - p->stream << "pass name: " << node->name; - p->stream << ", opt_level: " << node->opt_level; - if (node->required.empty()) { - p->stream << ", required passes: []\n"; - } else { - p->stream << ", required passes: [" - << "\n"; - for (const auto& it : node->required) { - p->stream << it << ", "; - } - p->stream << "]\n"; - } - }); +// Pattern A (RM): auto-default repr from reflection for PassInfoNode. TVM_FFI_STATIC_INIT_BLOCK() { PassContextNode::RegisterReflection(); @@ -545,13 +528,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { [](Pass pass, ffi::RValueRef mod) { return pass(*std::move(mod)); }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - const PassInfo info = node->Info(); - p->stream << "Run Module pass: " << info->name << " at the optimization level " - << info->opt_level; - }); +// Pattern A (RM): auto-default repr from reflection for ModulePassNode. TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; @@ -566,19 +543,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - const PassInfo info = node->Info(); - p->stream << "Run Sequential pass: " << info->name << " at the optimization level " - << info->opt_level << ". "; - p->stream << "The passes will be executed are: ["; - for (const auto& it : node->passes) { - const PassInfo pass_info = it->Info(); - p->stream << pass_info->name << " "; - } - p->stream << "]"; - }); +// Pattern A (RM): auto-default repr from reflection for SequentialNode. TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; @@ -602,19 +567,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - p->stream << "Pass context information: " - << "\n"; - p->stream << "\topt_level: " << node->opt_level << "\n"; - - p->stream << "\trequired passes: " << node->required_pass << "\n"; - p->stream << "\tdisabled passes: " << node->disabled_pass << "\n"; - p->stream << "\tinstruments: " << node->instruments << "\n"; - - p->stream << "\tconfig: " << node->config << "\n"; - }); +// Pattern A (RM): auto-default repr from reflection for PassContextNode. class PassContext::Internal { public: diff --git a/src/node/container_printing.cc b/src/node/container_printing.cc index b4773b2a816b..3a6700c78861 100644 --- a/src/node/container_printing.cc +++ b/src/node/container_printing.cc @@ -18,51 +18,9 @@ */ /*! - * Printer implementation for containers - * \file node/container_printint.cc + * \file node/container_printing.cc + * \brief DEPRECATED — tvm-ffi provides built-in repr for Array/Map/Shape. + * The legacy ReprPrinter dispatches for containers are no longer needed. */ -#include -#include -#include -#include - -namespace tvm { - -// Container printer -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << '['; - for (size_t i = 0; i < op->size(); ++i) { - if (i != 0) { - p->stream << ", "; - } - p->Print(op->at(i)); - } - p->stream << ']'; - }); - -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << '{'; - for (auto it = op->begin(); it != op->end(); ++it) { - if (it != op->begin()) { - p->stream << ", "; - } - if (auto opt_str = it->first.as()) { - p->stream << '\"' << opt_str.value() << "\": "; - } else { - p->Print(it->first); - p->stream << ": "; - } - p->Print(it->second); - } - p->stream << '}'; - }); - -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - p->stream << Downcast(node); - }); -} // namespace tvm +// This file is intentionally empty. Container repr is handled by +// ffi::ReprPrint (tvm-ffi/src/ffi/extra/dataclass.cc). diff --git a/src/node/repr.cc b/src/node/repr.cc new file mode 100644 index 000000000000..0ce7d0c781f5 --- /dev/null +++ b/src/node/repr.cc @@ -0,0 +1,119 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/*! + * \file node/repr.cc + * \brief Implements Dump helpers and FFI registration for ffi-repr-based printing. + * + * The legacy ReprPrinter has been replaced by ffi::ReprPrint. This file: + * - Implements the Dump() debug helpers (they call ffi::ReprPrint). + * - Registers node.AsRepr (for backward Python compatibility) via ffi::ReprPrint. + * - Registers __ffi_repr__ hooks for AccessPath and AccessStep. + */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../support/str_escape.h" + +namespace tvm { + +void Dump(const runtime::ObjectRef& n) { + std::cerr << ffi::ReprPrint(ffi::Any(n)) << "\n"; +} + +void Dump(const runtime::Object* n) { Dump(runtime::GetRef(n)); } + +namespace { +/*! + * \brief Format an AccessStep as a concise string fragment. + */ +void FormatAccessStep(std::ostringstream& os, const ffi::reflection::AccessStep& step) { + using ffi::reflection::AccessKind; + static const ffi::Function repr_fn = ffi::Function::GetGlobal("ffi.ReprPrint").value(); + switch (step->kind) { + case AccessKind::kAttr: + os << "." << step->key.cast(); + break; + case AccessKind::kArrayItem: + os << "[" << step->key.cast() << "]"; + break; + case AccessKind::kMapItem: + os << "[" << repr_fn(step->key).cast() << "]"; + break; + case AccessKind::kAttrMissing: + os << "." << step->key.cast() << "?"; + break; + case AccessKind::kArrayItemMissing: + os << "[" << step->key.cast() << "]?"; + break; + case AccessKind::kMapItemMissing: + os << "[" << repr_fn(step->key).cast() << "]?"; + break; + } +} + +/*! + * \brief Format an AccessPath as ".field[idx]". + */ +ffi::String FormatAccessPath(const ffi::reflection::AccessPath& path) { + std::vector steps; + const ffi::reflection::AccessPathObj* cur = path.get(); + while (cur->step.defined()) { + steps.push_back(cur->step.value()); + cur = static_cast(cur->parent.get()); + } + std::ostringstream os; + os << ""; + for (auto it = steps.rbegin(); it != steps.rend(); ++it) { + FormatAccessStep(os, *it); + } + return os.str(); +} +} // namespace + +TVM_FFI_STATIC_INIT_BLOCK() { + namespace refl = tvm::ffi::reflection; + // node.AsRepr: backward-compatible Python entry point. + // Python's tvm.runtime._ffi_node_api sets __object_repr__ = AsRepr via init_ffi_api. + refl::GlobalDef().def("node.AsRepr", [](ffi::Any obj) -> ffi::String { + return ffi::ReprPrint(obj); + }); + // Register __ffi_repr__ for AccessPath/AccessStep so that ffi.ReprPrint + // uses the concise ".field[idx]" format. + refl::TypeAttrDef().def( + refl::type_attr::kRepr, + [](ffi::reflection::AccessPath path, ffi::Function) -> ffi::String { + return FormatAccessPath(path); + }); + refl::TypeAttrDef().def( + refl::type_attr::kRepr, + [](ffi::reflection::AccessStep step, ffi::Function) -> ffi::String { + std::ostringstream os; + FormatAccessStep(os, step); + return os.str(); + }); +} +} // namespace tvm diff --git a/src/node/repr_printer.cc b/src/node/repr_printer.cc index 142ad74eb5e9..de01909c8dac 100644 --- a/src/node/repr_printer.cc +++ b/src/node/repr_printer.cc @@ -18,191 +18,8 @@ */ /*! - * Printer utilities * \file node/repr_printer.cc + * \brief DEPRECATED — implementation moved to src/node/repr.cc. */ -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "../support/str_escape.h" - -namespace tvm { - -void ReprPrinter::Print(const ObjectRef& node) { - static const FType& f = vtable(); - if (!node.defined()) { - stream << "(nullptr)"; - } else { - if (f.can_dispatch(node)) { - f(node, this); - } else { - // default value, output type key and addr. - stream << node->GetTypeKey() << "(" << node.get() << ")"; - } - } -} - -void ReprPrinter::Print(const ffi::Any& node) { - switch (node.type_index()) { - case ffi::TypeIndex::kTVMFFINone: { - stream << "(nullptr)"; - break; - } - case ffi::TypeIndex::kTVMFFIInt: { - stream << node.cast(); - break; - } - case ffi::TypeIndex::kTVMFFIBool: { - stream << node.cast(); - break; - } - case ffi::TypeIndex::kTVMFFIFloat: { - stream << node.cast(); - break; - } - case ffi::TypeIndex::kTVMFFIOpaquePtr: { - stream << node.cast(); - break; - } - case ffi::TypeIndex::kTVMFFIDataType: { - stream << node.cast(); - break; - } - case ffi::TypeIndex::kTVMFFIDevice: { - runtime::operator<<(stream, node.cast()); - break; - } - case ffi::TypeIndex::kTVMFFIObject: { - Print(node.cast()); - break; - } - case ffi::TypeIndex::kTVMFFISmallStr: - case ffi::TypeIndex::kTVMFFIStr: { - ffi::String str = node.cast(); - stream << '"' << support::StrEscape(str.data(), str.size()) << '"'; - break; - } - case ffi::TypeIndex::kTVMFFISmallBytes: - case ffi::TypeIndex::kTVMFFIBytes: { - ffi::Bytes bytes = node.cast(); - stream << "b\"" << support::StrEscape(bytes.data(), bytes.size()) << '"'; - break; - } - default: { - if (auto opt_obj = node.as()) { - Print(opt_obj.value()); - } else { - stream << "Any(type_key=`" << node.GetTypeKey() << "`)"; - } - break; - } - } -} - -void ReprPrinter::PrintIndent() { - for (int i = 0; i < indent; ++i) { - stream << ' '; - } -} - -ReprPrinter::FType& ReprPrinter::vtable() { - static FType inst; - return inst; -} - -void Dump(const runtime::ObjectRef& n) { std::cerr << n << "\n"; } - -void Dump(const runtime::Object* n) { Dump(runtime::GetRef(n)); } - -namespace { -/*! - * \brief Format an AccessStep as a concise string fragment. - * - * For map keys, uses ffi.ReprPrint which dispatches to __ffi_repr__. - */ -void FormatAccessStep(std::ostringstream& os, const ffi::reflection::AccessStep& step) { - using ffi::reflection::AccessKind; - static const ffi::Function repr_fn = ffi::Function::GetGlobal("ffi.ReprPrint").value(); - switch (step->kind) { - case AccessKind::kAttr: - os << "." << step->key.cast(); - break; - case AccessKind::kArrayItem: - os << "[" << step->key.cast() << "]"; - break; - case AccessKind::kMapItem: - os << "[" << repr_fn(step->key).cast() << "]"; - break; - case AccessKind::kAttrMissing: - os << "." << step->key.cast() << "?"; - break; - case AccessKind::kArrayItemMissing: - os << "[" << step->key.cast() << "]?"; - break; - case AccessKind::kMapItemMissing: - os << "[" << repr_fn(step->key).cast() << "]?"; - break; - } -} - -/*! - * \brief Format an AccessPath as ".field[idx]". - */ -ffi::String FormatAccessPath(const ffi::reflection::AccessPath& path) { - std::vector steps; - const ffi::reflection::AccessPathObj* cur = path.get(); - while (cur->step.defined()) { - steps.push_back(cur->step.value()); - cur = static_cast(cur->parent.get()); - } - std::ostringstream os; - os << ""; - for (auto it = steps.rbegin(); it != steps.rend(); ++it) { - FormatAccessStep(os, *it); - } - return os.str(); -} -} // namespace - -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - p->stream << FormatAccessPath(Downcast(node)); - }); - -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - std::ostringstream os; - FormatAccessStep(os, Downcast(node)); - p->stream << os.str(); - }); - -TVM_FFI_STATIC_INIT_BLOCK() { - namespace refl = tvm::ffi::reflection; - refl::GlobalDef().def("node.AsRepr", [](ffi::Any obj) { - std::ostringstream os; - os << obj; - return os.str(); - }); - // Register __ffi_repr__ for AccessPath/AccessStep so that ffi.ReprPrint - // uses the concise ".field[idx]" format instead of the dataclass repr. - refl::TypeAttrDef().def( - refl::type_attr::kRepr, - [](ffi::reflection::AccessPath path, ffi::Function) -> ffi::String { - return FormatAccessPath(path); - }); - refl::TypeAttrDef().def( - refl::type_attr::kRepr, - [](ffi::reflection::AccessStep step, ffi::Function) -> ffi::String { - std::ostringstream os; - FormatAccessStep(os, step); - return os.str(); - }); -} -} // namespace tvm +// This file is intentionally empty. The legacy ReprPrinter has been removed. +// See src/node/repr.cc for the replacement implementation. diff --git a/src/node/script_printer.cc b/src/node/script_printer.cc index 1774ba4b4b03..43cd651cb5fc 100644 --- a/src/node/script_printer.cc +++ b/src/node/script_printer.cc @@ -20,7 +20,8 @@ #include #include #include -#include +#include +#include #include #include @@ -39,10 +40,8 @@ TVMScriptPrinter::FType& TVMScriptPrinter::vtable() { std::string TVMScriptPrinter::Script(const ObjectRef& node, const ffi::Optional& cfg) { if (!TVMScriptPrinter::vtable().can_dispatch(node)) { - std::ostringstream os; - ReprPrinter printer(os); - printer.Print(node); - return os.str(); + // Fall back to ffi::ReprPrint for types not registered with TVMScriptPrinter. + return std::string(ffi::ReprPrint(ffi::Any(node))); } return TVMScriptPrinter::vtable()(node, cfg.value_or(PrinterConfig())); } diff --git a/src/relax/ir/dataflow_pattern.cc b/src/relax/ir/dataflow_pattern.cc index ce34f4705fba..85536f14475f 100644 --- a/src/relax/ir/dataflow_pattern.cc +++ b/src/relax/ir/dataflow_pattern.cc @@ -22,10 +22,13 @@ * \brief The dataflow pattern language for Relax */ +#include #include +#include #include #include +#include #include #include @@ -56,12 +59,27 @@ TVM_FFI_STATIC_INIT_BLOCK() { ConstantPatternNode::RegisterReflection(); } -#define RELAX_PATTERN_PRINTER_DEF(NODE_TYPE, REPR_LAMBDA) \ - TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) \ - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { \ - auto* node = static_cast(ref.get()); \ - REPR_LAMBDA(p, node); \ - }) +// Helper used inside RELAX_PATTERN_PRINTER_DEF lambdas. +// Mimics the ReprPrinter interface (p->stream, p->Print) so that existing +// REPR_LAMBDA bodies compile without modification. +struct PatternReprPrinterHelper { + std::ostringstream stream; + void Print(const ObjectRef& x) { stream << ffi::ReprPrint(ffi::Any(x)); } +}; + +#define RELAX_PATTERN_PRINTER_DEF(NODE_TYPE, REPR_LAMBDA) \ + TVM_FFI_STATIC_INIT_BLOCK() { \ + namespace refl = tvm::ffi::reflection; \ + refl::TypeAttrDef().def( \ + refl::type_attr::kRepr, \ + [](ffi::ObjectRef ref, ffi::Function) -> ffi::String { \ + auto* node = static_cast(ref.get()); \ + PatternReprPrinterHelper printer; \ + auto* p = &printer; \ + REPR_LAMBDA(p, node); \ + return printer.stream.str(); \ + }); \ + } ExternFuncPattern::ExternFuncPattern(ffi::String global_symbol) { ObjectPtr n = ffi::make_object(); diff --git a/src/relax/ir/emit_te.cc b/src/relax/ir/emit_te.cc index 004a856fd602..f5b0c4474d33 100644 --- a/src/relax/ir/emit_te.cc +++ b/src/relax/ir/emit_te.cc @@ -29,12 +29,7 @@ namespace tvm { namespace relax { -// RXPlaceholderOpNode -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "rxplaceholder(" << op->name << ", " << op << ")"; - }); +// Pattern A (RM): auto-default repr from reflection for RXPlaceholderOpNode. TVM_FFI_STATIC_INIT_BLOCK() { RXPlaceholderOpNode::RegisterReflection(); } diff --git a/src/relax/ir/expr.cc b/src/relax/ir/expr.cc index 2fbd573a5f7f..e58f88f04a4f 100644 --- a/src/relax/ir/expr.cc +++ b/src/relax/ir/expr.cc @@ -27,8 +27,6 @@ namespace tvm { namespace relax { -using tvm::ReprPrinter; - TVM_FFI_STATIC_INIT_BLOCK() { IdNode::RegisterReflection(); CallNode::RegisterReflection(); diff --git a/src/relax/ir/transform.cc b/src/relax/ir/transform.cc index 3fcdeeab834a..49b32307f7a7 100644 --- a/src/relax/ir/transform.cc +++ b/src/relax/ir/transform.cc @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include @@ -176,13 +176,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - const PassInfo info = node->Info(); - p->stream << "Run Function pass: " << info->name << " at the optimization level " - << info->opt_level; - }); +// Pattern A (RM): auto-default repr from reflection for FunctionPassNode. class DataflowBlockPass; @@ -399,13 +393,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - const PassInfo info = node->Info(); - p->stream << "Run DataflowBlock pass: " << info->name << " at the optimization level " - << info->opt_level; - }); +// Pattern A (RM): auto-default repr from reflection for DataflowBlockPassNode. TVM_FFI_STATIC_INIT_BLOCK() { FunctionPassNode::RegisterReflection(); diff --git a/src/s_tir/data_layout.cc b/src/s_tir/data_layout.cc index bee4c2e31fce..ccf617ac3d22 100644 --- a/src/s_tir/data_layout.cc +++ b/src/s_tir/data_layout.cc @@ -289,11 +289,13 @@ int32_t Layout::FactorOf(const LayoutAxis& axis) const { return factor; } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* l = static_cast(node.get()); - p->stream << "Layout(" << l->name << ")"; - }); +TVM_FFI_STATIC_INIT_BLOCK() { + namespace refl = tvm::ffi::reflection; + refl::TypeAttrDef().def( + refl::type_attr::kRepr, [](Layout l, ffi::Function) -> ffi::String { + return "Layout(" + std::string(l->name) + ")"; + }); +} inline bool GetStoreRule(ffi::Array* index_rule, ffi::Array* shape_rule, const Layout& src_layout, const Layout& dst_layout) { @@ -570,12 +572,14 @@ BijectiveLayout::BijectiveLayout(Layout src_layout, Layout dst_layout) { } } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* b = static_cast(node.get()); - p->stream << "BijectiveLayout(" << b->src_layout.name() << "->" << b->dst_layout.name() - << ")"; - }); +TVM_FFI_STATIC_INIT_BLOCK() { + namespace refl = tvm::ffi::reflection; + refl::TypeAttrDef().def( + refl::type_attr::kRepr, [](BijectiveLayout bl, ffi::Function) -> ffi::String { + return "BijectiveLayout(" + std::string(bl->src_layout.name()) + "->" + + std::string(bl->dst_layout.name()) + ")"; + }); +} TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; diff --git a/src/s_tir/meta_schedule/arg_info.cc b/src/s_tir/meta_schedule/arg_info.cc index f873f0a8a377..232824e8ba3e 100644 --- a/src/s_tir/meta_schedule/arg_info.cc +++ b/src/s_tir/meta_schedule/arg_info.cc @@ -19,6 +19,8 @@ #include #include +#include + #include "./utils.h" namespace tvm { @@ -153,12 +155,22 @@ TensorInfo TensorInfo::FromJSON(const ObjectRef& json_obj) { /******** Repr ********/ -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& n, ReprPrinter* p) { - const auto* self = n.as(); - TVM_FFI_ICHECK(self); - p->stream << "TensorInfo(\"" << self->dtype << "\", " << self->shape << ")"; - }); +TVM_FFI_STATIC_INIT_BLOCK() { + namespace refl = tvm::ffi::reflection; + refl::TypeAttrDef().def( + refl::type_attr::kRepr, [](TensorInfo ti, ffi::Function) -> ffi::String { + std::ostringstream os; + os << "TensorInfo(\"" << ti->dtype << "\", ["; + bool first = true; + for (int64_t v : ti->shape) { + if (!first) os << ", "; + os << v; + first = false; + } + os << "])"; + return os.str(); + }); +} /******** FFI ********/ TVM_FFI_STATIC_INIT_BLOCK() { TensorInfoNode::RegisterReflection(); } diff --git a/src/s_tir/meta_schedule/cost_model/cost_model.cc b/src/s_tir/meta_schedule/cost_model/cost_model.cc index 34d0cf5d77d2..6e9e8a464b9d 100644 --- a/src/s_tir/meta_schedule/cost_model/cost_model.cc +++ b/src/s_tir/meta_schedule/cost_model/cost_model.cc @@ -63,17 +63,16 @@ CostModel CostModel::PyCostModel(PyCostModelNode::FLoad f_load, // return CostModel(n); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& n, ReprPrinter* p) { - const auto* self = n.as(); - TVM_FFI_ICHECK(self); - PyCostModelNode::FAsString f_as_string = (*self).f_as_string; - TVM_FFI_ICHECK(f_as_string != nullptr) << "PyCostModel's AsString method not implemented!"; - p->stream << f_as_string(); - }); +// Pattern A (RM): auto-default repr from reflection. +// Ensure the type index is allocated for Python registration to work. +// (Previously, TVM_STATIC_IR_FUNCTOR(ReprPrinter).set_dispatch had +// a side-effect of calling PyCostModelNode::RuntimeTypeIndex() which registered the type.) TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; + // Trigger type index allocation for types that Python @register_object needs to find. + refl::ObjectDef(); + refl::ObjectDef(); refl::GlobalDef() .def_method("s_tir.meta_schedule.CostModelLoad", &CostModelNode::Load) .def_method("s_tir.meta_schedule.CostModelSave", &CostModelNode::Save) diff --git a/src/s_tir/meta_schedule/feature_extractor/feature_extractor.cc b/src/s_tir/meta_schedule/feature_extractor/feature_extractor.cc index e037231943cb..af8acc3d1cb0 100644 --- a/src/s_tir/meta_schedule/feature_extractor/feature_extractor.cc +++ b/src/s_tir/meta_schedule/feature_extractor/feature_extractor.cc @@ -40,15 +40,7 @@ FeatureExtractor FeatureExtractor::PyFeatureExtractor( return FeatureExtractor(n); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& n, ReprPrinter* p) { - const auto* self = n.as(); - TVM_FFI_ICHECK(self); - PyFeatureExtractorNode::FAsString f_as_string = (*self).f_as_string; - TVM_FFI_ICHECK(f_as_string != nullptr) - << "PyFeatureExtractor's AsString method not implemented!"; - p->stream << f_as_string(); - }); +// Pattern A (RM): auto-default repr from reflection. TVM_FFI_STATIC_INIT_BLOCK() { FeatureExtractorNode::RegisterReflection(); diff --git a/src/s_tir/meta_schedule/measure_callback/measure_callback.cc b/src/s_tir/meta_schedule/measure_callback/measure_callback.cc index 9f2a3056c258..38343338e699 100644 --- a/src/s_tir/meta_schedule/measure_callback/measure_callback.cc +++ b/src/s_tir/meta_schedule/measure_callback/measure_callback.cc @@ -50,15 +50,7 @@ ffi::Array MeasureCallback::Default() { }; } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& n, ReprPrinter* p) { - const auto* self = n.as(); - TVM_FFI_ICHECK(self); - PyMeasureCallbackNode::FAsString f_as_string = (*self).f_as_string; - TVM_FFI_ICHECK(f_as_string != nullptr) - << "PyMeasureCallback's AsString method not implemented!"; - p->stream << f_as_string(); - }); +// Pattern A (RM): auto-default repr from reflection. TVM_FFI_STATIC_INIT_BLOCK() { MeasureCallbackNode::RegisterReflection(); diff --git a/src/s_tir/meta_schedule/mutator/mutator.cc b/src/s_tir/meta_schedule/mutator/mutator.cc index 8821a239b4c9..b4b46f8e2ab2 100644 --- a/src/s_tir/meta_schedule/mutator/mutator.cc +++ b/src/s_tir/meta_schedule/mutator/mutator.cc @@ -79,14 +79,7 @@ ffi::Map Mutator::DefaultHexagon() { {Mutator::MutateParallel(/*max_jobs_per_core=*/16), FloatImm(DataType::Float(64), 0.02)}}; } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& n, ReprPrinter* p) { - const auto* self = n.as(); - TVM_FFI_ICHECK(self); - PyMutatorNode::FAsString f_as_string = (*self).f_as_string; - TVM_FFI_ICHECK(f_as_string != nullptr) << "PyMutator's AsString method not implemented!"; - p->stream << f_as_string(); - }); +// Pattern A (RM): auto-default repr from reflection. TVM_FFI_STATIC_INIT_BLOCK() { MutatorNode::RegisterReflection(); diff --git a/src/s_tir/meta_schedule/postproc/postproc.cc b/src/s_tir/meta_schedule/postproc/postproc.cc index ac8f73f260d7..d9363b811c5c 100644 --- a/src/s_tir/meta_schedule/postproc/postproc.cc +++ b/src/s_tir/meta_schedule/postproc/postproc.cc @@ -111,14 +111,7 @@ ffi::Array Postproc::DefaultHexagon() { }; } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& n, ReprPrinter* p) { - const auto* self = n.as(); - TVM_FFI_ICHECK(self); - PyPostprocNode::FAsString f_as_string = (*self).f_as_string; - TVM_FFI_ICHECK(f_as_string != nullptr) << "PyPostproc's AsString method not implemented!"; - p->stream << f_as_string(); - }); +// Pattern A (RM): auto-default repr from reflection. TVM_FFI_STATIC_INIT_BLOCK() { PostprocNode::RegisterReflection(); diff --git a/src/s_tir/meta_schedule/schedule_rule/schedule_rule.cc b/src/s_tir/meta_schedule/schedule_rule/schedule_rule.cc index 04a9a99eba5d..57ca24e53116 100644 --- a/src/s_tir/meta_schedule/schedule_rule/schedule_rule.cc +++ b/src/s_tir/meta_schedule/schedule_rule/schedule_rule.cc @@ -451,14 +451,7 @@ ffi::Array ScheduleRule::DefaultARM(const ffi::String& type) { ScheduleRule::RandomComputeLocation()); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& n, ReprPrinter* p) { - const auto* self = n.as(); - TVM_FFI_ICHECK(self); - PyScheduleRuleNode::FAsString f_as_string = (*self).f_as_string; - TVM_FFI_ICHECK(f_as_string != nullptr) << "PyScheduleRule's AsString method not implemented!"; - p->stream << f_as_string(); - }); +// Pattern A (RM): auto-default repr from reflection. TVM_FFI_STATIC_INIT_BLOCK() { ScheduleRuleNode::RegisterReflection(); diff --git a/src/s_tir/schedule/instruction.cc b/src/s_tir/schedule/instruction.cc index ef635462ab25..af891a177b66 100644 --- a/src/s_tir/schedule/instruction.cc +++ b/src/s_tir/schedule/instruction.cc @@ -103,10 +103,7 @@ ffi::String InstructionAsPythonRepr(const InstructionNode* self) { } } // namespace -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& obj, ReprPrinter* p) { - p->stream << InstructionAsPythonRepr(obj.as()); - }); +// AC: kRepr already registered below in TVM_FFI_STATIC_INIT_BLOCK. /**************** FFI ****************/ diff --git a/src/s_tir/schedule/trace.cc b/src/s_tir/schedule/trace.cc index 17b169b85749..fae3279e90f2 100644 --- a/src/s_tir/schedule/trace.cc +++ b/src/s_tir/schedule/trace.cc @@ -546,12 +546,6 @@ ffi::String TraceAsPythonRepr(const TraceNode* self) { } } // namespace -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& obj, ReprPrinter* p) { - p->stream << TraceAsPythonRepr(obj.as()); - p->stream << std::flush; - }); - /**************** Instruction Registration ****************/ struct EnterPostprocTraits : public UnpackedInstTraits { diff --git a/src/script/printer/ir/utils.h b/src/script/printer/ir/utils.h index 588e6066d9c0..6afc52cab72d 100644 --- a/src/script/printer/ir/utils.h +++ b/src/script/printer/ir/utils.h @@ -59,7 +59,7 @@ class IRFrame : public Frame { TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(IRFrame, Frame, IRFrameNode); }; -/*! \brief Redirected method for the ReprPrinter */ +/*! \brief Redirected method for the ffi repr hook */ inline std::string ReprPrintIR(const ObjectRef& obj, const PrinterConfig& cfg) { IRDocsifier d(cfg); With f(d); diff --git a/src/script/printer/relax/utils.h b/src/script/printer/relax/utils.h index d6aa98fda704..9982d31da944 100644 --- a/src/script/printer/relax/utils.h +++ b/src/script/printer/relax/utils.h @@ -67,7 +67,7 @@ class RelaxFrame : public Frame { TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(RelaxFrame, Frame, RelaxFrameNode); }; -/*! \brief Redirected method for the ReprPrinter */ +/*! \brief Redirected method for the ffi repr hook */ inline std::string ReprPrintRelax(const ObjectRef& obj, const PrinterConfig& cfg) { IRDocsifier d(cfg); With f(d); diff --git a/src/script/printer/tirx/utils.h b/src/script/printer/tirx/utils.h index fb512769b0a5..3810b357158d 100644 --- a/src/script/printer/tirx/utils.h +++ b/src/script/printer/tirx/utils.h @@ -166,7 +166,7 @@ inline ffi::Optional FindLowestVarDef(const ObjectRef& var, const IRDocsi return std::nullopt; } -/*! \brief Redirected method for the ReprPrinter */ +/*! \brief Redirected method for the ffi repr hook */ inline std::string ReprPrintTIR(const ObjectRef& obj, const PrinterConfig& cfg) { IRDocsifier d(cfg); d->SetCommonPrefix(obj, [](const ObjectRef& obj) { diff --git a/src/script/printer/utils.h b/src/script/printer/utils.h index 78f12d4983d6..efa3e7538bb3 100644 --- a/src/script/printer/utils.h +++ b/src/script/printer/utils.h @@ -19,11 +19,14 @@ #ifndef TVM_SCRIPT_PRINTER_UTILS_H_ #define TVM_SCRIPT_PRINTER_UTILS_H_ +#include #include #include +#include #include #include +#include #include #include #include @@ -35,18 +38,25 @@ namespace tvm { namespace script { namespace printer { -#define TVM_SCRIPT_REPR(ObjectType, Method) \ - TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) \ - .set_dispatch(RedirectedReprPrinterMethod); \ - TVM_STATIC_IR_FUNCTOR(TVMScriptPrinter, vtable).set_dispatch(Method); - -inline void RedirectedReprPrinterMethod(const ObjectRef& obj, ReprPrinter* p) { +#define TVM_SCRIPT_REPR(ObjectType, Method) \ + TVM_FFI_STATIC_INIT_BLOCK() { \ + namespace refl = tvm::ffi::reflection; \ + refl::TypeAttrDef().def(refl::type_attr::kRepr, \ + [](ffi::ObjectRef obj, ffi::Function) -> ffi::String { \ + return RedirectedReprPrinterMethod(obj); \ + }); \ + } \ + TVM_STATIC_IR_FUNCTOR(TVMScriptPrinter, vtable).set_dispatch(Method) + +inline std::string RedirectedReprPrinterMethod(const ObjectRef& obj) { try { - p->stream << TVMScriptPrinter::Script(obj, std::nullopt); + return TVMScriptPrinter::Script(obj, std::nullopt); } catch (const tvm::Error& e) { LOG(WARNING) << "TVMScript printer falls back to the basic address printer with the error:\n" << e.what(); - p->stream << obj->GetTypeKey() << '(' << obj.get() << ')'; + std::ostringstream os; + os << obj->GetTypeKey() << '(' << obj.get() << ')'; + return os.str(); } } diff --git a/src/target/target.cc b/src/target/target.cc index 2c093ee73fd1..3a414d40bace 100644 --- a/src/target/target.cc +++ b/src/target/target.cc @@ -488,9 +488,6 @@ TVM_FFI_STATIC_INIT_BLOCK() { [](Target target, ffi::Function) -> ffi::String { return target->str(); }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& obj, ReprPrinter* p) { - p->stream << Downcast(obj)->str(); - }); +// AC: kRepr already registered above at refl::TypeAttrDef().def(kRepr, ...) } // namespace tvm diff --git a/src/target/target_kind.cc b/src/target/target_kind.cc index 2fb5e17d5f71..8d4cd50a9a6c 100644 --- a/src/target/target_kind.cc +++ b/src/target/target_kind.cc @@ -54,11 +54,12 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& obj, ReprPrinter* p) { - const TargetKind& kind = Downcast(obj); - p->stream << kind->name; - }); +TVM_FFI_STATIC_INIT_BLOCK() { + namespace refl = tvm::ffi::reflection; + refl::TypeAttrDef().def( + refl::type_attr::kRepr, + [](TargetKind kind, ffi::Function) -> ffi::String { return kind->name; }); +} /********** Registry-related code **********/ diff --git a/src/target/virtual_device.cc b/src/target/virtual_device.cc index cd9d8f4ead92..c17298fdfdfe 100644 --- a/src/target/virtual_device.cc +++ b/src/target/virtual_device.cc @@ -26,45 +26,46 @@ #include #include +#include + namespace tvm { TVM_FFI_STATIC_INIT_BLOCK() { VirtualDeviceNode::RegisterReflection(); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = ref.as(); - p->stream << "VirtualDevice("; - if (node->IsFullyUnconstrained()) { - p->stream << "?"; - } else { - bool need_sep = false; - if (node->device_type() != kInvalidDeviceType) { - p->stream << "device_type=" << node->device_type(); - need_sep = true; - } - if (node->virtual_device_id >= 0) { - if (need_sep) { - p->stream << ", "; +TVM_FFI_STATIC_INIT_BLOCK() { + namespace refl = tvm::ffi::reflection; + refl::TypeAttrDef().def( + refl::type_attr::kRepr, [](VirtualDevice vd, ffi::Function) -> ffi::String { + auto* node = vd.get(); + std::ostringstream os; + os << "VirtualDevice("; + if (node->IsFullyUnconstrained()) { + os << "?"; + } else { + bool need_sep = false; + if (node->device_type() != kInvalidDeviceType) { + os << "device_type=" << node->device_type(); + need_sep = true; } - p->stream << "virtual_device_id=" << node->virtual_device_id; - need_sep = true; - } - if (node->target.defined()) { - if (need_sep) { - p->stream << ", "; + if (node->virtual_device_id >= 0) { + if (need_sep) os << ", "; + os << "virtual_device_id=" << node->virtual_device_id; + need_sep = true; } - p->stream << "target=" << node->target->str(); - need_sep = true; - } - if (!node->memory_scope.empty()) { - if (need_sep) { - p->stream << ", "; + if (node->target.defined()) { + if (need_sep) os << ", "; + os << "target=" << node->target->str(); + need_sep = true; + } + if (!node->memory_scope.empty()) { + if (need_sep) os << ", "; + os << "memory_scope='" << node->memory_scope << "'"; } - p->stream << "memory_scope='" << node->memory_scope << "'"; } - } - p->stream << ")"; - }); + os << ")"; + return os.str(); + }); +} VirtualDevice::VirtualDevice(int device_type_int, int virtual_device_id, Target target, MemoryScope memory_scope) { diff --git a/src/te/operation/compute_op.cc b/src/te/operation/compute_op.cc index 40ac3232c331..9df3242ecab9 100644 --- a/src/te/operation/compute_op.cc +++ b/src/te/operation/compute_op.cc @@ -45,13 +45,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { ComputeOpNode::RegisterReflection(); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "compute(" << op->name << ", body=" << op->body << ", axis=" << op->axis - << ", reduce_axis=" << op->reduce_axis << ", tag=" << op->tag - << ", attrs=" << op->attrs << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. /// Verify if ComputeOp is valid with respect to Reduce operations. static void VerifyComputeOp(const ComputeOpNode* op); diff --git a/src/te/operation/extern_op.cc b/src/te/operation/extern_op.cc index 714915609188..b6b7c17691b9 100644 --- a/src/te/operation/extern_op.cc +++ b/src/te/operation/extern_op.cc @@ -33,12 +33,7 @@ using namespace tirx; TVM_FFI_STATIC_INIT_BLOCK() { ExternOpNode::RegisterReflection(); } -// ExternOpNode -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "extern(" << op->name << ", " << op << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. int ExternOpNode::num_outputs() const { return static_cast(output_placeholders.size()); } diff --git a/src/te/operation/placeholder_op.cc b/src/te/operation/placeholder_op.cc index a063c8304572..17f4791d7615 100644 --- a/src/te/operation/placeholder_op.cc +++ b/src/te/operation/placeholder_op.cc @@ -31,12 +31,7 @@ namespace te { TVM_FFI_STATIC_INIT_BLOCK() { PlaceholderOpNode::RegisterReflection(); } -// PlaceholderOpNode -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "placeholder(" << op->name << ", " << op << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. int PlaceholderOpNode::num_outputs() const { return 1; } diff --git a/src/te/operation/scan_op.cc b/src/te/operation/scan_op.cc index 09f464f466d1..bfee2b42227f 100644 --- a/src/te/operation/scan_op.cc +++ b/src/te/operation/scan_op.cc @@ -32,11 +32,7 @@ using namespace tirx; TVM_FFI_STATIC_INIT_BLOCK() { ScanOpNode::RegisterReflection(); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* op = static_cast(node.get()); - p->stream << "scan(" << op->name << ", " << op << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. int ScanOpNode::num_outputs() const { return static_cast(update.size()); } diff --git a/src/te/tensor.cc b/src/te/tensor.cc index 031f2a0aba09..fe521ea9c949 100644 --- a/src/te/tensor.cc +++ b/src/te/tensor.cc @@ -121,11 +121,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& node, ReprPrinter* p) { - auto* t = static_cast(node.get()); - p->stream << "Tensor(shape=" << t->shape << ", op.name=" << t->op->name << ')'; - }); +// Pattern A (RM): auto-default repr from reflection. // Other tensor ops. TVM_FFI_STATIC_INIT_BLOCK() { diff --git a/src/tirx/ir/expr.cc b/src/tirx/ir/expr.cc index f4130e70d6c7..a3f300a03b9c 100644 --- a/src/tirx/ir/expr.cc +++ b/src/tirx/ir/expr.cc @@ -84,15 +84,8 @@ TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; refl::GlobalDef().def("tirx.convert", [](ffi::Variant> expr) { return expr; }); - // Register __ffi_repr__ for Var/SizeVar so repr shows just the name - refl::TypeAttrDef().def(refl::type_attr::kRepr, - [](Var var, ffi::Function) -> ffi::String { - return std::string(var->name_hint); - }); - refl::TypeAttrDef().def(refl::type_attr::kRepr, - [](SizeVar var, ffi::Function) -> ffi::String { - return std::string(var->name_hint); - }); + // Note: kRepr for VarNode/SizeVarNode is registered via TVM_SCRIPT_REPR in + // src/script/printer/tirx/expr.cc (-> ReprPrintTIR which delegates to TVMScriptPrinter). } #define TVM_DEFINE_BINOP_CONSTRUCTOR(Name) \ diff --git a/src/tirx/ir/transform.cc b/src/tirx/ir/transform.cc index 14b0cf6b1102..7f55547eefc4 100644 --- a/src/tirx/ir/transform.cc +++ b/src/tirx/ir/transform.cc @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include namespace tvm { @@ -159,12 +159,7 @@ TVM_FFI_STATIC_INIT_BLOCK() { }); } -TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable) - .set_dispatch([](const ObjectRef& ref, ReprPrinter* p) { - auto* node = static_cast(ref.get()); - const PassInfo info = node->Info(); - p->stream << "PrimFuncPass(" << info->name << ", opt_level=" << info->opt_level << ")"; - }); +// Pattern A (RM): auto-default repr from reflection. } // namespace transform } // namespace tirx diff --git a/tests/python/tirx-transform/test_tir_transform_vectorize.py b/tests/python/tirx-transform/test_tir_transform_vectorize.py index 02b82df6e4c5..ec38c4a9755b 100644 --- a/tests/python/tirx-transform/test_tir_transform_vectorize.py +++ b/tests/python/tirx-transform/test_tir_transform_vectorize.py @@ -499,7 +499,7 @@ def main(A: T.Buffer((25,), "int32")): for j in T.vectorized(n): A[j] = 3 - error_msg = "Failed to vectorize loop with extent n for target \\(nullptr\\)" + error_msg = "Failed to vectorize loop with extent n for target None" with pytest.raises(tvm.error.InternalError, match=error_msg): tvm.tirx.transform.VectorizeLoop()(Mod) From 032908bbd6bcbf19f209847d605378b53b15a8ad Mon Sep 17 00:00:00 2001 From: tqchen Date: Mon, 27 Apr 2026 16:05:31 +0000 Subject: [PATCH 2/5] [REFACTOR] Apply clang-format to repr.cc and script_printer.cc --- src/node/repr.cc | 15 +++++---------- src/node/script_printer.cc | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/node/repr.cc b/src/node/repr.cc index 0ce7d0c781f5..8c188421d359 100644 --- a/src/node/repr.cc +++ b/src/node/repr.cc @@ -40,9 +40,7 @@ namespace tvm { -void Dump(const runtime::ObjectRef& n) { - std::cerr << ffi::ReprPrint(ffi::Any(n)) << "\n"; -} +void Dump(const runtime::ObjectRef& n) { std::cerr << ffi::ReprPrint(ffi::Any(n)) << "\n"; } void Dump(const runtime::Object* n) { Dump(runtime::GetRef(n)); } @@ -98,19 +96,16 @@ TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; // node.AsRepr: backward-compatible Python entry point. // Python's tvm.runtime._ffi_node_api sets __object_repr__ = AsRepr via init_ffi_api. - refl::GlobalDef().def("node.AsRepr", [](ffi::Any obj) -> ffi::String { - return ffi::ReprPrint(obj); - }); + refl::GlobalDef().def("node.AsRepr", + [](ffi::Any obj) -> ffi::String { return ffi::ReprPrint(obj); }); // Register __ffi_repr__ for AccessPath/AccessStep so that ffi.ReprPrint // uses the concise ".field[idx]" format. refl::TypeAttrDef().def( - refl::type_attr::kRepr, - [](ffi::reflection::AccessPath path, ffi::Function) -> ffi::String { + refl::type_attr::kRepr, [](ffi::reflection::AccessPath path, ffi::Function) -> ffi::String { return FormatAccessPath(path); }); refl::TypeAttrDef().def( - refl::type_attr::kRepr, - [](ffi::reflection::AccessStep step, ffi::Function) -> ffi::String { + refl::type_attr::kRepr, [](ffi::reflection::AccessStep step, ffi::Function) -> ffi::String { std::ostringstream os; FormatAccessStep(os, step); return os.str(); diff --git a/src/node/script_printer.cc b/src/node/script_printer.cc index 43cd651cb5fc..2edf7860b590 100644 --- a/src/node/script_printer.cc +++ b/src/node/script_printer.cc @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ +#include #include #include #include #include -#include #include #include From c14dbf4831c73360003d9e5e6d739e8d394ba6af Mon Sep 17 00:00:00 2001 From: tqchen Date: Mon, 27 Apr 2026 16:05:41 +0000 Subject: [PATCH 3/5] [REFACTOR] Apply clang-format to remaining changed files --- src/relax/ir/dataflow_pattern.cc | 23 +++++++++++------------ src/s_tir/data_layout.cc | 8 ++++---- src/s_tir/meta_schedule/arg_info.cc | 26 +++++++++++++------------- src/s_tir/schedule/instruction.cc | 12 ++++++------ src/script/printer/utils.h | 14 +++++++------- 5 files changed, 41 insertions(+), 42 deletions(-) diff --git a/src/relax/ir/dataflow_pattern.cc b/src/relax/ir/dataflow_pattern.cc index 85536f14475f..90a837923691 100644 --- a/src/relax/ir/dataflow_pattern.cc +++ b/src/relax/ir/dataflow_pattern.cc @@ -67,18 +67,17 @@ struct PatternReprPrinterHelper { void Print(const ObjectRef& x) { stream << ffi::ReprPrint(ffi::Any(x)); } }; -#define RELAX_PATTERN_PRINTER_DEF(NODE_TYPE, REPR_LAMBDA) \ - TVM_FFI_STATIC_INIT_BLOCK() { \ - namespace refl = tvm::ffi::reflection; \ - refl::TypeAttrDef().def( \ - refl::type_attr::kRepr, \ - [](ffi::ObjectRef ref, ffi::Function) -> ffi::String { \ - auto* node = static_cast(ref.get()); \ - PatternReprPrinterHelper printer; \ - auto* p = &printer; \ - REPR_LAMBDA(p, node); \ - return printer.stream.str(); \ - }); \ +#define RELAX_PATTERN_PRINTER_DEF(NODE_TYPE, REPR_LAMBDA) \ + TVM_FFI_STATIC_INIT_BLOCK() { \ + namespace refl = tvm::ffi::reflection; \ + refl::TypeAttrDef().def(refl::type_attr::kRepr, \ + [](ffi::ObjectRef ref, ffi::Function) -> ffi::String { \ + auto* node = static_cast(ref.get()); \ + PatternReprPrinterHelper printer; \ + auto* p = &printer; \ + REPR_LAMBDA(p, node); \ + return printer.stream.str(); \ + }); \ } ExternFuncPattern::ExternFuncPattern(ffi::String global_symbol) { diff --git a/src/s_tir/data_layout.cc b/src/s_tir/data_layout.cc index ccf617ac3d22..392fd7a87466 100644 --- a/src/s_tir/data_layout.cc +++ b/src/s_tir/data_layout.cc @@ -291,10 +291,10 @@ int32_t Layout::FactorOf(const LayoutAxis& axis) const { TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; - refl::TypeAttrDef().def( - refl::type_attr::kRepr, [](Layout l, ffi::Function) -> ffi::String { - return "Layout(" + std::string(l->name) + ")"; - }); + refl::TypeAttrDef().def(refl::type_attr::kRepr, + [](Layout l, ffi::Function) -> ffi::String { + return "Layout(" + std::string(l->name) + ")"; + }); } inline bool GetStoreRule(ffi::Array* index_rule, ffi::Array* shape_rule, diff --git a/src/s_tir/meta_schedule/arg_info.cc b/src/s_tir/meta_schedule/arg_info.cc index 232824e8ba3e..bcb2fd81c68e 100644 --- a/src/s_tir/meta_schedule/arg_info.cc +++ b/src/s_tir/meta_schedule/arg_info.cc @@ -157,19 +157,19 @@ TensorInfo TensorInfo::FromJSON(const ObjectRef& json_obj) { TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; - refl::TypeAttrDef().def( - refl::type_attr::kRepr, [](TensorInfo ti, ffi::Function) -> ffi::String { - std::ostringstream os; - os << "TensorInfo(\"" << ti->dtype << "\", ["; - bool first = true; - for (int64_t v : ti->shape) { - if (!first) os << ", "; - os << v; - first = false; - } - os << "])"; - return os.str(); - }); + refl::TypeAttrDef().def(refl::type_attr::kRepr, + [](TensorInfo ti, ffi::Function) -> ffi::String { + std::ostringstream os; + os << "TensorInfo(\"" << ti->dtype << "\", ["; + bool first = true; + for (int64_t v : ti->shape) { + if (!first) os << ", "; + os << v; + first = false; + } + os << "])"; + return os.str(); + }); } /******** FFI ********/ diff --git a/src/s_tir/schedule/instruction.cc b/src/s_tir/schedule/instruction.cc index af891a177b66..a75ec7a333d8 100644 --- a/src/s_tir/schedule/instruction.cc +++ b/src/s_tir/schedule/instruction.cc @@ -79,8 +79,8 @@ ffi::String InstructionAsPythonRepr(const InstructionNode* self) { } else if (obj.as() || obj.as()) { inputs.push_back(obj); } else if (const auto* expr = obj.as()) { - PrimExpr new_expr = Substitute( - ffi::GetRef(expr), [](const Var& var) -> ffi::Optional { + PrimExpr new_expr = + Substitute(ffi::GetRef(expr), [](const Var& var) -> ffi::Optional { ObjectPtr new_var = ffi::make_object(*var.get()); new_var->name_hint = "_"; return Var(new_var); @@ -116,10 +116,10 @@ TVM_FFI_STATIC_INIT_BLOCK() { ffi::Array outputs) -> Instruction { return Instruction(kind, inputs, attrs, outputs); }); - refl::TypeAttrDef().def( - refl::type_attr::kRepr, [](Instruction inst, ffi::Function) -> ffi::String { - return InstructionAsPythonRepr(inst.get()); - }); + refl::TypeAttrDef().def(refl::type_attr::kRepr, + [](Instruction inst, ffi::Function) -> ffi::String { + return InstructionAsPythonRepr(inst.get()); + }); } } // namespace s_tir diff --git a/src/script/printer/utils.h b/src/script/printer/utils.h index efa3e7538bb3..1ec51450e30b 100644 --- a/src/script/printer/utils.h +++ b/src/script/printer/utils.h @@ -38,14 +38,14 @@ namespace tvm { namespace script { namespace printer { -#define TVM_SCRIPT_REPR(ObjectType, Method) \ - TVM_FFI_STATIC_INIT_BLOCK() { \ - namespace refl = tvm::ffi::reflection; \ - refl::TypeAttrDef().def(refl::type_attr::kRepr, \ +#define TVM_SCRIPT_REPR(ObjectType, Method) \ + TVM_FFI_STATIC_INIT_BLOCK() { \ + namespace refl = tvm::ffi::reflection; \ + refl::TypeAttrDef().def(refl::type_attr::kRepr, \ [](ffi::ObjectRef obj, ffi::Function) -> ffi::String { \ - return RedirectedReprPrinterMethod(obj); \ - }); \ - } \ + return RedirectedReprPrinterMethod(obj); \ + }); \ + } \ TVM_STATIC_IR_FUNCTOR(TVMScriptPrinter, vtable).set_dispatch(Method) inline std::string RedirectedReprPrinterMethod(const ObjectRef& obj) { From 1aba29544be820a1fe798e4dc948eae08282870f Mon Sep 17 00:00:00 2001 From: tqchen Date: Mon, 27 Apr 2026 16:54:02 +0000 Subject: [PATCH 4/5] [REFACTOR][NODE] Remove orphan str_escape.h include from repr.cc The include was carried over from the legacy repr_printer.cc but StrEscape is not referenced in the new repr.cc. --- src/node/repr.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/node/repr.cc b/src/node/repr.cc index 8c188421d359..7f566756de3c 100644 --- a/src/node/repr.cc +++ b/src/node/repr.cc @@ -36,8 +36,6 @@ #include #include -#include "../support/str_escape.h" - namespace tvm { void Dump(const runtime::ObjectRef& n) { std::cerr << ffi::ReprPrint(ffi::Any(n)) << "\n"; } From e9a5055b2d5c596cb9a95ed98c771d8417b188fb Mon Sep 17 00:00:00 2001 From: tqchen Date: Mon, 27 Apr 2026 17:04:53 +0000 Subject: [PATCH 5/5] [REFACTOR][NODE] Make AccessPath repr recursive via __ffi_repr__ Manual linked-list traversal in FormatAccessPath duplicated work that ffi::ReprPrint's __ffi_repr__ dispatch already does for free; the manual walk also invited a null-deref concern from static analysis. Recurse through parent and step via the kRepr hook; delete the manual walker. AccessStep's formatting is now inline in its own kRepr lambda, using the fn_repr argument for map-key sub-repr as intended. --- src/node/repr.cc | 95 ++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 55 deletions(-) diff --git a/src/node/repr.cc b/src/node/repr.cc index 7f566756de3c..a194708c6d5c 100644 --- a/src/node/repr.cc +++ b/src/node/repr.cc @@ -34,7 +34,6 @@ #include #include -#include namespace tvm { @@ -42,54 +41,6 @@ void Dump(const runtime::ObjectRef& n) { std::cerr << ffi::ReprPrint(ffi::Any(n) void Dump(const runtime::Object* n) { Dump(runtime::GetRef(n)); } -namespace { -/*! - * \brief Format an AccessStep as a concise string fragment. - */ -void FormatAccessStep(std::ostringstream& os, const ffi::reflection::AccessStep& step) { - using ffi::reflection::AccessKind; - static const ffi::Function repr_fn = ffi::Function::GetGlobal("ffi.ReprPrint").value(); - switch (step->kind) { - case AccessKind::kAttr: - os << "." << step->key.cast(); - break; - case AccessKind::kArrayItem: - os << "[" << step->key.cast() << "]"; - break; - case AccessKind::kMapItem: - os << "[" << repr_fn(step->key).cast() << "]"; - break; - case AccessKind::kAttrMissing: - os << "." << step->key.cast() << "?"; - break; - case AccessKind::kArrayItemMissing: - os << "[" << step->key.cast() << "]?"; - break; - case AccessKind::kMapItemMissing: - os << "[" << repr_fn(step->key).cast() << "]?"; - break; - } -} - -/*! - * \brief Format an AccessPath as ".field[idx]". - */ -ffi::String FormatAccessPath(const ffi::reflection::AccessPath& path) { - std::vector steps; - const ffi::reflection::AccessPathObj* cur = path.get(); - while (cur->step.defined()) { - steps.push_back(cur->step.value()); - cur = static_cast(cur->parent.get()); - } - std::ostringstream os; - os << ""; - for (auto it = steps.rbegin(); it != steps.rend(); ++it) { - FormatAccessStep(os, *it); - } - return os.str(); -} -} // namespace - TVM_FFI_STATIC_INIT_BLOCK() { namespace refl = tvm::ffi::reflection; // node.AsRepr: backward-compatible Python entry point. @@ -98,14 +49,48 @@ TVM_FFI_STATIC_INIT_BLOCK() { [](ffi::Any obj) -> ffi::String { return ffi::ReprPrint(obj); }); // Register __ffi_repr__ for AccessPath/AccessStep so that ffi.ReprPrint // uses the concise ".field[idx]" format. - refl::TypeAttrDef().def( - refl::type_attr::kRepr, [](ffi::reflection::AccessPath path, ffi::Function) -> ffi::String { - return FormatAccessPath(path); - }); + // + // AccessStep: format one step fragment (e.g. ".field", "[0]", "[key]?"). refl::TypeAttrDef().def( - refl::type_attr::kRepr, [](ffi::reflection::AccessStep step, ffi::Function) -> ffi::String { + refl::type_attr::kRepr, + [](ffi::reflection::AccessStep step, ffi::Function fn_repr) -> ffi::String { + using ffi::reflection::AccessKind; + std::ostringstream os; + switch (step->kind) { + case AccessKind::kAttr: + os << "." << step->key.cast(); + break; + case AccessKind::kArrayItem: + os << "[" << step->key.cast() << "]"; + break; + case AccessKind::kMapItem: + os << "[" << fn_repr(step->key).cast() << "]"; + break; + case AccessKind::kAttrMissing: + os << "." << step->key.cast() << "?"; + break; + case AccessKind::kArrayItemMissing: + os << "[" << step->key.cast() << "]?"; + break; + case AccessKind::kMapItemMissing: + os << "[" << fn_repr(step->key).cast() << "]?"; + break; + } + return os.str(); + }); + // AccessPath: recurse through parent via fn_repr rather than walking the + // linked list manually. Root (no step) emits ""; each non-root node + // prepends its parent's repr and appends the current step's repr. + refl::TypeAttrDef().def( + refl::type_attr::kRepr, + [](ffi::reflection::AccessPath path, ffi::Function fn_repr) -> ffi::String { + if (!path->step.has_value()) { + // Root node: no parent, no step. + return ""; + } std::ostringstream os; - FormatAccessStep(os, step); + os << fn_repr(path->parent.value()).cast(); + os << fn_repr(path->step.value()).cast(); return os.str(); }); }