فهرست منبع

Implement the 'RUN' instruction in the interpreter

jonathanvdc 8 سال پیش
والد
کامیت
f91f438320
3فایلهای تغییر یافته به همراه178 افزوده شده و 82 حذف شده
  1. 11 22
      kernel/modelverse_jit/runtime.py
  2. 149 51
      kernel/modelverse_kernel/main.py
  3. 18 9
      kernel/modelverse_kernel/primitives.py

+ 11 - 22
kernel/modelverse_jit/runtime.py

@@ -20,20 +20,13 @@ def call_function(function_id, named_arguments, **kwargs):
     # frame.
     # frame.
     try:
     try:
         # Try to compile.
         # Try to compile.
-        jit_gen = kernel.jit_compile(user_root, body_id)
-        inp = None
-        while 1:
-            inp = yield jit_gen.send(inp)
-    except primitive_functions.PrimitiveFinished as ex:
-        compiled_func = ex.result
+        compiled_func, = yield [("RUN", kernel.jit_compile(user_root, body_id))]
         # Add the keyword arguments to the argument dictionary.
         # Add the keyword arguments to the argument dictionary.
         named_arguments.update(kwargs)
         named_arguments.update(kwargs)
         # Run the function.
         # Run the function.
-        gen = compiled_func(**named_arguments)
-        inp = None
-        while 1:
-            inp = yield gen.send(inp)
-        # Let primitive_functions.PrimitiveFinished bubble up.
+        result, = yield [("RUN", compiled_func, named_arguments)]
+        # Return.
+        raise primitive_functions.PrimitiveFinished(result)
     except JitCompilationFailedException:
     except JitCompilationFailedException:
         # That's quite alright. Just build a stack frame and hand the function to
         # That's quite alright. Just build a stack frame and hand the function to
         # the interpreter.
         # the interpreter.
@@ -62,7 +55,7 @@ def call_function(function_id, named_arguments, **kwargs):
                            ("CD", [new_frame, "prev", user_frame]),
                            ("CD", [new_frame, "prev", user_frame]),
                            ("CD", [
                            ("CD", [
                                new_frame,
                                new_frame,
-                               primitive_functions.PRIMITIVE_RETURN_KEY,
+                               primitive_functions.EXCEPTION_RETURN_KEY,
                                intrinsic_return]),
                                intrinsic_return]),
                            ("DE", [frame_link])
                            ("DE", [frame_link])
                           ]
                           ]
@@ -85,13 +78,9 @@ def call_function(function_id, named_arguments, **kwargs):
         yield [("CE", [symbol_edge, param_var])]
         yield [("CE", [symbol_edge, param_var])]
 
 
     username = kwargs['username']
     username = kwargs['username']
-    while 1:
-        try:
-            gen = kernel.execute_rule(username)
-            inp = None
-            while 1:
-                inp = yield gen.send(inp)
-        except StopIteration:
-            # An instruction has been completed. Forward it.
-            yield None
-        # Let primitive_functions.PrimitiveFinished bubble up.
+    try:
+        while 1:
+            result, = yield [("RUN", kernel.execute_rule(username))]
+            yield result
+    except primitive_functions.InterpretedFunctionFinished as ex:
+        raise primitive_functions.PrimitiveFinished(ex.result)

+ 149 - 51
kernel/modelverse_kernel/main.py

@@ -11,11 +11,70 @@ if sys.version > '3': # pragma: no cover
 else:
 else:
     string_types = (str, unicode)
     string_types = (str, unicode)
 
 
