Refactor to make Opcode the base class

This commit is contained in:
Reid 'arrdem' McKenzie 2022-07-15 19:33:32 -06:00
parent 7e7c557dea
commit eb128d8b54
9 changed files with 358 additions and 269 deletions

View file

@ -3,33 +3,42 @@
from dataclasses import dataclass from dataclasses import dataclass
from random import choices from random import choices
from string import ascii_lowercase, digits 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 def gensym(prefix = None) -> isa.Label:
class Label(object): frag = ''.join(choices(ascii_lowercase + digits, k=8))
name: str return isa.Label(f"{prefix or 'gensym'}_{frag}")
def __hash__(self):
return hash(self.name)
class FuncBuilder(object): class FuncBuilder(object):
def __init__(self) -> None: def __init__(self) -> None:
self._opcodes = [] self._opcodes = []
def write(self, op: Opcode): def _write(self, op):
self._opcodes.append(op) self._opcodes.append(op)
def make_label(self, prefix=""): def write(self, op: Union[isa.Opcode, isa.Label, Sequence[isa.Opcode]]):
frag = ''.join(choices(ascii_lowercase + digits, k=8))
return Label(f"{prefix or 'gensym'}_{frag}")
def set_label(self, label: Label): def flatten(o):
self._opcodes.append(label) 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.""" """Assemble the written body into fully resolved opcodes."""
# The trivial two-pass assembler. First pass removes labels from the # The trivial two-pass assembler. First pass removes labels from the
@ -39,8 +48,8 @@ class FuncBuilder(object):
unassembled = [] unassembled = []
for op in self._opcodes: for op in self._opcodes:
match op: match op:
case Label(_) as l: case isa.Label(_) as l:
assert l not in labels # Label marks must be unique. assert l not in labels # isa.Label marks must be unique.
labels[l] = len(unassembled) labels[l] = len(unassembled)
case o: case o:
unassembled.append(o) unassembled.append(o)
@ -50,13 +59,36 @@ class FuncBuilder(object):
assembled = [] assembled = []
for op in unassembled: for op in unassembled:
match op: match op:
case Opcode.GOTO(Label(_) as l): case isa.GOTO(isa.Label(_) as l):
assembled.append(Opcode.GOTO(labels[l])) assembled.append(isa.GOTO(labels[l]))
case Opcode.VTEST(Label(_) as l): case isa.VTEST(isa.Label(_) as l):
assembled.append(Opcode.VTEST(labels[l])) assembled.append(isa.VTEST(labels[l]))
case o: case o:
assembled.append(o) assembled.append(o)
return assembled 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

View file

@ -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. 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.state import Module, Variant
from ichor.assembler import FuncBuilder from ichor.assembler import FuncBuilder
@ -22,59 +22,59 @@ NOT1 = BOOTSTRAP.define_function(
f";not;{BOOL};{BOOL}", f";not;{BOOL};{BOOL}",
[ [
# a: Bool # a: Bool
Opcode.IDENTIFIERC("bool"), isa.IDENTIFIERC("bool"),
Opcode.TYPEREF(), # <typeref bool> a isa.TYPEREF(), # <typeref bool> a
Opcode.DUP(), isa.DUP(),
Opcode.IDENTIFIERC("true"), isa.IDENTIFIERC("true"),
Opcode.VARIANTREF(), # <variantref true:bool> <typeref bool> a isa.VARIANTREF(), # <variantref true:bool> <typeref bool> a
Opcode.DUP(), isa.DUP(),
Opcode.SLOT(0), isa.SLOT(0),
Opcode.ROT(2), isa.ROT(2),
Opcode.VTEST(11), isa.VTEST(11),
Opcode.VARIANT(0), isa.VARIANT(0),
Opcode.RETURN(1), isa.RETURN(1),
Opcode.DROP(1), isa.DROP(1),
Opcode.IDENTIFIERC("false"), isa.IDENTIFIERC("false"),
Opcode.VARIANTREF(), isa.VARIANTREF(),
Opcode.VARIANT(0), isa.VARIANT(0),
Opcode.RETURN(1), isa.RETURN(1),
], ],
) )
OR2 = BOOTSTRAP.define_function( OR2 = BOOTSTRAP.define_function(
f";or;{BOOL},{BOOL};{BOOL}", f";or;{BOOL},{BOOL};{BOOL}",
[ [
Opcode.BREAK(), isa.BREAK(),
], ],
) )
OR3 = BOOTSTRAP.define_function( OR3 = BOOTSTRAP.define_function(
f";or;{BOOL},{BOOL},{BOOL};{BOOL}", f";or;{BOOL},{BOOL},{BOOL};{BOOL}",
[ [
Opcode.BREAK(), isa.BREAK(),
] ]
) )
AND2 = BOOTSTRAP.define_function( AND2 = BOOTSTRAP.define_function(
f";and;{BOOL},{BOOL};{BOOL}", f";and;{BOOL},{BOOL};{BOOL}",
[ [
Opcode.BREAK(), isa.BREAK(),
], ],
) )
AND3 = BOOTSTRAP.define_function( AND3 = BOOTSTRAP.define_function(
f";and;{BOOL},{BOOL},{BOOL};{BOOL}", f";and;{BOOL},{BOOL},{BOOL};{BOOL}",
[ [
Opcode.BREAK(), isa.BREAK(),
], ],
) )
XOR2 = BOOTSTRAP.define_function( XOR2 = BOOTSTRAP.define_function(
f";xor;{BOOL},{BOOL};{BOOL}", f";xor;{BOOL},{BOOL};{BOOL}",
[ [
Opcode.BREAK(), isa.BREAK(),
], ],
) )
@ -82,6 +82,6 @@ XOR3 = BOOTSTRAP.define_function(
f";xor;{BOOL},{BOOL},{BOOL};{BOOL}", f";xor;{BOOL},{BOOL},{BOOL};{BOOL}",
[ [
# A^B|B^C # A^B|B^C
Opcode.BREAK(), isa.BREAK(),
] ]
) )

View file

@ -13,7 +13,7 @@ from copy import deepcopy
import typing as t import typing as t
from textwrap import indent 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 from ichor.state import Closure, FunctionRef, Identifier, Module, Function, Type, TypeRef, VariantRef, Variant, Stackframe
@ -52,7 +52,7 @@ class Interpreter(object):
b = [] b = []
b.append(f"clock {clock}:") b.append(f"clock {clock}:")
b.append(" stack:") 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" {offset: <3} {it}")
b.append(f" op: {op}") b.append(f" op: {op}")
print(indent("\n".join(b), " " * stackframe.depth)) print(indent("\n".join(b), " " * stackframe.depth))
@ -64,7 +64,7 @@ class Interpreter(object):
clock += 1 clock += 1
match op: match op:
case Opcode.IDENTIFIERC(name): case isa.IDENTIFIERC(name):
if not (name in mod.functions if not (name in mod.functions
or name in mod.types or name in mod.types
or any(name in t.constructors for t in mod.types.values())): or any(name in t.constructors for t in mod.types.values())):
@ -72,7 +72,7 @@ class Interpreter(object):
stackframe.push(Identifier(name)) stackframe.push(Identifier(name))
case Opcode.TYPEREF(): case isa.TYPEREF():
id = stackframe.pop() id = stackframe.pop()
if not isinstance(id, Identifier): if not isinstance(id, Identifier):
_error("TYPEREF consumes an identifier") _error("TYPEREF consumes an identifier")
@ -81,7 +81,7 @@ class Interpreter(object):
stackframe.push(TypeRef(id.name)) stackframe.push(TypeRef(id.name))
case Opcode.VARIANTREF(): case isa.VARIANTREF():
id: Identifier = stackframe.pop() id: Identifier = stackframe.pop()
if not isinstance(id, Identifier): if not isinstance(id, Identifier):
_error("VARIANTREF consumes an identifier and a typeref") _error("VARIANTREF consumes an identifier and a typeref")
@ -96,7 +96,7 @@ class Interpreter(object):
stackframe.push(VariantRef(t, id.name)) stackframe.push(VariantRef(t, id.name))
case Opcode.VARIANT(n): case isa.VARIANT(n):
armref: VariantRef = stackframe.pop() armref: VariantRef = stackframe.pop()
if not isinstance(armref, VariantRef): if not isinstance(armref, VariantRef):
_error("VARIANT must be given a valid constructor reference") _error("VARIANT must be given a valid constructor reference")
@ -114,7 +114,7 @@ class Interpreter(object):
stackframe.drop(n) stackframe.drop(n)
stackframe.push(v) stackframe.push(v)
case Opcode.VTEST(n): case isa.VTEST(n):
armref: VariantRef = stackframe.pop() armref: VariantRef = stackframe.pop()
if not isinstance(armref, VariantRef): if not isinstance(armref, VariantRef):
_error("VTEST must be given a variant reference") _error("VTEST must be given a variant reference")
@ -127,38 +127,38 @@ class Interpreter(object):
stackframe.goto(n) stackframe.goto(n)
continue continue
case Opcode.GOTO(n): case isa.GOTO(n):
if (n < 0): if (n < 0):
_error("Illegal branch target") _error("Illegal branch target")
stackframe.goto(n) stackframe.goto(n)
continue continue
case Opcode.DUP(n): case isa.DUP(n):
if (n > len(stackframe)): if (n > len(stackframe)):
_error("Stack size violation") _error("Stack size violation")
stackframe.dup(n) stackframe.dup(n)
case Opcode.ROT(n): case isa.ROT(n):
if (n > len(stackframe)): if (n > len(stackframe)):
_error("Stack size violation") _error("Stack size violation")
stackframe.rot(n) stackframe.rot(n)
case Opcode.DROP(n): case isa.DROP(n):
if (n > len(stackframe)): if (n > len(stackframe)):
_error("Stack size violation") _error("Stack size violation")
stackframe.drop(n) stackframe.drop(n)
case Opcode.SLOT(n): case isa.SLOT(n):
if (n < 0): if (n < 0):
_error("SLOT must have a positive reference") _error("SLOT must have a positive reference")
if (n > len(stackframe) - 1): if (n > len(stackframe) - 1):
_error("SLOT reference out of range") _error("SLOT reference out of range")
stackframe.slot(n) stackframe.slot(n)
case Opcode.FUNREF(): case isa.FUNREF():
id = stackframe.pop() id = stackframe.pop()
if not isinstance(id, Identifier): if not isinstance(id, Identifier):
_error("FUNREF consumes an IDENTIFIER") _error("FUNREF consumes an IDENTIFIER")
@ -168,7 +168,7 @@ class Interpreter(object):
except: except:
_error("Invalid function ref") _error("Invalid function ref")
case Opcode.CALLF(n): case isa.CALLF(n):
sig = stackframe.pop() sig = stackframe.pop()
if not isinstance(sig, FunctionRef): if not isinstance(sig, FunctionRef):
_error("CALLF requires a funref at top of stack") _error("CALLF requires a funref at top of stack")
@ -186,7 +186,7 @@ class Interpreter(object):
stackframe = stackframe.call(fun, ip) stackframe = stackframe.call(fun, ip)
continue continue
case Opcode.RETURN(n): case isa.RETURN(n):
if (n > len(stackframe)): if (n > len(stackframe)):
_error("Stack size violation") _error("Stack size violation")
@ -199,7 +199,7 @@ class Interpreter(object):
stackframe = stackframe.ret(n) stackframe = stackframe.ret(n)
continue continue
case Opcode.CLOSUREF(n): case isa.CLOSUREF(n):
sig = stackframe.pop() sig = stackframe.pop()
if not isinstance(sig, FunctionRef): if not isinstance(sig, FunctionRef):
_error("CLOSUREF requires a funref at top of stack") _error("CLOSUREF requires a funref at top of stack")
@ -216,7 +216,7 @@ class Interpreter(object):
stackframe.drop(n) stackframe.drop(n)
stackframe.push(c) stackframe.push(c)
case Opcode.CLOSUREC(n): case isa.CLOSUREC(n):
c = stackframe.pop() c = stackframe.pop()
if not isinstance(c, Closure): if not isinstance(c, Closure):
_error("CLOSUREC requires a closure at top of stack") _error("CLOSUREC requires a closure at top of stack")
@ -233,7 +233,7 @@ class Interpreter(object):
stackframe.drop(n) stackframe.drop(n)
stackframe.push(c) stackframe.push(c)
case Opcode.CALLC(n): case isa.CALLC(n):
c = stackframe.pop() c = stackframe.pop()
if not isinstance(c, Closure): if not isinstance(c, Closure):
_error("CALLC requires a closure at top of stack") _error("CALLC requires a closure at top of stack")
@ -246,7 +246,7 @@ class Interpreter(object):
# Extract the function signature # Extract the function signature
# Push the closure's stack fragment # Push the closure's stack fragment
stackframe._stack = stackframe._stack.extendleft(c.frag) stackframe._stack = c.frag + stackframe._stack
# Perform a "normal" funref call # Perform a "normal" funref call
try: try:

View file

@ -1,205 +1,242 @@
"""The instruction set for Shogoth.""" """The instruction set for Shogoth."""
from abc import ABC
from dataclasses import dataclass
import typing as t import typing as t
class Opcode: @dataclass
# Note that there's no IF, TRUE or FALSE required if bool is a builtin. 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
#################################################################################################### @dataclass
# Stack manipulation class GOTO(Opcode):
#################################################################################################### """() -> ()
# 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 DUP(t.NamedTuple): Branch to another point within the same bytecode segment. The target MUST be within the same module range as the
"""(A, B, ...) -> (A, B, ...) 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): @dataclass
"""(A, B, ... Z) -> (Z, A, B, ...) 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): nargs: int = 2
"""(..., A) -> (A, ..., A)
Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack. @dataclass
Intended to allow users to emulate (immutable) frame local slots for reused values. class DROP(Opcode):
"""(*) -> ()
""" Drop the top N items of the stack.
target: int """
#################################################################################################### nargs: int = 1
# Functional abstraction
####################################################################################################
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. Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack.
They are a VM-internal naming structure with reference to the module. 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): Identifiers name functions, fields and types but are not strings.
"""(`FUNREF<... A to ... B>`, ... A) -> (... B) 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. val: str
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.
""" @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 @dataclass
appended to theirs. All other values on the stack will be discarded. 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): @dataclass
"""(`FUNREF<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`) 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, ... B to ... C>`, 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, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
class CALLC(t.NamedTuple): Construct a closure over the function reference at the top of the stack. This may produce nullary closures.
"""(`CLOSURE<... A to ... B>`, ... A) -> (... B)
Call [closure] """
Make a dynamic call to the closure at the top of stack. nargs: int = 0
The callee will see a stack containg only the provided `nargs` and closed-overs.
A subsequent RETURN will return execution to the next point.
Executing a `CALL` pushes the name and module path of the current function.
""" @dataclass
class CLOSUREC(Opcode):
"""(`CLOSURE<A, ... B to ... C>`, 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): Make a dynamic call to the closure at the top of stack.
"""(IDENTIFIER, TYPEREF) -> (VARIANTREF) 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): nargs: int = 0
"""(VARIANTREF<a ⊢ A ⊂ B>, ...) -> (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. # Structures
####################################################################################################
The name and module path of the current function MUST match the name and module path of `VARIANTREF`. # FIXME: This lacks any sort of way to do dynamic type/field references
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 TYPEREF(Opcode):
"""(IDENTIFIER) -> (TYPEREF)
class VTEST(t.NamedTuple): Produces a TYPEREF to the type named by the provided IDENTIFIER.
"""(VARIANTREF<a ⊢ A ⊂ B>, 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 VARIANTREF(Opcode):
"""(IDENTIFIER, TYPEREF) -> (VARIANTREF)
class VLOAD(t.NamedTuple): Produce a VARIANTREF to an 'arm' of the given variant type.
"""(VARIANTREF<a ⊢ A ⊂ B>, 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.
"""
class BREAK(t.NamedTuple): @dataclass
"""Abort the interpreter.""" class VARIANT(Opcode):
pass """(VARIANTREF<a ⊢ A ⊂ B>, ...) -> (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<a ⊢ A ⊂ B>, 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<a ⊢ A ⊂ B>, 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

View file

@ -4,7 +4,7 @@
import typing as t import typing as t
from ichor.isa import Opcode from ichor import isa
from pyrsistent import pdeque, PDeque from pyrsistent import pdeque, PDeque
from lark import Lark, Transformer, v_args, Token from lark import Lark, Transformer, v_args, Token
@ -77,13 +77,13 @@ class Function(t.NamedTuple):
name: str name: str
arguments: t.List[str] arguments: t.List[str]
returns: t.List[str] returns: t.List[str]
instructions: t.List[Opcode] instructions: t.List[isa.Opcode]
typevars: t.List[t.Any] = [] typevars: t.List[t.Any] = []
typeconstraints: t.List[t.Any] = [] typeconstraints: t.List[t.Any] = []
metadata: dict = {} metadata: dict = {}
@classmethod @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) constraints, name, args, rets = FUNC.parse(name)
# FIXME: Constraints probably needs some massaging # FIXME: Constraints probably needs some massaging
# FIXME: Need to get typevars from somewhere # FIXME: Need to get typevars from somewhere
@ -188,18 +188,18 @@ class Module(t.NamedTuple):
) )
@staticmethod @staticmethod
def translate(start: int, end: int, i: Opcode): def translate(start: int, end: int, i: isa.Opcode):
# FIXME: Consolidate bounds checks somehow # FIXME: Consolidate bounds checks somehow
match i: match i:
case Opcode.VTEST(t): case isa.VTEST(t):
d = t + start d = t + start
assert start <= d < end assert start <= d < end
return Opcode.VTEST(d) return isa.VTEST(d)
case Opcode.GOTO(t): case isa.GOTO(t):
d = t + start d = t + start
assert start <= d < end assert start <= d < end
return Opcode.GOTO(d) return isa.GOTO(d)
case _: case _:
return i return i
@ -279,7 +279,10 @@ class Stackframe(object):
self._stack = self._stack[:-nargs] self._stack = self._stack[:-nargs]
def rot(self, 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): def slot(self, n):
self.push(self._stack[n]) self.push(self._stack[n])

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from ichor import FuncBuilder, Opcode from ichor import FuncBuilder, isa
import pytest import pytest
@ -11,37 +11,37 @@ def builder() -> FuncBuilder:
def test_forwards_label(builder: FuncBuilder): def test_forwards_label(builder: FuncBuilder):
l = builder.make_label() l = builder.make_label()
builder.write(Opcode.GOTO(l)) builder.write(isa.GOTO(l))
builder.write(Opcode.DROP(0)) # no-op builder.write(isa.DROP(0)) # no-op
builder.set_label(l) builder.write(l)
builder.write(Opcode.DROP(0)) # no-op builder.write(isa.DROP(0)) # no-op
instrs = builder.build() instrs = builder.build()
assert instrs == [ assert instrs == [
Opcode.GOTO(2), isa.GOTO(2),
Opcode.DROP(0), isa.DROP(0),
Opcode.DROP(0), isa.DROP(0),
] ]
def test_backwards_label(builder: FuncBuilder): def test_backwards_label(builder: FuncBuilder):
l = builder.make_label() l = builder.make_label()
builder.set_label(l) builder.write(l)
builder.write(Opcode.DROP(0)) # no-op builder.write(isa.DROP(0)) # no-op
builder.write(Opcode.GOTO(l)) builder.write(isa.GOTO(l))
instrs = builder.build() instrs = builder.build()
assert instrs == [ assert instrs == [
Opcode.DROP(0), isa.DROP(0),
Opcode.GOTO(0), isa.GOTO(0),
] ]
def test_self_label(builder: FuncBuilder): def test_self_label(builder: FuncBuilder):
l = builder.make_label() l = builder.make_label()
builder.write(Opcode.DROP(0)) # no-op builder.write(isa.DROP(0)) # no-op
builder.set_label(l) builder.write(l)
builder.write(Opcode.GOTO(l)) builder.write(isa.GOTO(l))
instrs = builder.build() instrs = builder.build()
assert instrs == [ assert instrs == [
Opcode.DROP(0), isa.DROP(0),
Opcode.GOTO(1), isa.GOTO(1),
] ]

View file

@ -2,7 +2,7 @@
from .fixtures import * # noqa from .fixtures import * # noqa
from ichor import * from ichor import isa, TRUE, FALSE, NOT1
import pytest import pytest
@ -12,10 +12,10 @@ import pytest
]) ])
def test_not(vm, stack, ret): def test_not(vm, stack, ret):
assert vm.run([ assert vm.run([
Opcode.IDENTIFIERC(NOT1), isa.IDENTIFIERC(NOT1),
Opcode.FUNREF(), isa.FUNREF(),
Opcode.CALLF(1), isa.CALLF(1),
Opcode.RETURN(1) isa.RETURN(1)
], stack = stack) == ret ], stack = stack) == ret
@ -27,10 +27,10 @@ def test_not(vm, stack, ret):
# ]) # ])
# def test_or(vm, stack, ret): # def test_or(vm, stack, ret):
# assert vm.run([ # assert vm.run([
# Opcode.IDENTIFIERC(OR2), # isa.IDENTIFIERC(OR2),
# Opcode.FUNREF(), # isa.FUNREF(),
# Opcode.CALLF(2), # isa.CALLF(2),
# Opcode.RETURN(1) # isa.RETURN(1)
# ], stack = stack) == ret # ], stack = stack) == ret
@ -42,10 +42,10 @@ def test_not(vm, stack, ret):
# ]) # ])
# def test_and(vm, stack, ret): # def test_and(vm, stack, ret):
# assert vm.run([ # assert vm.run([
# Opcode.IDENTIFIERC(AND2), # isa.IDENTIFIERC(AND2),
# Opcode.FUNREF(), # isa.FUNREF(),
# Opcode.CALLF(2), # isa.CALLF(2),
# Opcode.RETURN(1) # isa.RETURN(1)
# ], stack = stack) == ret # ], stack = stack) == ret
@ -57,10 +57,10 @@ def test_not(vm, stack, ret):
# ]) # ])
# def test_xor2(vm, stack, ret): # def test_xor2(vm, stack, ret):
# assert vm.run([ # assert vm.run([
# Opcode.IDENTIFIERC(XOR2), # isa.IDENTIFIERC(XOR2),
# Opcode.FUNREF(), # isa.FUNREF(),
# Opcode.CALLF(2), # isa.CALLF(2),
# Opcode.RETURN(1) # isa.RETURN(1)
# ], stack = stack) == ret # ], stack = stack) == ret
@ -75,10 +75,10 @@ def test_not(vm, stack, ret):
# ]) # ])
# def test_xor3(vm, stack, ret): # def test_xor3(vm, stack, ret):
# assert vm.run([ # assert vm.run([
# Opcode.IDENTIFIERC(XOR3), # isa.IDENTIFIERC(XOR3),
# Opcode.FUNREF(), # isa.FUNREF(),
# Opcode.CALLF(3), # isa.CALLF(3),
# Opcode.RETURN(1) # isa.RETURN(1)
# ], stack = stack) == ret # ], stack = stack) == ret
@ -87,9 +87,9 @@ def test_not(vm, stack, ret):
# ]) # ])
# def test_funref(vm, stack, ret): # def test_funref(vm, stack, ret):
# assert vm.run([ # assert vm.run([
# Opcode.IDENTIFIERC(NOT1), # isa.IDENTIFIERC(NOT1),
# Opcode.FUNREF(), # isa.FUNREF(),
# Opcode.RETURN(1) # isa.RETURN(1)
# ], stack = stack) == ret # ], stack = stack) == ret
@ -98,10 +98,10 @@ def test_not(vm, stack, ret):
# ]) # ])
# def test_callf(vm, stack, ret): # def test_callf(vm, stack, ret):
# assert vm.run([ # assert vm.run([
# Opcode.IDENTIFIERC(NOT1), # isa.IDENTIFIERC(NOT1),
# Opcode.FUNREF(), # isa.FUNREF(),
# Opcode.CALLF(1), # isa.CALLF(1),
# Opcode.RETURN(1) # isa.RETURN(1)
# ], stack = stack) == ret # ], stack = stack) == ret
@ -113,11 +113,11 @@ def test_not(vm, stack, ret):
# ]) # ])
# def test_callc(vm, stack, ret): # def test_callc(vm, stack, ret):
# assert vm.run([ # assert vm.run([
# Opcode.IDENTIFIERC(XOR2), # isa.IDENTIFIERC(XOR2),
# Opcode.FUNREF(), # isa.FUNREF(),
# Opcode.CLOSUREF(1), # isa.CLOSUREF(1),
# Opcode.CALLC(1), # isa.CALLC(1),
# Opcode.RETURN(1), # isa.RETURN(1),
# ], stack = stack) == ret # ], stack = stack) == ret
@ -132,10 +132,10 @@ def test_not(vm, stack, ret):
# ]) # ])
# def test_closurec(vm, stack, ret): # def test_closurec(vm, stack, ret):
# assert vm.run([ # assert vm.run([
# Opcode.IDENTIFIERC(XOR3), # isa.IDENTIFIERC(XOR3),
# Opcode.FUNREF(), # isa.FUNREF(),
# Opcode.CLOSUREF(1), # isa.CLOSUREF(1),
# Opcode.CLOSUREC(1), # isa.CLOSUREC(1),
# Opcode.CALLC(1), # isa.CALLC(1),
# Opcode.RETURN(1), # isa.RETURN(1),
# ], stack = stack) == ret # ], stack = stack) == ret

View file

@ -4,59 +4,64 @@ Tests coverign the VM interpreter
from .fixtures import * # noqa from .fixtures import * # noqa
from ichor import * from ichor import isa, InterpreterError, TRUE, FALSE
import pytest import pytest
def test_return(vm): def test_return(vm):
assert vm.run([Opcode.RETURN(0)], stack=[TRUE, FALSE]) == [] assert vm.run([isa.RETURN(0)], stack=[TRUE, FALSE]) == []
assert vm.run([Opcode.RETURN(1)], stack=[TRUE, FALSE]) == [TRUE] assert vm.run([isa.RETURN(1)], stack=[TRUE, FALSE]) == [TRUE]
assert vm.run([Opcode.RETURN(2)], stack=[TRUE, FALSE]) == [TRUE, FALSE] assert vm.run([isa.RETURN(2)], stack=[TRUE, FALSE]) == [TRUE, FALSE]
def test_dup(vm): def test_dup(vm):
assert vm.run([Opcode.DUP(1), Opcode.RETURN(3)], stack=[FALSE, TRUE]) == [FALSE, TRUE, TRUE] assert vm.run([isa.DUP(1), isa.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(2), isa.RETURN(4)], stack=[FALSE, TRUE]) == [FALSE, TRUE, FALSE, TRUE]
def test_rot(vm): def test_rot(vm):
assert vm.run([ assert vm.run([
Opcode.ROT(2), isa.ROT(2),
Opcode.RETURN(2) isa.RETURN(2)
], stack=[FALSE, TRUE]) == [TRUE, FALSE] ], stack=[FALSE, TRUE]) == [TRUE, FALSE]
assert vm.run([ assert vm.run([
Opcode.ROT(3), isa.ROT(2),
Opcode.RETURN(3) 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] ], stack=[FALSE, TRUE, FALSE]) == [FALSE, FALSE, TRUE]
def test_drop(vm): def test_drop(vm):
assert vm.run([ assert vm.run([
Opcode.DROP(1), isa.DROP(1),
Opcode.RETURN(1) isa.RETURN(1)
], stack=[TRUE, FALSE]) == [TRUE] ], stack=[TRUE, FALSE]) == [TRUE]
def test_dup_too_many(vm): def test_dup_too_many(vm):
with pytest.raises(InterpreterError): with pytest.raises(InterpreterError):
vm.run([Opcode.DUP(1)]) vm.run([isa.DUP(1)])
with pytest.raises(InterpreterError): with pytest.raises(InterpreterError):
vm.run([Opcode.FALSE(), Opcode.DUP(2)]) vm.run([isa.DUP(2)], stack=[FALSE])
def test_rot_too_many(vm): def test_rot_too_many(vm):
with pytest.raises(InterpreterError): with pytest.raises(InterpreterError):
vm.run([Opcode.ROT(1)]) vm.run([isa.ROT(1)])
with pytest.raises(InterpreterError): with pytest.raises(InterpreterError):
vm.run([Opcode.TRUE(), Opcode.ROT(2)]) vm.run([isa.ROT(2)], stack=[FALSE])
def test_drop_too_many(vm): def test_drop_too_many(vm):
with pytest.raises(InterpreterError): with pytest.raises(InterpreterError):
vm.run([Opcode.DROP(1)]) vm.run([isa.DROP(1)])
with pytest.raises(InterpreterError): with pytest.raises(InterpreterError):
vm.run([Opcode.TRUE(), Opcode.DROP(2)]) vm.run([isa.DROP(2)], stack=[FALSE])

View file

@ -70,3 +70,15 @@ def test_stackframe_slot(frame):
frame.slot(2) frame.slot(2)
assert frame.pop() == 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