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()