+class RunRequest(Exception):
+    pass
+
+def set_finished_requests_flag(opStack):
+    """Sets the finished_requests flag in the top-of-stack tuple."""
+    current_generator, requests, finished_requests, replies, has_reply = opStack[-1]
+    opStack[-1] = (current_generator, requests, True, replies, has_reply)
+
+def push_generator(gen, opStack):
+    """Pushes a new generator onto the given stack."""
+    opStack.append((gen, None, True, [], False))
+
+def pop_requests(requests, opStack):
+    """Tries to pop a batch of Modelverse _state_ requests from the
+       given list of requests. RUN-requests are executed immediately.
+
+       A list of requests and a Boolean are returned. The latter is True
+       if there are no more requests to process, and false otherwise."""
+    if requests is None or len(requests) == 0:
+        # Couldn't find a request for the state to handle.
+        set_finished_requests_flag(opStack)
+        return requests
+
+    first_request = requests.pop(0)
+    if first_request[0] == "RUN":
+        # The kernel handles RUN-requests.
+        if len(requests) == 0:
+            set_finished_requests_flag(opStack)
+
+        request_length = len(first_request)
+        if request_length == 2:
+            # Format: ("RUN", gen)
+            _, gen = first_request
+            push_generator(gen, opStack)
+        else:
+            # Format: ("RUN", func, kwargs)
+            # This format is useful because it also works for functions that
+            # throw an exception but never yield.
+            _, func, kwargs = first_request
+            push_generator(func(**kwargs), opStack)
+
+        raise RunRequest()
+    else:
+        # The state handles all other requests.
+        if len(requests) == 0:
+            set_finished_requests_flag(opStack)
+
+        return [first_request]
+
 class ModelverseKernel(object):
 class ModelverseKernel(object):
     def __init__(self, root):
     def __init__(self, root):
         self.root = root
         self.root = root
         self.returnvalue = None
         self.returnvalue = None
         self.success = True
         self.success = True
+        #
+        # generators is a dictionary of usernames to dictionaries of operations
+        # to stacks of (generator, pending requests, request replies, has-reply) tuples.
+        # In generics notation:
+        # Dictionary<
+        #     Username,
+        #     Dictionary<
+        #         Operation,
+        #         Stack<(Generator, Queue<List<Request>>, List<Replies>, Boolean)>>
+        #
         self.generators = {}
         self.generators = {}
         self.allow_compiled = True
         self.allow_compiled = True
         #self.allow_compiled = False
         #self.allow_compiled = False
@@ -46,26 +105,80 @@ class ModelverseKernel(object):
         self.debug_info = defaultdict(list)
         self.debug_info = defaultdict(list)
 
 
     def execute_yields(self, username, operation, params, reply):
     def execute_yields(self, username, operation, params, reply):
