Commit 4b7c277f authored by Kevin Modzelewski's avatar Kevin Modzelewski

Move a bunch of stuff out of UnwindSession

It was pretty unwieldy and handled some disparate parts of the
exception-raising process.  I think things are a bit cleaner now:
- cxx_unwind.cpp handles C++ unwinding semantics
- unwinding.cpp converts C stacks to Python stacks
- exceptions.cpp takes Python stack frames and handles them appropriately

So for throwing a C++ exception, it starts out in cxx_unwind.cpp, which
then hands off the C frames to unwinding.cpp, which then hands off the
Python frames to exceptions.cpp.  When we get exceptions not via uncaught
C++ exceptions (ie explicitly handled C++ exceptions or CAPI exceptions),
those go directly into exceptions.cpp.  There are also non-exception cases
that we want to get the Python stack trace (ex sys._getframe), and those
are handled by unwinding.cpp
parent 10ac9f32
...@@ -131,8 +131,8 @@ static llvm::Value* getClosureElementGep(IREmitter& emitter, llvm::Value* closur ...@@ -131,8 +131,8 @@ static llvm::Value* getClosureElementGep(IREmitter& emitter, llvm::Value* closur
static llvm::Value* getBoxedLocalsGep(llvm::IRBuilder<true>& builder, llvm::Value* v) { static llvm::Value* getBoxedLocalsGep(llvm::IRBuilder<true>& builder, llvm::Value* v) {
static_assert(offsetof(FrameInfo, exc) == 0, ""); static_assert(offsetof(FrameInfo, exc) == 0, "");
static_assert(sizeof(ExcInfo) == 32, ""); static_assert(sizeof(ExcInfo) == 24, "");
static_assert(offsetof(FrameInfo, boxedLocals) == 32, ""); static_assert(offsetof(FrameInfo, boxedLocals) == 24, "");
return builder.CreateConstInBoundsGEP2_32(v, 0, 1); return builder.CreateConstInBoundsGEP2_32(v, 0, 1);
} }
...@@ -143,9 +143,9 @@ static llvm::Value* getExcinfoGep(llvm::IRBuilder<true>& builder, llvm::Value* v ...@@ -143,9 +143,9 @@ static llvm::Value* getExcinfoGep(llvm::IRBuilder<true>& builder, llvm::Value* v
static llvm::Value* getFrameObjGep(llvm::IRBuilder<true>& builder, llvm::Value* v) { static llvm::Value* getFrameObjGep(llvm::IRBuilder<true>& builder, llvm::Value* v) {
static_assert(offsetof(FrameInfo, exc) == 0, ""); static_assert(offsetof(FrameInfo, exc) == 0, "");
static_assert(sizeof(ExcInfo) == 32, ""); static_assert(sizeof(ExcInfo) == 24, "");
static_assert(sizeof(Box*) == 8, ""); static_assert(sizeof(Box*) == 8, "");
static_assert(offsetof(FrameInfo, frame_obj) == 40, ""); static_assert(offsetof(FrameInfo, frame_obj) == 32, "");
return builder.CreateConstInBoundsGEP2_32(v, 0, 2); return builder.CreateConstInBoundsGEP2_32(v, 0, 2);
// TODO: this could be made more resilient by doing something like // TODO: this could be made more resilient by doing something like
// gep->accumulateConstantOffset(g.tm->getDataLayout(), ap_offset) // gep->accumulateConstantOffset(g.tm->getDataLayout(), ap_offset)
......
...@@ -493,6 +493,11 @@ static const LineInfo lineInfoForFrame(PythonFrameIteratorImpl* frame_it) { ...@@ -493,6 +493,11 @@ static const LineInfo lineInfoForFrame(PythonFrameIteratorImpl* frame_it) {
return LineInfo(current_stmt->lineno, current_stmt->col_offset, source->fn, source->getName()); return LineInfo(current_stmt->lineno, current_stmt->col_offset, source->fn, source->getName());
} }
// A class that converts a C stack trace to a Python stack trace.
// It allows for different ways of driving the C stack trace; it just needs
// to have handleCFrame called once per frame.
// If you want to do a normal (non-destructive) stack walk, use unwindPythonStack
// which will use this internally.
class PythonStackExtractor { class PythonStackExtractor {
private: private:
bool skip_next_pythonlike_frame = false; bool skip_next_pythonlike_frame = false;
...@@ -522,74 +527,42 @@ public: ...@@ -522,74 +527,42 @@ public:
class PythonUnwindSession : public Box { class PythonUnwindSession : public Box {
ExcInfo exc_info; ExcInfo exc_info;
PythonStackExtractor pystack_extractor; PythonStackExtractor pystack_extractor;
bool is_active;
Timer t; Timer t;
public: public:
DEFAULT_CLASS_SIMPLE(unwind_session_cls); DEFAULT_CLASS_SIMPLE(unwind_session_cls);
PythonUnwindSession() : exc_info(NULL, NULL, NULL), is_active(false), t(/*min_usec=*/10000) {} PythonUnwindSession() : exc_info(NULL, NULL, NULL), t(/*min_usec=*/10000) {}
ExcInfo* getExcInfoStorage() { ExcInfo* getExcInfoStorage() { return &exc_info; }
RELEASE_ASSERT(is_active, "");
return &exc_info;
}
bool isActive() const { return is_active; }
void begin() { void begin() {
RELEASE_ASSERT(!is_active, "");
exc_info = ExcInfo(NULL, NULL, NULL); exc_info = ExcInfo(NULL, NULL, NULL);
is_active = true;
t.restart(); t.restart();
static StatCounter stat("unwind_sessions"); static StatCounter stat("unwind_sessions");
stat.log(); stat.log();
} }
void end() { void end() {
RELEASE_ASSERT(is_active, "");
is_active = false;
static StatCounter stat("us_unwind_session"); static StatCounter stat("us_unwind_session");
stat.log(t.end()); stat.log(t.end());
} }
void addTraceback(PythonFrameIteratorImpl& frame_iter) {
RELEASE_ASSERT(is_active, "");
if (exc_info.reraise) {
exc_info.reraise = false;
return;
}
// TODO: shouldn't fetch this multiple times?
frame_iter.getCurrentStatement()->cxx_exception_count++;
auto line_info = lineInfoForFrame(&frame_iter);
BoxedTraceback::here(line_info, &exc_info.traceback);
}
void logException() {
#if STAT_EXCEPTIONS
static StatCounter num_exceptions("num_exceptions");
num_exceptions.log();
std::string stat_name;
if (PyType_Check(exc_info.type))
stat_name = "num_exceptions_" + std::string(static_cast<BoxedClass*>(exc_info.type)->tp_name);
else
stat_name = "num_exceptions_" + std::string(exc_info.value->cls->tp_name);
Stats::log(Stats::getStatCounter(stat_name));
#if STAT_EXCEPTIONS_LOCATION
logByCurrentPythonLine(stat_name);
#endif
#endif
}
void handleCFrame(unw_cursor_t* cursor) { void handleCFrame(unw_cursor_t* cursor) {
unw_word_t ip = get_cursor_ip(cursor); unw_word_t ip = get_cursor_ip(cursor);
unw_word_t bp = get_cursor_bp(cursor); unw_word_t bp = get_cursor_bp(cursor);
PythonFrameIteratorImpl frame_iter; PythonFrameIteratorImpl frame_iter;
bool found_frame = pystack_extractor.handleCFrame(cursor, &frame_iter); bool found_frame = pystack_extractor.handleCFrame(cursor, &frame_iter);
if (found_frame) if (found_frame) {
addTraceback(frame_iter); if (exceptionAtLineCheck()) {
// TODO: shouldn't fetch this multiple times?
frame_iter.getCurrentStatement()->cxx_exception_count++;
auto line_info = lineInfoForFrame(&frame_iter);
exceptionAtLine(line_info, &exc_info.traceback);
}
}
} }
static void gcHandler(GCVisitor* v, Box* _o) { static void gcHandler(GCVisitor* v, Box* _o) {
...@@ -597,14 +570,6 @@ public: ...@@ -597,14 +570,6 @@ public:
PythonUnwindSession* o = static_cast<PythonUnwindSession*>(_o); PythonUnwindSession* o = static_cast<PythonUnwindSession*>(_o);
// this is our hack for eventually collecting
// exceptions/tracebacks after the exception has been caught.
// If a collection happens and a given thread's
// PythonUnwindSession isn't active, its exception info can be
// collected.
if (!o->is_active)
return;
v->visitIf(o->exc_info.type); v->visitIf(o->exc_info.type);
v->visitIf(o->exc_info.value); v->visitIf(o->exc_info.value);
v->visitIf(o->exc_info.traceback); v->visitIf(o->exc_info.traceback);
...@@ -612,17 +577,22 @@ public: ...@@ -612,17 +577,22 @@ public:
}; };
static __thread PythonUnwindSession* cur_unwind; static __thread PythonUnwindSession* cur_unwind;
PythonUnwindSession* beginPythonUnwindSession() { static PythonUnwindSession* getUnwindSession() {
if (!cur_unwind) { if (!cur_unwind) {
cur_unwind = new PythonUnwindSession(); cur_unwind = new PythonUnwindSession();
pyston::gc::registerPermanentRoot(cur_unwind); pyston::gc::registerPermanentRoot(cur_unwind);
} }
return cur_unwind;
}
PythonUnwindSession* beginPythonUnwindSession() {
getUnwindSession();
cur_unwind->begin(); cur_unwind->begin();
return cur_unwind; return cur_unwind;
} }
PythonUnwindSession* getActivePythonUnwindSession() { PythonUnwindSession* getActivePythonUnwindSession() {
RELEASE_ASSERT(cur_unwind && cur_unwind->isActive(), ""); ASSERT(cur_unwind, "");
return cur_unwind; return cur_unwind;
} }
...@@ -630,58 +600,13 @@ void endPythonUnwindSession(PythonUnwindSession* unwind) { ...@@ -630,58 +600,13 @@ void endPythonUnwindSession(PythonUnwindSession* unwind) {
RELEASE_ASSERT(unwind && unwind == cur_unwind, ""); RELEASE_ASSERT(unwind && unwind == cur_unwind, "");
unwind->end(); unwind->end();
} }
void* getPythonUnwindSessionExceptionStorage(PythonUnwindSession* unwind) { void* getPythonUnwindSessionExceptionStorage(PythonUnwindSession* unwind) {
RELEASE_ASSERT(unwind && unwind == cur_unwind, ""); RELEASE_ASSERT(unwind && unwind == cur_unwind, "");
PythonUnwindSession* state = static_cast<PythonUnwindSession*>(unwind); PythonUnwindSession* state = static_cast<PythonUnwindSession*>(unwind);
return state->getExcInfoStorage(); return state->getExcInfoStorage();
} }
void throwingException(PythonUnwindSession* unwind) {
RELEASE_ASSERT(unwind && unwind == cur_unwind, "");
unwind->logException();
}
extern "C" void capiExcCaughtInJit(AST_stmt* stmt, void* _source_info) {
SourceInfo* source = static_cast<SourceInfo*>(_source_info);
// TODO: handle reraise (currently on the ExcInfo object)
PyThreadState* tstate = PyThreadState_GET();
BoxedTraceback::here(LineInfo(stmt->lineno, stmt->col_offset, source->fn, source->getName()),
&tstate->curexc_traceback);
}
extern "C" void reraiseJitCapiExc() {
ensureCAPIExceptionSet();
// TODO: we are normalizing to many times?
ExcInfo e = excInfoForRaise(cur_thread_state.curexc_type, cur_thread_state.curexc_value,
cur_thread_state.curexc_traceback);
PyErr_Clear();
e.reraise = true;
throw e;
}
void exceptionCaughtInInterpreter(LineInfo line_info, ExcInfo* exc_info) {
static StatCounter frames_unwound("num_frames_unwound_python");
frames_unwound.log();
// basically the same as PythonUnwindSession::addTraceback, but needs to
// be callable after an PythonUnwindSession has ended. The interpreter
// will call this from catch blocks if it needs to ensure that a
// line is added. Right now this only happens in
// ASTInterpreter::visit_invoke.
// It's basically the same except for one thing: we don't have to
// worry about the 'skip' (osr) state that PythonUnwindSession handles
// here, because the only way we could have gotten into the ast
// interpreter is if the exception wasn't caught, and if there was
// the osr frame for the one the interpreter is running, it would
// have already caught it.
if (exc_info->reraise) {
exc_info->reraise = false;
return;
}
BoxedTraceback::here(line_info, &exc_info->traceback);
}
void unwindingThroughFrame(PythonUnwindSession* unwind_session, unw_cursor_t* cursor) { void unwindingThroughFrame(PythonUnwindSession* unwind_session, unw_cursor_t* cursor) {
unwind_session->handleCFrame(cursor); unwind_session->handleCFrame(cursor);
} }
......
...@@ -44,13 +44,18 @@ Box* getTraceback(); ...@@ -44,13 +44,18 @@ Box* getTraceback();
class PythonUnwindSession; class PythonUnwindSession;
PythonUnwindSession* beginPythonUnwindSession(); PythonUnwindSession* beginPythonUnwindSession();
PythonUnwindSession* getActivePythonUnwindSession(); PythonUnwindSession* getActivePythonUnwindSession();
void throwingException(PythonUnwindSession* unwind_session);
void endPythonUnwindSession(PythonUnwindSession* unwind_session); void endPythonUnwindSession(PythonUnwindSession* unwind_session);
void* getPythonUnwindSessionExceptionStorage(PythonUnwindSession* unwind_session); void* getPythonUnwindSessionExceptionStorage(PythonUnwindSession* unwind_session);
void unwindingThroughFrame(PythonUnwindSession* unwind_session, unw_cursor_t* cursor); void unwindingThroughFrame(PythonUnwindSession* unwind_session, unw_cursor_t* cursor);
// TODO move these to exceptions.h
void logException(ExcInfo* exc_info);
void startReraise();
bool exceptionAtLineCheck();
void exceptionAtLine(LineInfo line_info, Box** traceback);
void exceptionCaughtInInterpreter(LineInfo line_info, ExcInfo* exc_info); void exceptionCaughtInInterpreter(LineInfo line_info, ExcInfo* exc_info);
CLFunction* getTopPythonFunction(); CLFunction* getTopPythonFunction();
// debugging/stat helper, returns python filename:linenumber, or "unknown:-1" if it fails // debugging/stat helper, returns python filename:linenumber, or "unknown:-1" if it fails
......
...@@ -777,13 +777,8 @@ public: ...@@ -777,13 +777,8 @@ public:
struct ExcInfo { struct ExcInfo {
Box* type, *value, *traceback; Box* type, *value, *traceback;
bool reraise;
#ifndef NDEBUG constexpr ExcInfo(Box* type, Box* value, Box* traceback) : type(type), value(value), traceback(traceback) {}
ExcInfo(Box* type, Box* value, Box* traceback);
#else
ExcInfo(Box* type, Box* value, Box* traceback) : type(type), value(value), traceback(traceback), reraise(false) {}
#endif
bool matches(BoxedClass* cls) const; bool matches(BoxedClass* cls) const;
void printExcAndTraceback() const; void printExcAndTraceback() const;
}; };
......
...@@ -688,7 +688,7 @@ extern "C" void __cxa_throw(void* exc_obj, std::type_info* tinfo, void (*dtor)(v ...@@ -688,7 +688,7 @@ extern "C" void __cxa_throw(void* exc_obj, std::type_info* tinfo, void (*dtor)(v
pyston::StatTimer::overrideCounter(unwinding_stattimer); pyston::StatTimer::overrideCounter(unwinding_stattimer);
#endif #endif
// let unwinding.cpp know we've started unwinding // let unwinding.cpp know we've started unwinding
pyston::throwingException(pyston::getActivePythonUnwindSession()); pyston::logException(exc_data);
pyston::unwind(exc_data); pyston::unwind(exc_data);
} }
......
...@@ -76,17 +76,11 @@ extern "C" void raise0() { ...@@ -76,17 +76,11 @@ extern "C" void raise0() {
if (exc_info->type == None) if (exc_info->type == None)
raiseExcHelper(TypeError, "exceptions must be old-style classes or derived from BaseException, not NoneType"); raiseExcHelper(TypeError, "exceptions must be old-style classes or derived from BaseException, not NoneType");
exc_info->reraise = true; startReraise();
assert(!PyErr_Occurred()); assert(!PyErr_Occurred());
throw * exc_info; throw * exc_info;
} }
#ifndef NDEBUG
ExcInfo::ExcInfo(Box* type, Box* value, Box* traceback)
: type(type), value(value), traceback(traceback), reraise(false) {
}
#endif
void ExcInfo::printExcAndTraceback() const { void ExcInfo::printExcAndTraceback() const {
PyErr_Display(type, value, traceback); PyErr_Display(type, value, traceback);
} }
...@@ -158,7 +152,9 @@ extern "C" void raise3(Box* arg0, Box* arg1, Box* arg2) { ...@@ -158,7 +152,9 @@ extern "C" void raise3(Box* arg0, Box* arg1, Box* arg2) {
bool reraise = arg2 != NULL && arg2 != None; bool reraise = arg2 != NULL && arg2 != None;
auto exc_info = excInfoForRaise(arg0, arg1, arg2); auto exc_info = excInfoForRaise(arg0, arg1, arg2);
exc_info.reraise = reraise; if (reraise)
startReraise();
assert(!PyErr_Occurred()); assert(!PyErr_Occurred());
throw exc_info; throw exc_info;
} }
...@@ -169,12 +165,11 @@ extern "C" void raise3_capi(Box* arg0, Box* arg1, Box* arg2) noexcept { ...@@ -169,12 +165,11 @@ extern "C" void raise3_capi(Box* arg0, Box* arg1, Box* arg2) noexcept {
ExcInfo exc_info(NULL, NULL, NULL); ExcInfo exc_info(NULL, NULL, NULL);
try { try {
exc_info = excInfoForRaise(arg0, arg1, arg2); exc_info = excInfoForRaise(arg0, arg1, arg2);
exc_info.reraise = reraise;
} catch (ExcInfo e) { } catch (ExcInfo e) {
exc_info = e; exc_info = e;
} }
assert(!exc_info.reraise); // would get thrown away assert(!reraise); // would get thrown away
PyErr_Restore(exc_info.type, exc_info.value, exc_info.traceback); PyErr_Restore(exc_info.type, exc_info.value, exc_info.traceback);
} }
...@@ -206,4 +201,71 @@ void raiseExcHelper(BoxedClass* cls, const char* msg, ...) { ...@@ -206,4 +201,71 @@ void raiseExcHelper(BoxedClass* cls, const char* msg, ...) {
raiseExc(exc_obj); raiseExc(exc_obj);
} }
} }
void logException(ExcInfo* exc_info) {
#if STAT_EXCEPTIONS
static StatCounter num_exceptions("num_exceptions");
num_exceptions.log();
std::string stat_name;
if (PyType_Check(exc_info->type))
stat_name = "num_exceptions_" + std::string(static_cast<BoxedClass*>(exc_info->type)->tp_name);
else
stat_name = "num_exceptions_" + std::string(exc_info->value->cls->tp_name);
Stats::log(Stats::getStatCounter(stat_name));
#if STAT_EXCEPTIONS_LOCATION
logByCurrentPythonLine(stat_name);
#endif
#endif
}
extern "C" void capiExcCaughtInJit(AST_stmt* stmt, void* _source_info) {
SourceInfo* source = static_cast<SourceInfo*>(_source_info);
PyThreadState* tstate = PyThreadState_GET();
exceptionAtLine(LineInfo(stmt->lineno, stmt->col_offset, source->fn, source->getName()), &tstate->curexc_traceback);
}
extern "C" void reraiseJitCapiExc() {
ensureCAPIExceptionSet();
// TODO: we are normalizing to many times?
ExcInfo e = excInfoForRaise(cur_thread_state.curexc_type, cur_thread_state.curexc_value,
cur_thread_state.curexc_traceback);
PyErr_Clear();
startReraise();
throw e;
}
void exceptionCaughtInInterpreter(LineInfo line_info, ExcInfo* exc_info) {
static StatCounter frames_unwound("num_frames_unwound_python");
frames_unwound.log();
exceptionAtLine(line_info, &exc_info->traceback);
}
struct ExcState {
bool is_reraise;
constexpr ExcState() : is_reraise(false) {}
} static __thread exc_state;
bool exceptionAtLineCheck() {
if (exc_state.is_reraise) {
exc_state.is_reraise = false;
return false;
}
return true;
}
void exceptionAtLine(LineInfo line_info, Box** traceback) {
if (exceptionAtLineCheck())
BoxedTraceback::here(line_info, traceback);
}
void startReraise() {
assert(!exc_state.is_reraise);
exc_state.is_reraise = true;
}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment