Shuffling a bunch of stuff around

This commit is contained in:
Reid 'arrdem' McKenzie 2022-06-13 20:23:43 -06:00
parent bc7cb3e909
commit 8feb3f3989
5 changed files with 63 additions and 140 deletions

View file

@ -1,5 +1,12 @@
# noqa #!/usr/bin/env python
from .bootstrap import * # noqa """
from .impl import * # noqa Import everything out of the Ichor modules.
from .isa import * # noqa
FIXME: A real public API here would be great, but somewhat un-pythonic.
"""
from ichor.bootstrap import * # noqa
from ichor.impl import * # noqa
from ichor.isa import * # noqa
from ichor.typing import * # noqa

View file

@ -4,8 +4,8 @@ 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 .isa import Module, Opcode from ichor.isa import Opcode
from .typing import ProductExpr, SumExpr from ichor.state import Module
BOOTSTRAP = Module() BOOTSTRAP = Module()

View file

@ -9,16 +9,10 @@ context (a virtual machine) which DOES have an easily introspected and serialize
""" """
import sys
from ichor.typing import Identifier
assert sys.version_info > (3, 10, 0), "`match` support is required"
from copy import deepcopy from copy import deepcopy
from .isa import Closure, FunctionRef, Opcode, Identifier from ichor.isa import Opcode
from ichor.typing import Closure, FunctionRef, Identifier
def rotate(l): def rotate(l):
@ -39,7 +33,7 @@ class Stackframe(object):
def pop(self): def pop(self):
return self.stack.pop(0) return self.stack.pop(0)
def call(self, signature: FunctionRef, ip): def call(self, signature: FunctionRef, ip) -> "Stackframe":
self.ip += 1 self.ip += 1
nargs = len(signature.args) nargs = len(signature.args)
args, self.stack = self.stack[:nargs], self.stack[nargs:] args, self.stack = self.stack[:nargs], self.stack[nargs:]
@ -51,7 +45,7 @@ class Stackframe(object):
depth=self.depth+1 depth=self.depth+1
) )
def ret(self, nargs): def ret(self, nargs) -> "Stackframe":
self.parent.stack = self.stack[:nargs] + self.parent.stack self.parent.stack = self.stack[:nargs] + self.parent.stack
return self.parent return self.parent
@ -88,95 +82,95 @@ class Interpreter(object):
def run(self, opcodes, stack=[]): def run(self, opcodes, stack=[]):
"""Directly interpret some opcodes in the configured environment.""" """Directly interpret some opcodes in the configured environment."""
stack = Stackframe(stack=stack) stackframe = Stackframe(stack=stack)
mod = self.bootstrap.copy() mod = self.bootstrap.copy()
stack.ip = mod.functions[mod.define_function(";<main>;;", opcodes)] stackframe.ip = mod.functions[mod.define_function(";<main>;;", opcodes)]
print(mod) print(mod)
def _error(msg=None): def _error(msg=None):
# Note this is pretty expensive because we have to snapshot the stack BEFORE we do anything # Note this is pretty expensive because we have to snapshot the stack BEFORE we do anything
# And the stack object isn't immutable or otherwise designed for cheap snapshotting # And the stack object isn't immutable or otherwise designed for cheap snapshotting
raise InterpreterError(mod, deepcopy(stack), msg) raise InterpreterError(mod, deepcopy(stackframe), msg)
while True: while True:
op = mod.opcodes[stack.ip] op = mod.opcodes[stackframe.ip]
print("{0}{1: <50} {2}: {3}".format(" " * stack.depth, str(stack.stack), stack.ip, op)) print("{0}{1: <50} {2}: {3}".format(" " * stackframe.depth, str(stackframe.stack), stackframe.ip, op))
match op: match op:
case Opcode.TRUE(): case Opcode.TRUE():
stack.push(True) stackframe.push(True)
case Opcode.FALSE(): case Opcode.FALSE():
stack.push(False) stackframe.push(False)
case Opcode.IF(target): case Opcode.IF(target):
if len(stack) < 1: if len(stackframe) < 1:
_error("Stack size violation") _error("Stack size violation")
val = stack.pop() val = stackframe.pop()
if val not in [True, False]: if val not in [True, False]:
_error("Type violation") _error("Type violation")
if val is False: if val is False:
stack.ip = target stackframe.ip = target
continue continue
case Opcode.GOTO(n): case Opcode.GOTO(n):
if (n < 0): if (n < 0):
_error("Illegal branch target") _error("Illegal branch target")
stack.ip = n stackframe.ip = n
continue continue
case Opcode.DUP(n): case Opcode.DUP(n):
if (n > len(stack)): if (n > len(stackframe)):
_error("Stack size violation") _error("Stack size violation")
stack.dup(n) stackframe.dup(n)
case Opcode.ROT(n): case Opcode.ROT(n):
if (n > len(stack)): if (n > len(stackframe)):
_error("Stack size violation") _error("Stack size violation")
stack.rot(n) stackframe.rot(n)
case Opcode.DROP(n): case Opcode.DROP(n):
if (n > len(stack)): if (n > len(stackframe)):
_error("Stack size violation") _error("Stack size violation")
stack.drop(n) stackframe.drop(n)
case Opcode.SLOT(n): case Opcode.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(stack.stack) - 1): if (n > len(stackframe.stack) - 1):
_error("SLOT reference out of range") _error("SLOT reference out of range")
stack.push(stack.stack[len(stack) - n - 1]) stackframe.push(stackframe.stack[len(stackframe) - n - 1])
case Opcode.IDENTIFIERC(name): case Opcode.IDENTIFIERC(name):
if not (name in mod.functions or name in mod.types): if not (name in mod.functions or name in mod.types):
_error("IDENTIFIERC references unknown entity") _error("IDENTIFIERC references unknown entity")
stack.push(Identifier(name)) stackframe.push(Identifier(name))
case Opcode.FUNREF(): case Opcode.FUNREF():
id = stack.pop() id = stackframe.pop()
if not isinstance(id, Identifier): if not isinstance(id, Identifier):
_error("FUNREF consumes an IDENTIFIER") _error("FUNREF consumes an IDENTIFIER")
try: try:
# FIXME: Verify this statically # FIXME: Verify this statically
stack.push(FunctionRef.parse(id.name)) stackframe.push(FunctionRef.parse(id.name))
except: except:
_error("Invalid function ref") _error("Invalid function ref")
case Opcode.CALLF(n): case Opcode.CALLF(n):
sig = stack.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")
if n != len(sig.args): if n != len(sig.args):
_error("CALLF target violation; argument count missmatch") _error("CALLF target violation; argument count missmatch")
if n > len(stack): if n > len(stackframe):
_error("Stack size violation") _error("Stack size violation")
try: try:
@ -184,69 +178,69 @@ class Interpreter(object):
except KeyError: except KeyError:
_error("Unknown target") _error("Unknown target")
stack = stack.call(sig, ip) stackframe = stackframe.call(sig, ip)
continue continue
case Opcode.RETURN(n): case Opcode.RETURN(n):
if (n > len(stack)): if (n > len(stackframe)):
_error("Stack size violation") _error("Stack size violation")
if stack.depth == 0: if stackframe.depth == 0:
return stack[:n] return stackframe[:n]
sig = FunctionRef.parse(stack.name) sig = FunctionRef.parse(stackframe.name)
if (len(sig.ret) != n): if (len(sig.ret) != n):
_error("Signature violation") _error("Signature violation")
stack = stack.ret(n) stackframe = stackframe.ret(n)
continue continue
case Opcode.CLOSUREF(n): case Opcode.CLOSUREF(n):
sig = stack.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")
if not n <= len(sig.args): if not n <= len(sig.args):
_error("CLOSUREF target violation; too many parameters provided") _error("CLOSUREF target violation; too many parameters provided")
if n > len(stack): if n > len(stackframe):
_error("Stack size violation") _error("Stack size violation")
c = Closure( c = Closure(
sig, sig,
stack.stack[:n] stackframe.stack[:n]
) )
stack.drop(n) stackframe.drop(n)
stack.push(c) stackframe.push(c)
case Opcode.CLOSUREC(n): case Opcode.CLOSUREC(n):
c = stack.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")
if n + len(c.frag) > len(c.funref.args): if n + len(c.frag) > len(c.funref.args):
_error("CLOSUREC target violation; too many parameters provided") _error("CLOSUREC target violation; too many parameters provided")
if n > len(stack): if n > len(stackframe):
_error("Stack size violation") _error("Stack size violation")
c = Closure( c = Closure(
c.funref, c.funref,
stack.stack[:n] + c.frag stackframe.stack[:n] + c.frag
) )
stack.drop(n) stackframe.drop(n)
stack.push(c) stackframe.push(c)
case Opcode.CALLC(n): case Opcode.CALLC(n):
c = stack.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")
if n + len(c.frag) != len(c.funref.args): if n + len(c.frag) != len(c.funref.args):
_error("CALLC target vionation; argument count missmatch") _error("CALLC target vionation; argument count missmatch")
if n > len(stack): if n > len(stackframe):
_error("Stack size violation") _error("Stack size violation")
# Extract the function signature # Extract the function signature
sig = c.funref sig = c.funref
# Push the closure's stack fragment # Push the closure's stack fragment
stack.stack = c.frag + stack.stack stackframe.stack = c.frag + stackframe.stack
# Perform a "normal" funref call # Perform a "normal" funref call
try: try:
@ -254,28 +248,10 @@ class Interpreter(object):
except KeyError: except KeyError:
_error("Unknown target") _error("Unknown target")
stack = stack.call(sig, ip) stackframe = stackframe.call(sig, ip)
continue continue
case Opcode.STRUCT(structref, n):
if not (t := module.types.get(structref)):
_error(f"STRUCT must reference a known type, {structref!r} is undefined")
if n > len(stack):
_error("Stack size violation")
args = stack[:n]
stack.drop(n)
# FIXME: typecheck
for arg, ftype in zip(args, t.children):
pass
inst = Struct(structref, [], dict(zip(t.field_names, args)))
stack.push(inst)
case _: case _:
raise Exception(f"Unhandled interpreter state {op}") raise Exception(f"Unhandled interpreter state {op}")
stack.ip += 1 stackframe.ip += 1

View file

@ -3,8 +3,6 @@
import typing as t import typing as t
from .typing import *
class Opcode: class Opcode:
#################################################################################################### ####################################################################################################
@ -231,61 +229,3 @@ class Opcode:
class BREAK(t.NamedTuple): class BREAK(t.NamedTuple):
"""Abort the interpreter.""" """Abort the interpreter."""
pass pass
class Module(t.NamedTuple):
opcodes: list = []
functions: dict = {}
types: dict = {}
constants: dict = {}
def copy(self):
return Module(
self.opcodes.copy(),
self.functions.copy(),
self.types.copy(),
self.constants.copy(),
)
@staticmethod
def translate(start: int, end: int, i: "Opcode"):
# FIXME: Consolidate bounds checks somehow
match i:
case Opcode.IF(t):
d = t + start
assert start <= d < end
return Opcode.IF(d)
case Opcode.GOTO(t):
d = t + start
assert start <= d < end
return Opcode.GOTO(d)
case _:
return i
def define_function(self, name, opcodes):
try:
sig = FunctionRef.parse(name)
assert sig.name
except:
raise ValueError("Illegal name provided")
start = len(self.opcodes)
self.functions[name] = start
for op in opcodes:
self.opcodes.append(self.translate(start, start + len(opcodes), op))
return name
def define_type(self, name, signature):
self.types[name] = signature
return name
def __str__(self):
b = []
marks = {v: k for k, v in self.functions.items()}
for i, o in zip(range(1<<64), self.opcodes):
if(i in marks):
b.append(f"{marks[i]}:")
b.append(f"{i: >10}: {o}")
return "\n".join(b)

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from ichor import * from ichor import Interpreter, BOOTSTRAP
import pytest import pytest