-        try:
-            self.success = True
-            self.username = username
-            if username not in self.generators:
-                self.generators[username] = {}
-            if operation not in self.generators[username]:
-                # Create the generator for the function to execute
-                self.generators[username][operation] = getattr(self, operation)(username, *params)
-
-            if reply is not None:
-                return self.generators[username][operation].send(reply)
-            else:
-                return self.generators[username][operation].next()
-        except StopIteration:
-            # Done, so remove the generator
-            del self.generators[username][operation]
-            return None
-        except:
-            print("Unknown error @ %s" % self.debug_info[username])
-            raise
+        self.success = True
+        self.username = username
+        if username not in self.generators:
+            self.generators[username] = {}
+        if operation not in self.generators[username]:
+            # Create the generator for the function to execute
+            self.generators[username][operation] = []
+        opStack = self.generators[username][operation]
+        if len(opStack) == 0:
+            push_generator(getattr(self, operation)(username, *params), opStack)
+
+        current_generator, requests, finished_requests, replies, has_reply = opStack[-1]
+        # Append the server's replies to the list of replies.
+        if reply is not None:
+            replies.extend(reply)
+            has_reply = True
+            opStack[-1] = (current_generator, requests, finished_requests, replies, has_reply)
+        while 1:
+            try:
+                if not finished_requests:
+                    try:
+                        # Try to pop a request for the modelverse state.
+                        result = pop_requests(requests, opStack)
+                        return result
+                    except RunRequest:
+                        # Carry on.
+                        pass
+
+                # The call to pop_requests may have appended a tuple to the stack,
+                # so we need to reload the top-of-stack tuple.
+                current_generator, requests, finished_requests, replies, has_reply = opStack[-1]
+
+                # Send the replies to the generator, and ask for new requests.
+                requests = current_generator.send(replies if has_reply else None)
+                finished_requests = False
+
+                # Update the entry in the stack.
+                opStack[-1] = (current_generator, requests, finished_requests, [], False)
+            except StopIteration:
+                # Done, so remove the generator
+                opStack.pop()
+                if len(opStack) == 0:
+                    # Looks like we're done here.
+                    # Maybe remove the stack of generators as well?
+                    # del self.generators[username][operation]
+                    return None
+
+                # Update the current generator, requests, and replies.
+                current_generator, requests, finished_requests, replies, has_reply = opStack[-1]
+                # Append 'None' to the list of replies.
+                replies.append(None)
+                if not has_reply:
+                    has_reply = True
+                    # Update the stack.
+                    opStack[-1] = current_generator, requests, finished_requests, replies, has_reply
+            except primitive_functions.PrimitiveFinished as ex:
+                # Done, so remove the generator.
+                opStack.pop()
+                if len(opStack) == 0:
+                    # Looks like we're done here.
+                    # Maybe remove the stack of generators as well?
+                    # del self.generators[username][operation]
+                    return None
+
+                # Update the current generator, requests, and replies.
+                current_generator, requests, finished_requests, replies, has_reply = opStack[-1]
+                # Append the generator's result to the list of replies.
+                replies.append(ex.result)
+                if not has_reply:
+                    has_reply = True
+                    # Update the stack.
+                    opStack[-1] = current_generator, requests, finished_requests, replies, has_reply
+            except:
+                raise
 
 
     def execute_rule(self, username):
     def execute_rule(self, username):
         user_root, =    yield [("RD", [self.root, username])]
         user_root, =    yield [("RD", [self.root, username])]
@@ -176,34 +289,19 @@ class ModelverseKernel(object):
         parameters["mvk"] = self
         parameters["mvk"] = self
 
 
         # Have the JIT compile the function.
         # Have the JIT compile the function.
-        try:
-            jit_gen = self.jit_compile(user_root, inst)
-            inp = None
-            while 1:
-                inp = yield jit_gen.send(inp)
-        except primitive_functions.PrimitiveFinished as e:
-            compiled_func = e.result
-
+        compiled_func, = yield [("RUN", self.jit_compile(user_root, inst))]
         # Run the compiled function.
         # Run the compiled function.
-        try:
-            prim = compiled_func(**parameters)
-            inp = None
-            while 1:
-                inp = yield prim.send(inp)
-        except StopIteration:
-            # Execution has ended without return value, so we have no idea what to do
-            raise Exception("%s: primitive finished without returning a value!" % (self.debug_info[username]))
-        except primitive_functions.PrimitiveFinished as e:
-            # Execution has ended with a returnvalue, so read it out from the exception being thrown
-            result = e.result
-
-            #if result is None:
-            #    raise Exception("Primitive raised exception: value of None for operation %s with parameters %s" % (compiled_func, str(parameters)))
+        results = yield [("RUN", compiled_func, parameters)]
+        if results is None:
+            raise Exception(
+                "%s: primitive finished without returning a value!" % (self.debug_info[username]))
+        else:
+            result, = results
 
 
         # Clean up the current stack, as if a return happened
         # Clean up the current stack, as if a return happened
         old_frame, exception_return = yield [
         old_frame, exception_return = yield [
             ("RD", [user_frame, "prev"]),
             ("RD", [user_frame, "prev"]),
-            ("RD", [user_frame, primitive_functions.PRIMITIVE_RETURN_KEY])]
+            ("RD", [user_frame, primitive_functions.EXCEPTION_RETURN_KEY])]
 
 
         if self.debug_info[self.username]:
         if self.debug_info[self.username]:
             self.debug_info[self.username].pop()
             self.debug_info[self.username].pop()
