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 445bd71b5 [FEAT] Render codegen errors with TVMScript locations (#83)
445bd71b5 is described below

commit 445bd71b50c1fd13ce6e8255b741ff2cbdcd6f45
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Oct 26 10:04:42 2026 -0400

    [FEAT] Render codegen errors with TVMScript locations (#83)
    
    Attach visit context during expression and statement dispatch and render
    codegen failures with underlined TVMScript locations. Share location
    rendering with transformation passes and prefer exact failing
    expressions before ancestor fallbacks.
---
 include/tvm/ir/expr_functor.h        |  7 ++++++-
 include/tvm/ir/transform.h           | 17 +++++++++++++++++
 include/tvm/tirx/stmt_functor.h      |  7 ++++++-
 python/tvm_tirx/tirx/_compile.py     | 10 +++-------
 src/codegen/codegen.cc               |  7 ++++++-
 src/ir/transform.cc                  | 24 ++++++++++++++++--------
 tests/cpp/ir/expr_functor_test.cc    | 26 ++++++++++++++++++++++++++
 tests/python/codegen/c/test_basic.py | 21 +++++++++++++++++++++
 8 files changed, 101 insertions(+), 18 deletions(-)

diff --git a/include/tvm/ir/expr_functor.h b/include/tvm/ir/expr_functor.h
index 786b25742..374d78136 100644
--- a/include/tvm/ir/expr_functor.h
+++ b/include/tvm/ir/expr_functor.h
@@ -70,7 +70,12 @@ class ExprFunctor<R(const Expr&, Args...)> {
    */
   TVM_FFI_INLINE virtual R Dispatch(const Expr& node, Args... args) {
     TVM_FFI_ICHECK(node.defined()) << "Cannot dispatch a null expression";
-    return (*vtable_)(node, this, std::forward<Args>(args)...);
+    try {
+      return (*vtable_)(node, this, std::forward<Args>(args)...);
+    } catch (ffi::Error& error) {
+      ffi::detail::UpdateVisitErrorContext(error, node);
+      throw;
+    }
   }
 
   virtual R Dispatch_(const LambdaExprObj* node, Args... args) {
diff --git a/include/tvm/ir/transform.h b/include/tvm/ir/transform.h
index 8c1b7ce77..bd3035a6a 100644
--- a/include/tvm/ir/transform.h
+++ b/include/tvm/ir/transform.h
@@ -450,6 +450,23 @@ inline Pass 
CreateModulePass(std::function<IRModule(IRModule, PassContext)> pass
  */
 TVM_TIRX_DLL Pass PrintIR(ffi::String header = "");
 
+/*!
+ * \brief Append an underlined TVMScript location from an error's visit 
context.
+ *
+ * Preserve the original error kind, message, and backtrace. Return the 
original
+ * error if the context cannot be resolved or printed. A rendered error drops
+ * its visit context so outer handlers do not append the location again.
+ *
+ * \param err The original error.
+ * \param mod The module containing the visited nodes.
+ * \param context The operation description preceding the location.
+ * \param func Optional function to search before falling back to the module.
+ * \return The enriched error, or the original error on fallback.
+ */
+TVM_TIRX_DLL ffi::Error EnrichErrorWithContext(
+    const ffi::Error& err, const IRModule& mod, ffi::String context,
+    ffi::Optional<GlobalVar> func = ffi::Optional<GlobalVar>(std::nullopt));
+
 /*!
  * \brief Enrich a pass-time error with a TVMScript-rendered, underlined source
  *        location derived from the error's embedded VisitErrorContext.
diff --git a/include/tvm/tirx/stmt_functor.h b/include/tvm/tirx/stmt_functor.h
index fdf26f476..54279a832 100644
--- a/include/tvm/tirx/stmt_functor.h
+++ b/include/tvm/tirx/stmt_functor.h
@@ -69,7 +69,12 @@ class StmtFunctor<R(const Stmt&, Args...)> {
   /*! \brief Dispatch to a node hook, including registered ancestor hooks. */
   TVM_FFI_INLINE virtual R Dispatch(const Stmt& node, Args... args) {
     TVM_FFI_ICHECK(node.defined()) << "Cannot dispatch a null statement";
-    return (*vtable_)(node, this, std::forward<Args>(args)...);
+    try {
+      return (*vtable_)(node, this, std::forward<Args>(args)...);
+    } catch (ffi::Error& error) {
+      ffi::detail::UpdateVisitErrorContext(error, node);
+      throw;
+    }
   }
 
   virtual R Dispatch_(const BindObj* node, Args... args) {
diff --git a/python/tvm_tirx/tirx/_compile.py b/python/tvm_tirx/tirx/_compile.py
index 6c85b8257..ac80f960f 100644
--- a/python/tvm_tirx/tirx/_compile.py
+++ b/python/tvm_tirx/tirx/_compile.py
@@ -132,13 +132,9 @@ def _split_host_device_modules(
 
 def _build_for_target(mod: IRModule, target: Target) -> tvm_ffi.Module:
     """Build a runtime module from an IRModule and a Target."""
-    if 
tvm_tirx.ir.transform.PassContext.current().config.get("tirx.disable_assert", 
False):
-        mod = tvm_tirx.tirx.transform.SkipAssert()(mod)
-    build_f_name = "target.build." + target.kind.name
-    bf = tvm_ffi.get_global_func(build_f_name)
-    if bf is None:
-        raise ValueError(f"{build_f_name} is not enabled")
-    return bf(mod, target)
+    # Retain the Python ValueError for an unavailable backend.
+    tvm_ffi.get_global_func("target.build." + target.kind.name)
+    return tvm_tirx.codegen.build_module(mod, target)
 
 
 def _codegen_host_and_device(
diff --git a/src/codegen/codegen.cc b/src/codegen/codegen.cc
index 600052d71..fd0120183 100644
--- a/src/codegen/codegen.cc
+++ b/src/codegen/codegen.cc
@@ -22,6 +22,7 @@
  */
 #include <tvm/codegen/codegen.h>
 #include <tvm/ffi/reflection/registry.h>
+#include <tvm/ir/transform.h>
 #include <tvm/tirx/transform.h>
 
 namespace tvm {
@@ -36,7 +37,11 @@ ffi::Module Build(IRModule mod, Target target) {
   std::string build_f_name = "target.build." + target->kind->name;
   const auto bf = tvm::ffi::Function::GetGlobal(build_f_name);
   TVM_FFI_ICHECK(bf.has_value()) << build_f_name << " is not enabled";
-  return (*bf)(mod, target).cast<ffi::Module>();
+  try {
+    return (*bf)(mod, target).cast<ffi::Module>();
+  } catch (const ffi::Error& error) {
+    throw transform::EnrichErrorWithContext(error, mod, "Error in codegen: " + 
target->kind->name);
+  }
 }
 
 TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/ir/transform.cc b/src/ir/transform.cc
index 965dfd20d..12e4c5ba5 100644
--- a/src/ir/transform.cc
+++ b/src/ir/transform.cc
@@ -316,8 +316,8 @@ std::optional<std::string> RenderScriptWithUnderline(const 
ffi::ObjectRef& node,
 }
 }  // namespace
 
-ffi::Error EnrichPassErrorWithContext(const ffi::Error& err, const IRModule& 
mod,
-                                      ffi::String pass_name, 
ffi::Optional<GlobalVar> func) {
+ffi::Error EnrichErrorWithContext(const ffi::Error& err, const IRModule& mod, 
ffi::String context,
+                                  ffi::Optional<GlobalVar> func) {
   namespace ffi = tvm::ffi;
   // No visit-context payload => nothing to resolve; return the error 
untouched.
   // (The enriched error below drops the payload, so an outer catch that
@@ -337,8 +337,13 @@ ffi::Error EnrichPassErrorWithContext(const ffi::Error& 
err, const IRModule& mod
   }
 
   auto try_render = [&](const ffi::ObjectRef& search_root) -> 
std::optional<std::string> {
-    auto paths = ffi::VisitErrorContext::FindAccessPaths(search_root, 
visit_ctx.value(),
-                                                         
/*allow_prefix_match=*/true);
+    // Prefer the precise throw site. Prefix matches may include unrelated
+    // leaves of an enclosing statement before the full match in traversal 
order.
+    auto paths = ffi::VisitErrorContext::FindAccessPaths(search_root, 
visit_ctx.value());
+    if (paths.empty()) {
+      paths = ffi::VisitErrorContext::FindAccessPaths(search_root, 
visit_ctx.value(),
+                                                      
/*allow_prefix_match=*/true);
+    }
     if (paths.empty()) return std::nullopt;
     return RenderScriptWithUnderline(search_root, paths[0]);
   };
@@ -350,16 +355,19 @@ ffi::Error EnrichPassErrorWithContext(const ffi::Error& 
err, const IRModule& mod
   }
   if (!snippet) return err;
 
-  // Append the pass name + underlined TVMScript location. Preserve kind,
+  // Append the operation context + underlined TVMScript location. Preserve 
kind,
   // original message, and backtrace; drop the VisitErrorContext payload so an
   // outer catch does not re-enrich.
   std::ostringstream suffix;
-  suffix << "\n\nError in pass: " << pass_name << "\n"
-         << kLocationMarker << "\n"
-         << snippet.value();
+  suffix << "\n\n" << context << "\n" << kLocationMarker << "\n" << 
snippet.value();
   return ffi::Error(err.kind(), err.message() + suffix.str(), err.backtrace());
 }
 
+ffi::Error EnrichPassErrorWithContext(const ffi::Error& err, const IRModule& 
mod,
+                                      ffi::String pass_name, 
ffi::Optional<GlobalVar> func) {
+  return EnrichErrorWithContext(err, mod, "Error in pass: " + pass_name, func);
+}
+
 IRModule Pass::operator()(IRModule mod, const PassContext& pass_ctx) const {
   const PassObj* node = operator->();
   TVM_FFI_ICHECK(node != nullptr);
diff --git a/tests/cpp/ir/expr_functor_test.cc 
b/tests/cpp/ir/expr_functor_test.cc
index dc88a92ff..dd4edf456 100644
--- a/tests/cpp/ir/expr_functor_test.cc
+++ b/tests/cpp/ir/expr_functor_test.cc
@@ -114,5 +114,31 @@ TEST(ExprMutator, 
NativeErrorContextThroughStructuralFallback) {
   EXPECT_TRUE(pattern[2].same_as(pair));
 }
 
+class ThrowExprFunctor : public ExprFunctor<void(const Expr&)> {
+ public:
+  using ExprFunctor::Dispatch_;
+  ffi::Error error{"ValueError", "dispatch failure", "original backtrace"};
+  void Dispatch_(const IntImmObj*) override { throw error; }
+};
+
+TEST(ExprFunctor, AttachesContextAndPreservesError) {
+  ThrowExprFunctor functor;
+  auto node = IntImm::Int32(7);
+  try {
+    functor(node);
+    FAIL() << "Expected a dispatch failure";
+  } catch (const ffi::Error& error) {
+    EXPECT_TRUE(error.same_as(functor.error));
+    EXPECT_EQ(error.kind(), "ValueError");
+    EXPECT_EQ(error.message(), "dispatch failure");
+    EXPECT_EQ(error.backtrace(), "original backtrace");
+    auto context = ffi::VisitErrorContext::TryGetFromError(error);
+    ASSERT_TRUE(context.has_value());
+    const auto& pattern = context.value()->reverse_visit_pattern;
+    ASSERT_EQ(pattern.size(), 1);
+    EXPECT_TRUE(pattern[0].same_as(node));
+  }
+}
+
 }  // namespace
 }  // namespace tvm
diff --git a/tests/python/codegen/c/test_basic.py 
b/tests/python/codegen/c/test_basic.py
index e0aec80d3..5cc75fc7a 100644
--- a/tests/python/codegen/c/test_basic.py
+++ b/tests/python/codegen/c/test_basic.py
@@ -380,5 +380,26 @@ def test_min_max_nan_preserving(dtype):
     )
 
 
+def test_unsupported_vector_diagnostic():
+    from tvm_tirx import ir, tirx
+    from tvm_tirx.ir import prim
+
+    value = prim.Broadcast(prim.const(1.0, "float32"), 17)
+    var = ir.Var("bad_value", ir.PrimType("float32x17"))
+    func = tirx.Function([], ir.Bind(var, value)).with_attr("global_symbol", 
"main")
+    mod = tvm_tirx.IRModule({"main": func})
+    with pytest.raises(RuntimeError, match="Cannot convert type float32x17 to 
C type") as caught:
+        tvm_tirx.codegen.build_module(mod, "c")
+
+    message = str(caught.value)
+    assert "Error in codegen: c" in message
+    assert message.count("Location (TVMScript):") == 1
+    expression = "T.Broadcast(T.float32(1.0), 17)"
+    lines = message.splitlines()
+    index = next(i for i, line in enumerate(lines) if expression in line)
+    start = lines[index].index(expression)
+    assert lines[index + 1] == " " * start + "^" * len(expression)
+
+
 if __name__ == "__main__":
     tvm_tirx.testing.main()

Reply via email to