From eb128d8b54a1bb9f36a766a89dbf11fb0fd49782 Mon Sep 17 00:00:00 2001 From: Reid 'arrdem' McKenzie Date: Fri, 15 Jul 2022 19:33:32 -0600 Subject: [PATCH] Refactor to make Opcode the base class --- .../shoggoth/src/python/ichor/assembler.py | 72 +++-- .../shoggoth/src/python/ichor/bootstrap.py | 46 +-- projects/shoggoth/src/python/ichor/impl.py | 38 +-- projects/shoggoth/src/python/ichor/isa.py | 283 ++++++++++-------- projects/shoggoth/src/python/ichor/state.py | 21 +- .../test/python/ichor/test_assembler.py | 36 +-- .../test/python/ichor/test_bootstrap.py | 78 ++--- .../test/python/ichor/test_interpreter.py | 41 +-- .../shoggoth/test/python/ichor/test_state.py | 12 + 9 files changed, 358 insertions(+), 269 deletions(-) diff --git a/projects/shoggoth/src/python/ichor/assembler.py b/projects/shoggoth/src/python/ichor/assembler.py index dc47df3..6b487e9 100644 --- a/projects/shoggoth/src/python/ichor/assembler.py +++ b/projects/shoggoth/src/python/ichor/assembler.py @@ -3,33 +3,42 @@ from dataclasses import dataclass from random import choices from string import ascii_lowercase, digits -from typing import List +from typing import Generator, List, Union, Optional, Sequence -from ichor.isa import Opcode +from ichor import isa -@dataclass -class Label(object): - name: str +def gensym(prefix = None) -> isa.Label: + frag = ''.join(choices(ascii_lowercase + digits, k=8)) + return isa.Label(f"{prefix or 'gensym'}_{frag}") - def __hash__(self): - return hash(self.name) class FuncBuilder(object): def __init__(self) -> None: self._opcodes = [] - def write(self, op: Opcode): + def _write(self, op): self._opcodes.append(op) - def make_label(self, prefix=""): - frag = ''.join(choices(ascii_lowercase + digits, k=8)) - return Label(f"{prefix or 'gensym'}_{frag}") + def write(self, op: Union[isa.Opcode, isa.Label, Sequence[isa.Opcode]]): - def set_label(self, label: Label): - self._opcodes.append(label) + def flatten(o): + for e in o: + if isinstance(e, (isa.Opcode, isa.Label)): + yield e + else: + yield from flatten(e) - def build(self) -> List[Opcode]: + if isinstance(op, (isa.Opcode, isa.Label)): + self._opcodes.append(op) + else: + for e in op: + self.write(e) + + def make_label(self, prefix: Optional[str] = ""): + return gensym(prefix) + + def build(self) -> List[isa.Opcode]: """Assemble the written body into fully resolved opcodes.""" # The trivial two-pass assembler. First pass removes labels from the @@ -39,8 +48,8 @@ class FuncBuilder(object): unassembled = [] for op in self._opcodes: match op: - case Label(_) as l: - assert l not in labels # Label marks must be unique. + case isa.Label(_) as l: + assert l not in labels # isa.Label marks must be unique. labels[l] = len(unassembled) case o: unassembled.append(o) @@ -50,13 +59,36 @@ class FuncBuilder(object): assembled = [] for op in unassembled: match op: - case Opcode.GOTO(Label(_) as l): - assembled.append(Opcode.GOTO(labels[l])) + case isa.GOTO(isa.Label(_) as l): + assembled.append(isa.GOTO(labels[l])) - case Opcode.VTEST(Label(_) as l): - assembled.append(Opcode.VTEST(labels[l])) + case isa.VTEST(isa.Label(_) as l): + assembled.append(isa.VTEST(labels[l])) case o: assembled.append(o) return assembled + + +def assemble(builder_cls=FuncBuilder, /, **opcodes: List[isa.Opcode]) -> List[isa.Opcode]: + builder = builder_cls() + for o in opcodes: + builder.write(o) + return builder.build() + + +class LocalBuilder(FuncBuilder): + def __init__(self): + super().__init__() + self._stack = 0 + self._labels = {} + + def _write(self, op): + pass + + def write_local(self, label: isa.Label): + pass + + def get_local(self, label: isa.Label): + pass diff --git a/projects/shoggoth/src/python/ichor/bootstrap.py b/projects/shoggoth/src/python/ichor/bootstrap.py index 40dc611..7fc2682 100644 --- a/projects/shoggoth/src/python/ichor/bootstrap.py +++ b/projects/shoggoth/src/python/ichor/bootstrap.py @@ -4,7 +4,7 @@ Some utterly trivial functions and types that allow me to begin testing the VM. Hopefully no "real" interpreter ever uses this code, since it's obviously replaceable. """ -from ichor.isa import Opcode +from ichor import isa from ichor.state import Module, Variant from ichor.assembler import FuncBuilder @@ -22,59 +22,59 @@ NOT1 = BOOTSTRAP.define_function( f";not;{BOOL};{BOOL}", [ # a: Bool - Opcode.IDENTIFIERC("bool"), - Opcode.TYPEREF(), # a - Opcode.DUP(), - Opcode.IDENTIFIERC("true"), - Opcode.VARIANTREF(), # a - Opcode.DUP(), - Opcode.SLOT(0), - Opcode.ROT(2), - Opcode.VTEST(11), + isa.IDENTIFIERC("bool"), + isa.TYPEREF(), # a + isa.DUP(), + isa.IDENTIFIERC("true"), + isa.VARIANTREF(), # a + isa.DUP(), + isa.SLOT(0), + isa.ROT(2), + isa.VTEST(11), - Opcode.VARIANT(0), - Opcode.RETURN(1), + isa.VARIANT(0), + isa.RETURN(1), - Opcode.DROP(1), - Opcode.IDENTIFIERC("false"), - Opcode.VARIANTREF(), - Opcode.VARIANT(0), - Opcode.RETURN(1), + isa.DROP(1), + isa.IDENTIFIERC("false"), + isa.VARIANTREF(), + isa.VARIANT(0), + isa.RETURN(1), ], ) OR2 = BOOTSTRAP.define_function( f";or;{BOOL},{BOOL};{BOOL}", [ - Opcode.BREAK(), + isa.BREAK(), ], ) OR3 = BOOTSTRAP.define_function( f";or;{BOOL},{BOOL},{BOOL};{BOOL}", [ - Opcode.BREAK(), + isa.BREAK(), ] ) AND2 = BOOTSTRAP.define_function( f";and;{BOOL},{BOOL};{BOOL}", [ - Opcode.BREAK(), + isa.BREAK(), ], ) AND3 = BOOTSTRAP.define_function( f";and;{BOOL},{BOOL},{BOOL};{BOOL}", [ - Opcode.BREAK(), + isa.BREAK(), ], ) XOR2 = BOOTSTRAP.define_function( f";xor;{BOOL},{BOOL};{BOOL}", [ - Opcode.BREAK(), + isa.BREAK(), ], ) @@ -82,6 +82,6 @@ XOR3 = BOOTSTRAP.define_function( f";xor;{BOOL},{BOOL},{BOOL};{BOOL}", [ # A^B|B^C - Opcode.BREAK(), + isa.BREAK(), ] ) diff --git a/projects/shoggoth/src/python/ichor/impl.py b/projects/shoggoth/src/python/ichor/impl.py index 3fefde8..e5d5030 100644 --- a/projects/shoggoth/src/python/ichor/impl.py +++ b/projects/shoggoth/src/python/ichor/impl.py @@ -13,7 +13,7 @@ from copy import deepcopy import typing as t from textwrap import indent -from ichor.isa import Opcode +from ichor import isa from ichor.state import Closure, FunctionRef, Identifier, Module, Function, Type, TypeRef, VariantRef, Variant, Stackframe @@ -52,7 +52,7 @@ class Interpreter(object): b = [] b.append(f"clock {clock}:") b.append(" stack:") - for offset, it in zip(range(len(stackframe), 0, -1), stackframe): + for offset, it in zip(range(0, len(stackframe), 1), stackframe): b.append(f" {offset: <3} {it}") b.append(f" op: {op}") print(indent("\n".join(b), " " * stackframe.depth)) @@ -64,7 +64,7 @@ class Interpreter(object): clock += 1 match op: - case Opcode.IDENTIFIERC(name): + case isa.IDENTIFIERC(name): if not (name in mod.functions or name in mod.types or any(name in t.constructors for t in mod.types.values())): @@ -72,7 +72,7 @@ class Interpreter(object): stackframe.push(Identifier(name)) - case Opcode.TYPEREF(): + case isa.TYPEREF(): id = stackframe.pop() if not isinstance(id, Identifier): _error("TYPEREF consumes an identifier") @@ -81,7 +81,7 @@ class Interpreter(object): stackframe.push(TypeRef(id.name)) - case Opcode.VARIANTREF(): + case isa.VARIANTREF(): id: Identifier = stackframe.pop() if not isinstance(id, Identifier): _error("VARIANTREF consumes an identifier and a typeref") @@ -96,7 +96,7 @@ class Interpreter(object): stackframe.push(VariantRef(t, id.name)) - case Opcode.VARIANT(n): + case isa.VARIANT(n): armref: VariantRef = stackframe.pop() if not isinstance(armref, VariantRef): _error("VARIANT must be given a valid constructor reference") @@ -114,7 +114,7 @@ class Interpreter(object): stackframe.drop(n) stackframe.push(v) - case Opcode.VTEST(n): + case isa.VTEST(n): armref: VariantRef = stackframe.pop() if not isinstance(armref, VariantRef): _error("VTEST must be given a variant reference") @@ -127,38 +127,38 @@ class Interpreter(object): stackframe.goto(n) continue - case Opcode.GOTO(n): + case isa.GOTO(n): if (n < 0): _error("Illegal branch target") stackframe.goto(n) continue - case Opcode.DUP(n): + case isa.DUP(n): if (n > len(stackframe)): _error("Stack size violation") stackframe.dup(n) - case Opcode.ROT(n): + case isa.ROT(n): if (n > len(stackframe)): _error("Stack size violation") stackframe.rot(n) - case Opcode.DROP(n): + case isa.DROP(n): if (n > len(stackframe)): _error("Stack size violation") stackframe.drop(n) - case Opcode.SLOT(n): + case isa.SLOT(n): if (n < 0): _error("SLOT must have a positive reference") if (n > len(stackframe) - 1): _error("SLOT reference out of range") stackframe.slot(n) - case Opcode.FUNREF(): + case isa.FUNREF(): id = stackframe.pop() if not isinstance(id, Identifier): _error("FUNREF consumes an IDENTIFIER") @@ -168,7 +168,7 @@ class Interpreter(object): except: _error("Invalid function ref") - case Opcode.CALLF(n): + case isa.CALLF(n): sig = stackframe.pop() if not isinstance(sig, FunctionRef): _error("CALLF requires a funref at top of stack") @@ -186,7 +186,7 @@ class Interpreter(object): stackframe = stackframe.call(fun, ip) continue - case Opcode.RETURN(n): + case isa.RETURN(n): if (n > len(stackframe)): _error("Stack size violation") @@ -199,7 +199,7 @@ class Interpreter(object): stackframe = stackframe.ret(n) continue - case Opcode.CLOSUREF(n): + case isa.CLOSUREF(n): sig = stackframe.pop() if not isinstance(sig, FunctionRef): _error("CLOSUREF requires a funref at top of stack") @@ -216,7 +216,7 @@ class Interpreter(object): stackframe.drop(n) stackframe.push(c) - case Opcode.CLOSUREC(n): + case isa.CLOSUREC(n): c = stackframe.pop() if not isinstance(c, Closure): _error("CLOSUREC requires a closure at top of stack") @@ -233,7 +233,7 @@ class Interpreter(object): stackframe.drop(n) stackframe.push(c) - case Opcode.CALLC(n): + case isa.CALLC(n): c = stackframe.pop() if not isinstance(c, Closure): _error("CALLC requires a closure at top of stack") @@ -246,7 +246,7 @@ class Interpreter(object): # Extract the function signature # Push the closure's stack fragment - stackframe._stack = stackframe._stack.extendleft(c.frag) + stackframe._stack = c.frag + stackframe._stack # Perform a "normal" funref call try: diff --git a/projects/shoggoth/src/python/ichor/isa.py b/projects/shoggoth/src/python/ichor/isa.py index c0f054d..0c6d839 100644 --- a/projects/shoggoth/src/python/ichor/isa.py +++ b/projects/shoggoth/src/python/ichor/isa.py @@ -1,205 +1,242 @@ """The instruction set for Shogoth.""" - +from abc import ABC +from dataclasses import dataclass import typing as t -class Opcode: - # Note that there's no IF, TRUE or FALSE required if bool is a builtin. +@dataclass +class Label(object): + name: str - class GOTO(t.NamedTuple): - """() -> () + def __hash__(self): + return hash(self.name) - Branch to another point within the same bytecode segment. The target MUST be within the same module range as the - current function. Branching does NOT update the name or module of the current function. - """ +class Opcode(ABC): + pass - target: int - #################################################################################################### - # Stack manipulation - #################################################################################################### - # https://wiki.laptop.org/go/Forth_stack_operators - # https://www.forth.com/starting-forth/2-stack-manipulation-operators-arithmetic/ - # https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5.swap +@dataclass +class GOTO(Opcode): + """() -> () - class DUP(t.NamedTuple): - """(A, B, ...) -> (A, B, ...) + Branch to another point within the same bytecode segment. The target MUST be within the same module range as the + current function. Branching does NOT update the name or module of the current function. - Duplicate the top N items of the stack. + """ - """ + target: int - nargs: int = 1 +#################################################################################################### +# Stack manipulation +#################################################################################################### +# https://wiki.laptop.org/go/Forth_stack_operators +# https://www.forth.com/starting-forth/2-stack-manipulation-operators-arithmetic/ +# https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5.swap - class ROT(t.NamedTuple): - """(A, B, ... Z) -> (Z, A, B, ...) +@dataclass +class DUP(Opcode): + """(A, B, ...) -> (A, B, ...) - Rotate the top N elements of the stack. + Duplicate the top N items of the stack. - """ + """ - nargs: int = 2 + nargs: int = 1 - class DROP(t.NamedTuple): - """(*) -> () - Drop the top N items of the stack. +@dataclass +class ROT(Opcode): + """(A, B, ... Z) -> (Z, A, B, ...) - """ + Rotate the top N elements of the stack. - nargs: int = 1 + """ - class SLOT(t.NamedTuple): - """(..., A) -> (A, ..., A) + nargs: int = 2 - Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack. - Intended to allow users to emulate (immutable) frame local slots for reused values. +@dataclass +class DROP(Opcode): + """(*) -> () - """ + Drop the top N items of the stack. - target: int + """ - #################################################################################################### - # Functional abstraction - #################################################################################################### + nargs: int = 1 - class IDENTIFIERC(t.NamedTuple): - """() -> (IDENTIFIER) - An inline constant which produces an identifier to the stack. +@dataclass +class SLOT(Opcode): + """(..., A) -> (A, ..., A) - Identifiers name functions, fields and types but are not strings. - They are a VM-internal naming structure with reference to the module. + Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack. + Intended to allow users to emulate (immutable) frame local slots for reused values. - """ + """ - val: str + target: int - class FUNREF(t.NamedTuple): - """(IDENTIFIER) -> (`FUNREF<... A to ... B>`) +#################################################################################################### +# Functional abstraction +#################################################################################################### - Construct a reference to a static codepoint. +@dataclass +class IDENTIFIERC(Opcode): + """() -> (IDENTIFIER) - """ + An inline constant which produces an identifier to the stack. - class CALLF(t.NamedTuple): - """(`FUNREF<... A to ... B>`, ... A) -> (... B) + Identifiers name functions, fields and types but are not strings. + They are a VM-internal naming structure with reference to the module. - Call [funref] + """ - Make a dynamic call to the function reference at the top of stack. - The callee will see a stack containg only the provided `nargs`. - A subsequent RETURN will return execution to the next point. + val: str - Executing a `CALL` pushes the name and module path of the current function. - """ +@dataclass +class FUNREF(Opcode): + """(IDENTIFIER) -> (`FUNREF<... A to ... B>`) - nargs: int = 0 + Construct a reference to a static codepoint. - class RETURN(t.NamedTuple): - """(... A) -> () + """ - Return to the source of the last `CALL`. The returnee will see the top `nargs` values of the present stack - appended to theirs. All other values on the stack will be discarded. +@dataclass +class CALLF(Opcode): + """(`FUNREF<... A to ... B>`, ... A) -> (... B) - Executing a `RETURN` pops (restores) the name and module path of the current function back to that of the caller. + Call [funref] - If the call stack is empty, `RETURN` will exit the interpreter. + Make a dynamic call to the function reference at the top of stack. + The callee will see a stack containg only the provided `nargs`. + A subsequent RETURN will return execution to the next point. - """ + Executing a `CALL` pushes the name and module path of the current function. - nargs: int + """ + nargs: int = 0 - class CLOSUREF(t.NamedTuple): - """(`FUNREF`, A) -> (`CLOSURE<... B to ... C>`) +@dataclass +class RETURN(Opcode): + """(... A) -> () - Construct a closure over the function reference at the top of the stack. This may produce nullary closures. + Return to the source of the last `CALL`. The returnee will see the top `nargs` values of the present stack + appended to theirs. All other values on the stack will be discarded. - """ + Executing a `RETURN` pops (restores) the name and module path of the current function back to that of the caller. - nargs: int = 0 + If the call stack is empty, `RETURN` will exit the interpreter. - class CLOSUREC(t.NamedTuple): - """(`CLOSURE`, A) -> (`CLOSURE<... B to ... C>`) + """ - Further close over the closure at the top of the stack. This may produce nullary closures. + nargs: int - """ - nargs: int = 0 +@dataclass +class CLOSUREF(Opcode): + """(`FUNREF`, A) -> (`CLOSURE<... B to ... C>`) - class CALLC(t.NamedTuple): - """(`CLOSURE<... A to ... B>`, ... A) -> (... B) + Construct a closure over the function reference at the top of the stack. This may produce nullary closures. - Call [closure] + """ - Make a dynamic call to the closure at the top of stack. - The callee will see a stack containg only the provided `nargs` and closed-overs. - A subsequent RETURN will return execution to the next point. + nargs: int = 0 - Executing a `CALL` pushes the name and module path of the current function. - """ +@dataclass +class CLOSUREC(Opcode): + """(`CLOSURE`, A) -> (`CLOSURE<... B to ... C>`) - nargs: int = 0 + Further close over the closure at the top of the stack. This may produce nullary closures. - #################################################################################################### - # Structures - #################################################################################################### + """ - # FIXME: This lacks any sort of way to do dynamic type/field references + nargs: int = 0 - class TYPEREF(t.NamedTuple): - """(IDENTIFIER) -> (TYPEREF) - Produces a TYPEREF to the type named by the provided IDENTIFIER. +class CALLC(Opcode): + """(`CLOSURE<... A to ... B>`, ... A) -> (... B) - """ + Call [closure] - class VARIANTREF(t.NamedTuple): - """(IDENTIFIER, TYPEREF) -> (VARIANTREF) + Make a dynamic call to the closure at the top of stack. + The callee will see a stack containg only the provided `nargs` and closed-overs. + A subsequent RETURN will return execution to the next point. - Produce a VARIANTREF to an 'arm' of the given variant type. + Executing a `CALL` pushes the name and module path of the current function. - """ + """ - class VARIANT(t.NamedTuple): - """(VARIANTREF, ...) -> (B) + nargs: int = 0 - Construct an instance of an 'arm' of a variant. - The type of the 'arm' is considered to be the type of the whole variant. +#################################################################################################### +# Structures +#################################################################################################### - The name and module path of the current function MUST match the name and module path of `VARIANTREF`. - The arity of this opcode MUST match the arity of the arm. - The signature of the arm MUST match the signature fo the top N of the stack. - """ +# FIXME: This lacks any sort of way to do dynamic type/field references - nargs: int = 0 +@dataclass +class TYPEREF(Opcode): + """(IDENTIFIER) -> (TYPEREF) - class VTEST(t.NamedTuple): - """(VARIANTREF, B) -> () + Produces a TYPEREF to the type named by the provided IDENTIFIER. - Test whether B is a given arm of a variant A . - If it is, branch to the given target. - Otherwise fall through. + """ - """ - target: int +@dataclass +class VARIANTREF(Opcode): + """(IDENTIFIER, TYPEREF) -> (VARIANTREF) - class VLOAD(t.NamedTuple): - """(VARIANTREF, B) -> (A) + Produce a VARIANTREF to an 'arm' of the given variant type. - Load the value of the variant arm. - VLOAD errors (undefined) if B is not within the variant. - VLOAD errors (undefined) if the value in B is not an A - use VTEST as needed. + """ - """ - class BREAK(t.NamedTuple): - """Abort the interpreter.""" - pass +@dataclass +class VARIANT(Opcode): + """(VARIANTREF, ...) -> (B) + + Construct an instance of an 'arm' of a variant. + The type of the 'arm' is considered to be the type of the whole variant. + + The name and module path of the current function MUST match the name and module path of `VARIANTREF`. + The arity of this opcode MUST match the arity of the arm. + The signature of the arm MUST match the signature fo the top N of the stack. + """ + + nargs: int = 0 + + +@dataclass +class VTEST(Opcode): + """(VARIANTREF, B) -> () + + Test whether B is a given arm of a variant A . + If it is, branch to the given target. + Otherwise fall through. + + """ + + target: int + + +@dataclass +class VLOAD(Opcode): + """(VARIANTREF, B) -> (A) + + Load the value of the variant arm. + VLOAD errors (undefined) if B is not within the variant. + VLOAD errors (undefined) if the value in B is not an A - use VTEST as needed. + + """ + + +@dataclass +class BREAK(Opcode): + """Abort the interpreter.""" + pass diff --git a/projects/shoggoth/src/python/ichor/state.py b/projects/shoggoth/src/python/ichor/state.py index cb8782f..623cce3 100644 --- a/projects/shoggoth/src/python/ichor/state.py +++ b/projects/shoggoth/src/python/ichor/state.py @@ -4,7 +4,7 @@ import typing as t -from ichor.isa import Opcode +from ichor import isa from pyrsistent import pdeque, PDeque from lark import Lark, Transformer, v_args, Token @@ -77,13 +77,13 @@ class Function(t.NamedTuple): name: str arguments: t.List[str] returns: t.List[str] - instructions: t.List[Opcode] + instructions: t.List[isa.Opcode] typevars: t.List[t.Any] = [] typeconstraints: t.List[t.Any] = [] metadata: dict = {} @classmethod - def build(cls, name: str, instructions: t.List[Opcode]): + def build(cls, name: str, instructions: t.List[isa.Opcode]): constraints, name, args, rets = FUNC.parse(name) # FIXME: Constraints probably needs some massaging # FIXME: Need to get typevars from somewhere @@ -188,18 +188,18 @@ class Module(t.NamedTuple): ) @staticmethod - def translate(start: int, end: int, i: Opcode): + def translate(start: int, end: int, i: isa.Opcode): # FIXME: Consolidate bounds checks somehow match i: - case Opcode.VTEST(t): + case isa.VTEST(t): d = t + start assert start <= d < end - return Opcode.VTEST(d) + return isa.VTEST(d) - case Opcode.GOTO(t): + case isa.GOTO(t): d = t + start assert start <= d < end - return Opcode.GOTO(d) + return isa.GOTO(d) case _: return i @@ -279,7 +279,10 @@ class Stackframe(object): self._stack = self._stack[:-nargs] def rot(self, nargs): - self._stack[nargs:].extend(rotate(self._stack[:nargs])) + frag = self._stack[-nargs:] + base = self._stack[:-nargs] + rotated = rotate(frag) + self._stack = base + rotated def slot(self, n): self.push(self._stack[n]) diff --git a/projects/shoggoth/test/python/ichor/test_assembler.py b/projects/shoggoth/test/python/ichor/test_assembler.py index c7deea5..0ec2ee2 100644 --- a/projects/shoggoth/test/python/ichor/test_assembler.py +++ b/projects/shoggoth/test/python/ichor/test_assembler.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from ichor import FuncBuilder, Opcode +from ichor import FuncBuilder, isa import pytest @@ -11,37 +11,37 @@ def builder() -> FuncBuilder: def test_forwards_label(builder: FuncBuilder): l = builder.make_label() - builder.write(Opcode.GOTO(l)) - builder.write(Opcode.DROP(0)) # no-op - builder.set_label(l) - builder.write(Opcode.DROP(0)) # no-op + builder.write(isa.GOTO(l)) + builder.write(isa.DROP(0)) # no-op + builder.write(l) + builder.write(isa.DROP(0)) # no-op instrs = builder.build() assert instrs == [ - Opcode.GOTO(2), - Opcode.DROP(0), - Opcode.DROP(0), + isa.GOTO(2), + isa.DROP(0), + isa.DROP(0), ] def test_backwards_label(builder: FuncBuilder): l = builder.make_label() - builder.set_label(l) - builder.write(Opcode.DROP(0)) # no-op - builder.write(Opcode.GOTO(l)) + builder.write(l) + builder.write(isa.DROP(0)) # no-op + builder.write(isa.GOTO(l)) instrs = builder.build() assert instrs == [ - Opcode.DROP(0), - Opcode.GOTO(0), + isa.DROP(0), + isa.GOTO(0), ] def test_self_label(builder: FuncBuilder): l = builder.make_label() - builder.write(Opcode.DROP(0)) # no-op - builder.set_label(l) - builder.write(Opcode.GOTO(l)) + builder.write(isa.DROP(0)) # no-op + builder.write(l) + builder.write(isa.GOTO(l)) instrs = builder.build() assert instrs == [ - Opcode.DROP(0), - Opcode.GOTO(1), + isa.DROP(0), + isa.GOTO(1), ] diff --git a/projects/shoggoth/test/python/ichor/test_bootstrap.py b/projects/shoggoth/test/python/ichor/test_bootstrap.py index 283c3f3..2e338e6 100644 --- a/projects/shoggoth/test/python/ichor/test_bootstrap.py +++ b/projects/shoggoth/test/python/ichor/test_bootstrap.py @@ -2,7 +2,7 @@ from .fixtures import * # noqa -from ichor import * +from ichor import isa, TRUE, FALSE, NOT1 import pytest @@ -12,10 +12,10 @@ import pytest ]) def test_not(vm, stack, ret): assert vm.run([ - Opcode.IDENTIFIERC(NOT1), - Opcode.FUNREF(), - Opcode.CALLF(1), - Opcode.RETURN(1) + isa.IDENTIFIERC(NOT1), + isa.FUNREF(), + isa.CALLF(1), + isa.RETURN(1) ], stack = stack) == ret @@ -27,10 +27,10 @@ def test_not(vm, stack, ret): # ]) # def test_or(vm, stack, ret): # assert vm.run([ -# Opcode.IDENTIFIERC(OR2), -# Opcode.FUNREF(), -# Opcode.CALLF(2), -# Opcode.RETURN(1) +# isa.IDENTIFIERC(OR2), +# isa.FUNREF(), +# isa.CALLF(2), +# isa.RETURN(1) # ], stack = stack) == ret @@ -42,10 +42,10 @@ def test_not(vm, stack, ret): # ]) # def test_and(vm, stack, ret): # assert vm.run([ -# Opcode.IDENTIFIERC(AND2), -# Opcode.FUNREF(), -# Opcode.CALLF(2), -# Opcode.RETURN(1) +# isa.IDENTIFIERC(AND2), +# isa.FUNREF(), +# isa.CALLF(2), +# isa.RETURN(1) # ], stack = stack) == ret @@ -57,10 +57,10 @@ def test_not(vm, stack, ret): # ]) # def test_xor2(vm, stack, ret): # assert vm.run([ -# Opcode.IDENTIFIERC(XOR2), -# Opcode.FUNREF(), -# Opcode.CALLF(2), -# Opcode.RETURN(1) +# isa.IDENTIFIERC(XOR2), +# isa.FUNREF(), +# isa.CALLF(2), +# isa.RETURN(1) # ], stack = stack) == ret @@ -75,10 +75,10 @@ def test_not(vm, stack, ret): # ]) # def test_xor3(vm, stack, ret): # assert vm.run([ -# Opcode.IDENTIFIERC(XOR3), -# Opcode.FUNREF(), -# Opcode.CALLF(3), -# Opcode.RETURN(1) +# isa.IDENTIFIERC(XOR3), +# isa.FUNREF(), +# isa.CALLF(3), +# isa.RETURN(1) # ], stack = stack) == ret @@ -87,9 +87,9 @@ def test_not(vm, stack, ret): # ]) # def test_funref(vm, stack, ret): # assert vm.run([ -# Opcode.IDENTIFIERC(NOT1), -# Opcode.FUNREF(), -# Opcode.RETURN(1) +# isa.IDENTIFIERC(NOT1), +# isa.FUNREF(), +# isa.RETURN(1) # ], stack = stack) == ret @@ -98,10 +98,10 @@ def test_not(vm, stack, ret): # ]) # def test_callf(vm, stack, ret): # assert vm.run([ -# Opcode.IDENTIFIERC(NOT1), -# Opcode.FUNREF(), -# Opcode.CALLF(1), -# Opcode.RETURN(1) +# isa.IDENTIFIERC(NOT1), +# isa.FUNREF(), +# isa.CALLF(1), +# isa.RETURN(1) # ], stack = stack) == ret @@ -113,11 +113,11 @@ def test_not(vm, stack, ret): # ]) # def test_callc(vm, stack, ret): # assert vm.run([ -# Opcode.IDENTIFIERC(XOR2), -# Opcode.FUNREF(), -# Opcode.CLOSUREF(1), -# Opcode.CALLC(1), -# Opcode.RETURN(1), +# isa.IDENTIFIERC(XOR2), +# isa.FUNREF(), +# isa.CLOSUREF(1), +# isa.CALLC(1), +# isa.RETURN(1), # ], stack = stack) == ret @@ -132,10 +132,10 @@ def test_not(vm, stack, ret): # ]) # def test_closurec(vm, stack, ret): # assert vm.run([ -# Opcode.IDENTIFIERC(XOR3), -# Opcode.FUNREF(), -# Opcode.CLOSUREF(1), -# Opcode.CLOSUREC(1), -# Opcode.CALLC(1), -# Opcode.RETURN(1), +# isa.IDENTIFIERC(XOR3), +# isa.FUNREF(), +# isa.CLOSUREF(1), +# isa.CLOSUREC(1), +# isa.CALLC(1), +# isa.RETURN(1), # ], stack = stack) == ret diff --git a/projects/shoggoth/test/python/ichor/test_interpreter.py b/projects/shoggoth/test/python/ichor/test_interpreter.py index 57d9dc4..4992355 100644 --- a/projects/shoggoth/test/python/ichor/test_interpreter.py +++ b/projects/shoggoth/test/python/ichor/test_interpreter.py @@ -4,59 +4,64 @@ Tests coverign the VM interpreter from .fixtures import * # noqa -from ichor import * +from ichor import isa, InterpreterError, TRUE, FALSE import pytest def test_return(vm): - assert vm.run([Opcode.RETURN(0)], stack=[TRUE, FALSE]) == [] - assert vm.run([Opcode.RETURN(1)], stack=[TRUE, FALSE]) == [TRUE] - assert vm.run([Opcode.RETURN(2)], stack=[TRUE, FALSE]) == [TRUE, FALSE] + assert vm.run([isa.RETURN(0)], stack=[TRUE, FALSE]) == [] + assert vm.run([isa.RETURN(1)], stack=[TRUE, FALSE]) == [TRUE] + assert vm.run([isa.RETURN(2)], stack=[TRUE, FALSE]) == [TRUE, FALSE] def test_dup(vm): - assert vm.run([Opcode.DUP(1), Opcode.RETURN(3)], stack=[FALSE, TRUE]) == [FALSE, TRUE, TRUE] - assert vm.run([Opcode.DUP(2), Opcode.RETURN(4)], stack=[FALSE, TRUE]) == [FALSE, TRUE, FALSE, TRUE] + assert vm.run([isa.DUP(1), isa.RETURN(3)], stack=[FALSE, TRUE]) == [FALSE, TRUE, TRUE] + assert vm.run([isa.DUP(2), isa.RETURN(4)], stack=[FALSE, TRUE]) == [FALSE, TRUE, FALSE, TRUE] def test_rot(vm): assert vm.run([ - Opcode.ROT(2), - Opcode.RETURN(2) + isa.ROT(2), + isa.RETURN(2) ], stack=[FALSE, TRUE]) == [TRUE, FALSE] assert vm.run([ - Opcode.ROT(3), - Opcode.RETURN(3) + isa.ROT(2), + isa.RETURN(5) + ], stack=[TRUE, TRUE, TRUE, FALSE, TRUE]) == [TRUE, TRUE, TRUE, TRUE, FALSE] + + assert vm.run([ + isa.ROT(3), + isa.RETURN(3) ], stack=[FALSE, TRUE, FALSE]) == [FALSE, FALSE, TRUE] def test_drop(vm): assert vm.run([ - Opcode.DROP(1), - Opcode.RETURN(1) + isa.DROP(1), + isa.RETURN(1) ], stack=[TRUE, FALSE]) == [TRUE] def test_dup_too_many(vm): with pytest.raises(InterpreterError): - vm.run([Opcode.DUP(1)]) + vm.run([isa.DUP(1)]) with pytest.raises(InterpreterError): - vm.run([Opcode.FALSE(), Opcode.DUP(2)]) + vm.run([isa.DUP(2)], stack=[FALSE]) def test_rot_too_many(vm): with pytest.raises(InterpreterError): - vm.run([Opcode.ROT(1)]) + vm.run([isa.ROT(1)]) with pytest.raises(InterpreterError): - vm.run([Opcode.TRUE(), Opcode.ROT(2)]) + vm.run([isa.ROT(2)], stack=[FALSE]) def test_drop_too_many(vm): with pytest.raises(InterpreterError): - vm.run([Opcode.DROP(1)]) + vm.run([isa.DROP(1)]) with pytest.raises(InterpreterError): - vm.run([Opcode.TRUE(), Opcode.DROP(2)]) + vm.run([isa.DROP(2)], stack=[FALSE]) diff --git a/projects/shoggoth/test/python/ichor/test_state.py b/projects/shoggoth/test/python/ichor/test_state.py index dffadb0..6645618 100644 --- a/projects/shoggoth/test/python/ichor/test_state.py +++ b/projects/shoggoth/test/python/ichor/test_state.py @@ -70,3 +70,15 @@ def test_stackframe_slot(frame): frame.slot(2) assert frame.pop() == 2 + + +def test_stackframe_rot(frame): + frame.push(0) + frame.push(1) + frame.push(2) + frame.push(3) + frame.push(4) + + frame.rot(2) + assert frame.pop() == 3 + assert frame.pop() == 4