|
@@ -11,11 +11,70 @@ if sys.version > '3': # pragma: no cover
|
|
|
else:
|
|
|
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):
|
|
|
def __init__(self, root):
|
|
|
self.root = root
|
|
|
self.returnvalue = None
|
|
|
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.allow_compiled = True
|
|
|
#self.allow_compiled = False
|
|
@@ -46,26 +105,80 @@ class ModelverseKernel(object):
|
|
|
self.debug_info = defaultdict(list)
|
|
|
|
|
|
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):
|
|
|
user_root, = yield [("RD", [self.root, username])]
|
|
@@ -176,34 +289,19 @@ class ModelverseKernel(object):
|
|
|
parameters["mvk"] = self
|
|
|
|
|
|
# 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.
|
|
|
- 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
|
|
|
old_frame, exception_return = yield [
|
|
|
("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]:
|
|
|
self.debug_info[self.username].pop()
|
|
@@ -211,10 +309,10 @@ class ModelverseKernel(object):
|
|
|
if exception_return is not None:
|
|
|
# The caller has requested that we throw an exception instead of injecting
|
|
|
# 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]),
|
|
|
("DN", [user_frame])]
|
|
|
- raise primitive_functions.PrimitiveFinished(result)
|
|
|
+ raise primitive_functions.InterpretedFunctionFinished(result)
|
|
|
else:
|
|
|
lnk, = yield [("RDE", [old_frame, "returnvalue"])]
|
|
|
_, _, _, _ = yield [("CD", [old_frame, "returnvalue", result]),
|
|
@@ -614,10 +712,10 @@ class ModelverseKernel(object):
|
|
|
|
|
|
if value is None:
|
|
|
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
|
|
|
# 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:
|
|
|
_, = yield [("DN", [user_root])]
|
|
|
del self.debug_info[self.username]
|
|
@@ -629,7 +727,7 @@ class ModelverseKernel(object):
|
|
|
]
|
|
|
|
|
|
if exception_return is not None:
|
|
|
- raise primitive_functions.PrimitiveFinished(None)
|
|
|
+ raise primitive_functions.InterpretedFunctionFinished(None)
|
|
|
else:
|
|
|
evalstack, evalstack_link, ip_link, new_evalstack, evalstack_phase = \
|
|
|
yield [("RD", [user_frame, "evalstack"]),
|
|
@@ -655,17 +753,17 @@ class ModelverseKernel(object):
|
|
|
user_frame, = yield [("RD", [user_root, "frame"])]
|
|
|
prev_frame, exception_return, returnvalue = yield [
|
|
|
("RD", [user_frame, "prev"]),
|
|
|
- ("RD", [user_frame, primitive_functions.PRIMITIVE_RETURN_KEY]),
|
|
|
+ ("RD", [user_frame, primitive_functions.EXCEPTION_RETURN_KEY]),
|
|
|
("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
|
|
|
# gives us O(1) state reads per jit-interpreter transition.
|
|
|
if exception_return is not None:
|
|
|
yield [
|
|
|
("CD", [user_root, "frame", prev_frame]),
|
|
|
("DN", [user_frame])]
|
|
|
- raise primitive_functions.PrimitiveFinished(returnvalue)
|
|
|
+ raise primitive_functions.InterpretedFunctionFinished(returnvalue)
|
|
|
else:
|
|
|
old_returnvalue_link, = yield [("RDE", [prev_frame, "returnvalue"])]
|
|
|
yield [
|