@@ -211,10 +309,10 @@ class ModelverseKernel(object):
         if exception_return is not None:
         if exception_return is not None:
             # The caller has requested that we throw an exception instead of injecting
             # The caller has requested that we throw an exception instead of injecting
             # the return value into the caller's frame. Read the comment at
             # the return value into the caller's frame. Read the comment at
-            # primitive_functions.PRIMITIVE_RETURN_KEY for the rationale behind this design.
+            # primitive_functions.EXCEPTION_RETURN_KEY for the rationale behind this design.
             yield [("CD", [user_root, "frame", old_frame]),
             yield [("CD", [user_root, "frame", old_frame]),
                    ("DN", [user_frame])]
                    ("DN", [user_frame])]
-            raise primitive_functions.PrimitiveFinished(result)
+            raise primitive_functions.InterpretedFunctionFinished(result)
         else:
         else:
             lnk, =          yield [("RDE", [old_frame, "returnvalue"])]
             lnk, =          yield [("RDE", [old_frame, "returnvalue"])]
             _, _, _, _ =    yield [("CD", [old_frame, "returnvalue", result]),
             _, _, _, _ =    yield [("CD", [old_frame, "returnvalue", result]),
@@ -614,10 +712,10 @@ class ModelverseKernel(object):
 
 
         if value is None:
         if value is None:
             prev_frame, =   yield [("RD", [user_frame, "prev"])]
             prev_frame, =   yield [("RD", [user_frame, "prev"])]
-            # If the callee's frame is marked with the '__primitive_return' key, then
+            # If the callee's frame is marked with the '__exception_return' key, then
             # we need to throw an exception instead of just finishing here. This design
             # we need to throw an exception instead of just finishing here. This design
             # gives us O(1) state reads per jit-interpreter transition.
             # gives us O(1) state reads per jit-interpreter transition.
-            exception_return, = yield [("RD", [user_frame, primitive_functions.PRIMITIVE_RETURN_KEY])]
+            exception_return, = yield [("RD", [user_frame, primitive_functions.EXCEPTION_RETURN_KEY])]
             if prev_frame is None:
             if prev_frame is None:
                 _, =            yield [("DN", [user_root])]
                 _, =            yield [("DN", [user_root])]
                 del self.debug_info[self.username]
                 del self.debug_info[self.username]
@@ -629,7 +727,7 @@ class ModelverseKernel(object):
                                       ]
                                       ]
 
 
             if exception_return is not None:
             if exception_return is not None:
