Skip to content

Commit 2c015b9

Browse files
tqchenShiboXing
authored andcommitted
[FFI][PYTHON] Improve the traceback generation in python (apache#18141)
* [FFI][PYTHON] Improve the traceback generation in python This PR improves the traceback generation in python by bringing a more precise caching of the code object. * Fix lint
1 parent 76e3060 commit 2c015b9

3 files changed

Lines changed: 69 additions & 50 deletions

File tree

python/tvm/ffi/cython/base.pxi

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,6 @@ from cpython cimport PyErr_CheckSignals, PyGILState_Ensure, PyGILState_Release,
2424
from cpython cimport pycapsule, PyCapsule_Destructor
2525
from cpython cimport PyErr_SetNone
2626

27-
# Cython internal for custom traceback creation
28-
cdef extern from *:
29-
"""
30-
static void __Pyx_AddTraceback(const char *funcname, int c_line, int py_line, const char *filename);
31-
static void __Pyx_AddTraceback_(const char *funcname, int c_line, int py_line, const char *filename) {
32-
__Pyx_AddTraceback(funcname, c_line, py_line, filename);
33-
}
34-
"""
35-
# __Pyx_AddTraceback is a Cython internal function to add custom traceback to exception
36-
# We declare __Pyx_AddTraceback_ and redirect to it to avoid mixed C/C++ calling
37-
# This is a nice hack to enable us to create customized traceback refers to c++ code
38-
void __Pyx_AddTraceback_(const char *funcname, int c_line, int py_line, const char *filename)
3927

4028
# Cython binding for TVM FFI C API
4129
cdef extern from "tvm/ffi/c_api.h":

python/tvm/ffi/cython/error.pxi

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,43 +26,6 @@ _WITH_APPEND_TRACEBACK = None
2626
_TRACEBACK_TO_STR = None
2727

2828

29-
cdef inline int _raise_not_implemented_with_extra_frame(
30-
const char* func_name,
31-
int lineno,
32-
const char* file_path
33-
) except -1:
34-
"""Helper util, raise internal """
35-
PyErr_SetNone(NotImplementedError)
36-
__Pyx_AddTraceback_(func_name, 0, lineno, file_path)
37-
return -1
38-
39-
40-
def _append_traceback_frame(tb, filename, lineno, func):
41-
"""Append tracebacks to frame.
42-
43-
Parameters
44-
----------
45-
tb : types.TracebackType
46-
The traceback to append to.
47-
48-
filename : str
49-
The filename of the traceback
50-
lineno : int
51-
The line number of the traceback
52-
func : str
53-
The function name of the traceback
54-
"""
55-
try:
56-
_raise_not_implemented_with_extra_frame(
57-
c_str(func), lineno, c_str(filename)
58-
)
59-
except NotImplementedError as e:
60-
dummy_tb = e.__traceback__
61-
# skip the first frame which is the dummy frame
62-
new_frame = dummy_tb.tb_next
63-
return types.TracebackType(tb, new_frame.tb_frame, new_frame.tb_lasti, new_frame.tb_lineno)
64-
65-
6629
cdef class Error(Object):
6730
"""Base class for all FFI errors, usually they are attached to errors
6831

python/tvm/ffi/error.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
# pylint: disable=invalid-name
1818
"""Error handling."""
1919
import re
20+
import types
21+
import sys
22+
import ast
2023
from . import core
2124

2225

@@ -48,11 +51,76 @@ def _parse_traceback(traceback):
4851
return result
4952

5053

54+
class TracebackManager:
55+
"""
56+
Helper to manage traceback generation
57+
"""
58+
59+
def __init__(self):
60+
self._code_cache = {}
61+
62+
def _get_cached_code_object(self, filename, lineno, func):
63+
# Hack to create a code object that points to the correct
64+
# line number and function name
65+
key = (filename, lineno, func)
66+
# cache the code object to avoid re-creating it
67+
if key in self._code_cache:
68+
return self._code_cache[key]
69+
# Parse to AST and zero out column info
70+
# since column info are not accurate in original trace
71+
tree = ast.parse("_getframe()", filename=filename, mode="eval")
72+
for node in ast.walk(tree):
73+
if hasattr(node, "col_offset"):
74+
node.col_offset = 0
75+
if hasattr(node, "end_col_offset"):
76+
node.end_col_offset = 0
77+
# call into get frame, bt changes the context
78+
code_object = compile(tree, filename, "eval")
79+
# replace the function name and line number
80+
code_object = code_object.replace(co_name=func, co_firstlineno=lineno)
81+
self._code_cache[key] = code_object
82+
return code_object
83+
84+
def _create_frame(self, filename, lineno, func):
85+
"""Create a frame object from the filename, lineno, and func"""
86+
code_object = self._get_cached_code_object(filename, lineno, func)
87+
# call into get frame, but changes the context so the code
88+
# points to the correct frame
89+
context = {"_getframe": sys._getframe}
90+
# pylint: disable=eval-used
91+
return eval(code_object, context, context)
92+
93+
def append_traceback(self, tb, filename, lineno, func):
94+
"""Append a traceback to the given traceback
95+
96+
Parameters
97+
----------
98+
tb : types.TracebackType
99+
The traceback to append to.
100+
filename : str
101+
The filename of the traceback
102+
lineno : int
103+
The line number of the traceback
104+
func : str
105+
The function name of the traceback
106+
107+
Returns
108+
-------
109+
new_tb : types.TracebackType
110+
The new traceback with the appended frame.
111+
"""
112+
frame = self._create_frame(filename, lineno, func)
113+
return types.TracebackType(tb, frame, frame.f_lasti, lineno)
114+
115+
116+
_TRACEBACK_MANAGER = TracebackManager()
117+
118+
51119
def _with_append_traceback(py_error, traceback):
52120
"""Append the traceback to the py_error and return it"""
53121
tb = py_error.__traceback__
54122
for filename, lineno, func in reversed(_parse_traceback(traceback)):
55-
tb = core._append_traceback_frame(tb, filename, lineno, func)
123+
tb = _TRACEBACK_MANAGER.append_traceback(tb, filename, lineno, func)
56124
return py_error.with_traceback(tb)
57125

58126

0 commit comments

Comments
 (0)