Browse Source

Create stack traces for jitted functions

jonathanvdc 8 years ago
parent
commit
b072a71f26

+ 10 - 5
kernel/modelverse_jit/runtime.py

@@ -43,14 +43,19 @@ BASELINE_JIT_ORIGIN_NAME = "baseline-jit"
 FAST_JIT_ORIGIN_NAME = "fast-jit"
 FAST_JIT_ORIGIN_NAME = "fast-jit"
 """The origin name for functions that were produced by the fast JIT."""
 """The origin name for functions that were produced by the fast JIT."""
 
 
-def format_trace_message(debug_info, function_name, origin='unknown'):
-    """Creates a formatted trace message."""
-    if debug_info is None:
-        debug_info = 'unknown location '
+def format_stack_frame(function_name, debug_info, origin='unknown'):
+    """Formats a stack frame, which consists of a function name, debug
+       information and an origin."""
     if function_name is None:
     if function_name is None:
         function_name = 'unknown function'
         function_name = 'unknown function'
+    if debug_info is None:
+        debug_info = '[unknown location] '
 
 
-    return 'TRACE: %s(%s, %s)' % (debug_info, function_name, origin)
+    return '%sin %s (%s)' % (debug_info, function_name, origin)
+
+def format_trace_message(debug_info, function_name, origin='unknown'):
+    """Creates a formatted trace message."""
+    return 'TRACE: %s' % format_stack_frame(function_name, debug_info, origin)
 
 
 def call_function(function_id, named_arguments, **kwargs):
 def call_function(function_id, named_arguments, **kwargs):
     """Runs the function with the given id, passing it the specified argument dictionary."""
     """Runs the function with the given id, passing it the specified argument dictionary."""

+ 4 - 1
kernel/modelverse_jit/source_map.py

@@ -12,7 +12,10 @@ class SourceMap(object):
     def get_debug_info(self, line_number):
     def get_debug_info(self, line_number):
         """Gets the debug information for the given line number, or None if no debug info was
         """Gets the debug information for the given line number, or None if no debug info was
            found."""
            found."""
-        return self.lines[line_number]
+        if line_number in self.lines:
+            return self.lines[line_number]
+        else:
+            return None
 
 
     def __str__(self):
     def __str__(self):
         return '\n'.join(
         return '\n'.join(

+ 63 - 8
kernel/modelverse_kernel/request_handler.py

@@ -1,4 +1,6 @@
+import sys
 import modelverse_kernel.primitives as primitive_functions
 import modelverse_kernel.primitives as primitive_functions
+import modelverse_jit.runtime as jit_runtime
 
 
 class KnownRequestHandled(Exception):
 class KnownRequestHandled(Exception):
     """An exception that signifies that a known request was handled."""
     """An exception that signifies that a known request was handled."""
@@ -10,7 +12,7 @@ class GeneratorStackEntry(object):
         self.generator = generator
         self.generator = generator
         self.function_name = None
         self.function_name = None
         self.source_map = None
         self.source_map = None
-        self.function_source = None
+        self.function_origin = None
         self.pending_requests = None
         self.pending_requests = None
         self.finished_requests = True
         self.finished_requests = True
         self.replies = []
         self.replies = []
@@ -38,6 +40,22 @@ class GeneratorStackEntry(object):
         self.replies = []
         self.replies = []
         self.has_reply = False
         self.has_reply = False
 
 
+def format_stack_trace(stack_trace):
+    """Formats a list of (function name, debug info, origin) triples."""
+    return '\n'.join([jit_runtime.format_stack_frame(*triple) for triple in stack_trace])
+
+class UnhandledRequestHandlerException(Exception):
+    """The type of exception that is thrown when the request handler encounters an
+       unhandled exception."""
+    def __init__(self, inner_exception, stack_trace):
+        Exception.__init__(
+            self,
+            """The request handler encountered an unknown exception.\n
+               Inner exception: %s\n
+               Stack trace:\n%s\n""" % (inner_exception, format_stack_trace(stack_trace)))
+        self.inner_exception = inner_exception
+        self.stack_trace = stack_trace
+
 class RequestHandler(object):
 class RequestHandler(object):
     """A type of object that intercepts logic-related Modelverse requests, and
     """A type of object that intercepts logic-related Modelverse requests, and
        forwards Modelverse state requests."""
        forwards Modelverse state requests."""
@@ -109,8 +127,9 @@ class RequestHandler(object):
                     return None
                     return None
             except Exception as ex:
             except Exception as ex:
                 # Maybe get an exception handler to do this.
                 # Maybe get an exception handler to do this.
-                if not self.handle_exception(ex):
-                    raise
+                stack_trace = self.handle_exception(ex)
+                if stack_trace is not None:
+                    raise UnhandledRequestHandlerException(ex, stack_trace)
 
 
     def set_finished_requests_flag(self):
     def set_finished_requests_flag(self):
         """Sets the finished_requests flag in the top-of-stack tuple."""
         """Sets the finished_requests flag in the top-of-stack tuple."""
@@ -181,14 +200,50 @@ class RequestHandler(object):
                     ('TAIL_CALL_ARGS', [applicable_handler, (exception,)])]
                     ('TAIL_CALL_ARGS', [applicable_handler, (exception,)])]
                 stack_entry.finished_requests = False
                 stack_entry.finished_requests = False
                 self.generator_stack.append(stack_entry)
                 self.generator_stack.append(stack_entry)