-                raise primitive_functions.PrimitiveFinished(None)
+                raise primitive_functions.InterpretedFunctionFinished(None)
         else:
         else:
             evalstack, evalstack_link, ip_link, new_evalstack, evalstack_phase = \
             evalstack, evalstack_link, ip_link, new_evalstack, evalstack_phase = \
                             yield [("RD", [user_frame, "evalstack"]),
                             yield [("RD", [user_frame, "evalstack"]),
@@ -655,17 +753,17 @@ class ModelverseKernel(object):
         user_frame, = yield [("RD", [user_root, "frame"])]
         user_frame, = yield [("RD", [user_root, "frame"])]
         prev_frame, exception_return, returnvalue = yield [
         prev_frame, exception_return, returnvalue = yield [
             ("RD", [user_frame, "prev"]),
             ("RD", [user_frame, "prev"]),
-            ("RD", [user_frame, primitive_functions.PRIMITIVE_RETURN_KEY]),
+            ("RD", [user_frame, primitive_functions.EXCEPTION_RETURN_KEY]),
             ("RD", [user_frame, "returnvalue"])]
             ("RD", [user_frame, "returnvalue"])]
 
 
-        # If the callee's frame is marked with the '__primitive_return' key, then
+        # If the callee's frame is marked with the '__exception_return' key, then
         # we need to throw an exception instead of just finishing here. This design
         # we need to throw an exception instead of just finishing here. This design
         # gives us O(1) state reads per jit-interpreter transition.
         # gives us O(1) state reads per jit-interpreter transition.
         if exception_return is not None:
         if exception_return is not None:
             yield [
             yield [
                 ("CD", [user_root, "frame", prev_frame]),
                 ("CD", [user_root, "frame", prev_frame]),
                 ("DN", [user_frame])]
                 ("DN", [user_frame])]
-            raise primitive_functions.PrimitiveFinished(returnvalue)
+            raise primitive_functions.InterpretedFunctionFinished(returnvalue)
         else:
         else:
             old_returnvalue_link, = yield [("RDE", [prev_frame, "returnvalue"])]
             old_returnvalue_link, = yield [("RDE", [prev_frame, "returnvalue"])]
             yield [
             yield [

+ 18 - 9
kernel/modelverse_kernel/primitives.py

@@ -1,25 +1,34 @@
-# Exception to indicate the result value of the primitive, as a return cannot be used
+
 class PrimitiveFinished(Exception):
 class PrimitiveFinished(Exception):
+    """Exception to indicate the result value of a primitive, as a return cannot be used."""
+    def __init__(self, value):
+        Exception.__init__(self)
+        self.result = value
+
+class InterpretedFunctionFinished(Exception):
+    """Exception to indicate the result value of an interpreted function, as a return
+       cannot be used."""
     def __init__(self, value):
     def __init__(self, value):
+        Exception.__init__(self)
         self.result = value
         self.result = value
 
 
-# Functions annotated with __primitive_return use the JIT's calling convention instead of
+# Functions annotated with __exception_return use the JIT's calling convention instead of
 # the kernel's: returns are handled by throwing a PrimitiveFinished exception; the caller's
 # the kernel's: returns are handled by throwing a PrimitiveFinished exception; the caller's
 # returnvalue is not modified.
 # returnvalue is not modified.
 #
 #
-# ### Rationale for __primitive_return
+# ### Rationale for __exception_return
 #
 #
-# __primitive_return is a useful mechanism because it allows us to have an __interpret_function
+# __exception_return is a useful mechanism because it allows us to have an __call_function
 # implementation that has O(1) state read overhead. A previous implementation of
 # implementation that has O(1) state read overhead. A previous implementation of
-# __interpret_function checked if the caller's frame had been popped whenever
+# __call_function checked if the caller's frame had been popped whenever
 # ModelverseKernel.execute_yield threw a StopIteration exception. However, that incurs O(n) overhead
 # ModelverseKernel.execute_yield threw a StopIteration exception. However, that incurs O(n) overhead
 # _per call,_ where n is the number of StopIteration exceptions that are thrown during the call.
 # _per call,_ where n is the number of StopIteration exceptions that are thrown during the call.
-# O(n) is pretty bad, but this actually becomes O(n * m) when m calls to __interpret_function are
+# O(n) is pretty bad, but this actually becomes O(n * m) when m calls to __call_function are
 # nested. And that's just not acceptable.
 # nested. And that's just not acceptable.
-# __primitive_return requires kernel support, but I think the complexity gains are well worth it;
+# __exception_return requires kernel support, but I think the complexity gains are well worth it;
 # I reckon JIT-to-interpreter switches aren't going to get a whole lot cheaper than this.
 # I reckon JIT-to-interpreter switches aren't going to get a whole lot cheaper than this.
-PRIMITIVE_RETURN_KEY = "__primitive_return"
-"""A dictionary key for functions which request that the kernel throw a PrimitiveFinished
+EXCEPTION_RETURN_KEY = "__exception_return"
+"""A dictionary key for functions which request that the kernel throw a InterpretedFunctionFinished
    exception with the return value instead of injecting the return value in the caller's frame."""
    exception with the return value instead of injecting the return value in the caller's frame."""
 
 
 def integer_subtraction(a, b, **remainder):
 def integer_subtraction(a, b, **remainder):