From 0603b93f479b8669d53b7d6552c316100829ff2c Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 2 Mar 2018 02:49:43 +0100 Subject: [PATCH] call/return --- testvm.txt | 18 ++++++++----- tinyvm/core.py | 2 ++ tinyvm/parse.py | 7 +++-- tinyvm/vm.py | 71 ++++++++++++++++++++++++++++++------------------- 4 files changed, 62 insertions(+), 36 deletions(-) diff --git a/testvm.txt b/testvm.txt index 401575782..03e1cb709 100644 --- a/testvm.txt +++ b/testvm.txt @@ -2,6 +2,7 @@ %block b1 %vardefs var byte teller 1 +var byte numbertoprint 0 const byte one 1 const byte thousand 100000 var array_byte newlinestr 2 [32 32] @@ -17,17 +18,20 @@ back: push teller push one add - pop teller - push teller + dup + dup + pop teller + call 1 printnumber + push thousand + cmp_lt + jump_if_true back + return 0 +printnumber: syscall decimalstr_signed syscall printstr push newlinestr syscall printstr - push teller - push thousand - cmp_lt - jump_if_true back - return + return 0 %end_instructions %subblocks diff --git a/tinyvm/core.py b/tinyvm/core.py index 3ae61d7d6..bc3d3b889 100644 --- a/tinyvm/core.py +++ b/tinyvm/core.py @@ -11,6 +11,8 @@ class Opcode(enum.IntEnum): POP = 13 POP2 = 14 POP3 = 15 + DUP = 16 + DUP2 = 17 ADD = 50 SUB = 51 MUL = 52 diff --git a/tinyvm/parse.py b/tinyvm/parse.py index 9940c8e73..2a5262b93 100644 --- a/tinyvm/parse.py +++ b/tinyvm/parse.py @@ -116,6 +116,8 @@ class Parser: args = parts[1].split() else: args = [] + if opcode in (Opcode.CALL, Opcode.RETURN): + args[0] = int(args[0]) # the number of arguments/parameters return Instruction(opcode, args, None, None) while not self.source[self.lineno].startswith("%"): @@ -175,7 +177,8 @@ l1: nop push c1 push2 c1 cws - return + call 3 l1 + return 2 %end_instructions %subblocks @@ -198,7 +201,7 @@ l1: nop l1: nop - return + return 99 %end_instructions %end_block ; b3 """ diff --git a/tinyvm/vm.py b/tinyvm/vm.py index 4181af9de..bfc3591e7 100644 --- a/tinyvm/vm.py +++ b/tinyvm/vm.py @@ -134,17 +134,19 @@ class Memory: class CallFrameMarker: - pass - - -class ReturnInstruction: - __slots__ = ["instruction"] + __slots__ = ["returninstruction"] def __init__(self, instruction: Instruction) -> None: - self.instruction = instruction + self.returninstruction = instruction + + def __str__(self) -> str: + return repr(self) + + def __repr__(self) -> str: + return "".format(str(self.returninstruction)) -StackValueType = Union[bool, int, float, bytearray, array.array, CallFrameMarker, ReturnInstruction] +StackValueType = Union[bool, int, float, bytearray, array.array, CallFrameMarker] class Stack: @@ -176,6 +178,9 @@ class Stack: self.pop_history.append(z) return x, y, z + def pop_under(self, number: int) -> StackValueType: + return self.stack.pop(-1-number) + def push(self, item: StackValueType) -> None: self._typecheck(item) self.stack.append(item) @@ -192,6 +197,9 @@ class Stack: self._typecheck(third) self.stack.extend([first, second, third]) + def push_under(self, number: int, value: StackValueType) -> None: + self.stack.insert(-number, value) + def peek(self) -> StackValueType: return self.stack[-1] if self.stack else None @@ -201,7 +209,7 @@ class Stack: self.stack[-2] = x def _typecheck(self, value: StackValueType): - if type(value) not in (bool, int, float, bytearray, array.array, CallFrameMarker, ReturnInstruction): + if type(value) not in (bool, int, float, bytearray, array.array, CallFrameMarker): raise TypeError("invalid item type pushed") @@ -223,6 +231,9 @@ class VM: if method.startswith("opcode_"): if not method[7:] in opcode_names: raise RuntimeError("opcode method for undefined opcode " + method) + for oc in Opcode: + if oc not in self.dispatch_table: + raise NotImplementedError("no dispatch entry in table for " + oc.name) self.memory = Memory() for start, end in self.readonly_mem_ranges: self.memory.mark_readonly(start, end) @@ -305,15 +316,14 @@ class VM: elif i.opcode == Opcode.JUMP: i.next = self.labels[i.args[0]] # jump target elif i.opcode == Opcode.CALL: - i.next = self.labels[i.args[0]] # call target + i.next = self.labels[i.args[1]] # call target i.alt_next = nexti # return instruction else: i.next = nexti def run(self) -> None: self.pc = self.program[0] # first instruction of the main program - self.stack.push(ReturnInstruction(None)) # sentinel - self.stack.push(CallFrameMarker()) # enter the call frame so the timer program can end with a RETURN + self.stack.push(CallFrameMarker(None)) # enter the call frame so the timer program can end with a RETURN try: while self.pc is not None: with self.timer_irq_interlock: @@ -346,8 +356,7 @@ class VM: self.stack = self.timer_stack self.program = self.timer_program self.pc = self.program[0] - self.stack.push(ReturnInstruction(None)) # sentinel - self.stack.push(CallFrameMarker()) # enter the call frame so the timer program can end with a RETURN + self.stack.push(CallFrameMarker(None)) # enter the call frame so the timer program can end with a RETURN while self.pc is not None: next_pc = self.dispatch_table[self.pc.opcode](self, self.pc) if next_pc: @@ -362,14 +371,14 @@ class VM: def debug_stack(self, size: int=5) -> None: stack = self.stack.debug_peek(size) if len(stack) > 0: - print("* stack (top {:d}):".format(size)) + print("** stack (top {:d}):".format(size)) for i, value in enumerate(reversed(stack), start=1): - print(" {:d}. {:s} {!r}".format(i, type(value).__name__, value)) + print(" {:d}. {:s} {:s}".format(i, type(value).__name__, str(value))) else: - print("* stack is empty.") + print("** stack is empty.") if self.stack.pop_history: - print("* last {:d} values popped from stack (most recent last):".format(self.stack.pop_history.maxlen)) - pprint.pprint(list(self.stack.pop_history), indent=2, compact=True, width=20) # type: ignore + print("** last {:d} values popped from stack (most recent on top):".format(self.stack.pop_history.maxlen)) + pprint.pprint(list(reversed(self.stack.pop_history)), indent=2, compact=True, width=20) # type: ignore if self.pc is not None: print("* instruction:", self.pc) @@ -389,6 +398,16 @@ class VM: self.stack.push(value) return True + def opcode_DUP(self, instruction: Instruction) -> bool: + self.stack.push(self.stack.peek()) + return True + + def opcode_DUP2(self, instruction: Instruction) -> bool: + x = self.stack.peek() + self.stack.push(x) + self.stack.push(x) + return True + def opcode_PUSH2(self, instruction: Instruction) -> bool: value1 = self.variables[instruction.args[0]].value value2 = self.variables[instruction.args[1]].value @@ -497,18 +516,14 @@ class VM: return True def opcode_CALL(self, instruction: Instruction) -> bool: - self.stack.push(ReturnInstruction(instruction.alt_next)) - self.stack.push(CallFrameMarker()) + # arguments are already on the stack + self.stack.push_under(instruction.args[0], CallFrameMarker(instruction.alt_next)) return True def opcode_RETURN(self, instruction: Instruction) -> bool: - # unwind the function call frame - item = self.stack.pop() - while not isinstance(item, CallFrameMarker): - item = self.stack.pop() - returninstruction = self.stack.pop() - assert isinstance(returninstruction, ReturnInstruction) - self.pc = returninstruction.instruction + callframe = self.stack.pop_under(instruction.args[0]) + assert isinstance(callframe, CallFrameMarker) + self.pc = callframe.returninstruction return False def opcode_JUMP(self, instruction: Instruction) -> bool: @@ -544,6 +559,8 @@ class VM: Opcode.POP: opcode_POP, Opcode.POP2: opcode_POP2, Opcode.POP3: opcode_POP3, + Opcode.DUP: opcode_DUP, + Opcode.DUP2: opcode_DUP2, Opcode.ADD: opcode_ADD, Opcode.SUB: opcode_SUB, Opcode.MUL: opcode_MUL,