-                return True
+                return None
 
 
         # We couldn't find an applicable exception handler, even after exhausting the
         # We couldn't find an applicable exception handler, even after exhausting the
         # entire exception handler stack. All is lost.
         # entire exception handler stack. All is lost.
-        # Also, clean up after ourselves.
-        self.generator_stack = []
+        # Also, clean up after ourselves by unwinding the stack.
+        return self.unwind_stack()
+
+    def unwind_stack(self):
+        """Unwinds the entirety of the stack. All generators and exception handlers are
+           discarded. A list of (function name, debug information, source) statements is
+           returned."""
+        class UnwindStackException(Exception):
+            """A hard-to-catch exception that is used to make generators crash.
+               The exceptions they produce can then be analyzed for line numbers."""
+            pass
+
+        # First throw away all exception handlers. We won't be needing them any more.
         self.exception_handlers = []
         self.exception_handlers = []
-        return False
+
+        # Then pop every generator from the stack and make it crash.
+        stack_trace = []
+        while len(self.generator_stack) > 0:
+            top_entry = self.generator_stack.pop()
+            if top_entry.function_origin is None:
+                # Skip this function.
+                continue
+
+            try:
+                # Crash the generator.
+                top_entry.generator.throw(UnwindStackException())
+            except UnwindStackException:
+                # Find out where the exception was thrown.
+                _, _, exc_traceback = sys.exc_info()
+                line_number = exc_traceback.tb_lineno
+                source_map = top_entry.source_map
+                if source_map is not None:
+                    debug_info = source_map.get_debug_info(line_number)
+                else:
+                    debug_info = None
+
+                function_name = top_entry.function_name
+                stack_trace.append((function_name, debug_info, top_entry.function_origin))
+
+        return stack_trace[::-1]
 
 
     def pop_requests(self):
     def pop_requests(self):
         """Tries to pop a batch of Modelverse _state_ requests from the
         """Tries to pop a batch of Modelverse _state_ requests from the
@@ -353,6 +408,6 @@ class RequestHandler(object):
         # encountered.
         # encountered.
         # Format: ("DEBUG_INFO", [function_name, source_map])
         # Format: ("DEBUG_INFO", [function_name, source_map])
         top_entry = self.generator_stack[-1]
         top_entry = self.generator_stack[-1]
-        top_entry.function_name, top_entry.source_map, top_entry.function_source = request_args
+        top_entry.function_name, top_entry.source_map, top_entry.function_origin = request_args
         top_entry.append_reply(None)
         top_entry.append_reply(None)