2022-05-12 06:21:22 +00:00
|
|
|
#!/usr/bin/env python3
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-06-01 01:25:18 +00:00
|
|
|
"""The Ichor VM implementation.
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-06-01 01:25:18 +00:00
|
|
|
The whole point of Shoggoth is that program executions are checkpointable and restartable. This requires that rather than
|
2022-06-01 01:04:14 +00:00
|
|
|
using a traditional recursive interpreter which is difficult to snapshot, interpretation in shoggoth occur within a
|
2022-03-29 07:29:18 +00:00
|
|
|
context (a virtual machine) which DOES have an easily introspected and serialized representation.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2022-05-12 06:21:22 +00:00
|
|
|
|
2022-04-12 07:49:12 +00:00
|
|
|
from copy import deepcopy
|
2022-06-15 07:46:32 +00:00
|
|
|
import typing as t
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-06-14 02:23:43 +00:00
|
|
|
from ichor.isa import Opcode
|
2022-06-16 16:55:18 +00:00
|
|
|
from ichor.state import Closure, FunctionRef, Identifier, Module, Function, Type, TypeRef, VariantRef, Variant
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
def rotate(l):
|
|
|
|
return [l[-1]] + l[:-1]
|
|
|
|
|
|
|
|
|
|
|
|
class Stackframe(object):
|
2022-06-15 07:46:32 +00:00
|
|
|
def __init__(self,
|
2022-06-15 15:21:43 +00:00
|
|
|
fun: Function,
|
|
|
|
ip: int,
|
|
|
|
stack: t.Optional[t.List[t.Any]] = None,
|
|
|
|
parent: t.Optional["Stackframe"] = None):
|
|
|
|
self._fun = fun
|
|
|
|
self._ip = ip
|
|
|
|
self._stack = stack or []
|
|
|
|
self._parent = parent
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
def push(self, obj):
|
2022-06-15 15:21:43 +00:00
|
|
|
self._stack.insert(0, obj)
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
def pop(self):
|
2022-06-15 15:21:43 +00:00
|
|
|
return self._stack.pop(0)
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-06-15 07:46:32 +00:00
|
|
|
def call(self, fun: Function, ip) -> "Stackframe":
|
2022-06-15 15:21:43 +00:00
|
|
|
assert isinstance(fun, Function)
|
|
|
|
assert isinstance(ip, int)
|
|
|
|
self._ip += 1
|
2022-06-15 07:46:32 +00:00
|
|
|
nargs = len(fun.arguments)
|
2022-06-15 15:21:43 +00:00
|
|
|
args, self._stack = self._stack[:nargs], self._stack[nargs:]
|
2022-03-29 07:29:18 +00:00
|
|
|
return Stackframe(
|
2022-06-15 15:21:43 +00:00
|
|
|
fun,
|
|
|
|
ip,
|
2022-06-01 05:08:29 +00:00
|
|
|
stack=args,
|
|
|
|
parent=self,
|
|
|
|
)
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-06-14 02:23:43 +00:00
|
|
|
def ret(self, nargs) -> "Stackframe":
|
2022-06-15 15:21:43 +00:00
|
|
|
assert nargs >= 0
|
|
|
|
assert isinstance(self._parent, Stackframe)
|
|
|
|
self._parent._stack = self._stack[:nargs] + self._parent._stack
|
|
|
|
return self._parent
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
def dup(self, nargs):
|
2022-06-15 15:21:43 +00:00
|
|
|
self._stack = self._stack[:nargs] + self._stack
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
def drop(self, nargs):
|
2022-06-15 15:21:43 +00:00
|
|
|
self._stack = self._stack[nargs:]
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
def rot(self, nargs):
|
2022-06-15 15:21:43 +00:00
|
|
|
self._stack = rotate(self._stack[:nargs]) + self._stack[nargs:]
|
|
|
|
|
|
|
|
def slot(self, n):
|
|
|
|
self.push(self._stack[len(self) - n - 1])
|
|
|
|
|
|
|
|
def goto(self, target: int):
|
|
|
|
self._ip = target
|
|
|
|
|
|
|
|
@property
|
|
|
|
def depth(self):
|
|
|
|
if self._parent == None:
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
return self._parent.depth + 1
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-04-12 07:49:12 +00:00
|
|
|
def __getitem__(self, key):
|
2022-06-15 15:21:43 +00:00
|
|
|
return self._stack.__getitem__(key)
|
2022-04-12 07:49:12 +00:00
|
|
|
|
|
|
|
def __len__(self):
|
2022-06-15 15:21:43 +00:00
|
|
|
return len(self._stack)
|
2022-04-12 07:49:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class InterpreterError(Exception):
|
|
|
|
"""An error raised by the interpreter when something goes awry."""
|
|
|
|
|
|
|
|
def __init__(self, module, stack, message=None):
|
|
|
|
self.module = module
|
|
|
|
self.stack = stack
|
|
|
|
super().__init__(message)
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
class Interpreter(object):
|
2022-04-12 07:49:12 +00:00
|
|
|
"""A shit simple instruction pointer based interpreter."""
|
2022-06-14 03:11:15 +00:00
|
|
|
def __init__(self, bootstrap_module: Module):
|
2022-03-29 07:29:18 +00:00
|
|
|
self.bootstrap = bootstrap_module
|
|
|
|
|
2022-04-15 06:31:58 +00:00
|
|
|
def run(self, opcodes, stack=[]):
|
2022-03-29 07:29:18 +00:00
|
|
|
"""Directly interpret some opcodes in the configured environment."""
|
|
|
|
|
|
|
|
mod = self.bootstrap.copy()
|
2022-06-15 15:21:43 +00:00
|
|
|
main = mod.define_function(";<main>;;", opcodes)
|
|
|
|
main_fun = mod.functions[main]
|
|
|
|
main_ip = mod.labels[main]
|
|
|
|
stackframe = Stackframe(main_fun, main_ip, stack)
|
2022-06-01 06:09:59 +00:00
|
|
|
|
|
|
|
print(mod)
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-04-12 07:49:12 +00:00
|
|
|
def _error(msg=None):
|
|
|
|
# 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
|
2022-06-14 02:23:43 +00:00
|
|
|
raise InterpreterError(mod, deepcopy(stackframe), msg)
|
2022-04-12 07:49:12 +00:00
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
while True:
|
2022-06-15 15:21:43 +00:00
|
|
|
op = mod.codepage[stackframe._ip]
|
|
|
|
print("{0}{1: <50} {2}: {3}".format(" " * stackframe.depth, str(stackframe._stack), stackframe._ip, op))
|
2022-06-01 05:08:29 +00:00
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
match op:
|
2022-06-16 16:55:18 +00:00
|
|
|
case Opcode.IDENTIFIERC(name):
|
|
|
|
if not (name in mod.functions or name in mod.types):
|
|
|
|
_error("IDENTIFIERC references unknown entity")
|
|
|
|
|
|
|
|
stackframe.push(Identifier(name))
|
|
|
|
|
|
|
|
case Opcode.TYPEREF():
|
|
|
|
id = stackframe.pop()
|
|
|
|
if not isinstance(id, Identifier):
|
|
|
|
_error("TYPEREF consumes an identifier")
|
|
|
|
if not id.name in mod.types:
|
|
|
|
_error("TYPEREF must be given a valid type identifier")
|
|
|
|
|
|
|
|
stackframe.push(TypeRef(id.name))
|
|
|
|
|
|
|
|
case Opcode.VARIANTREF():
|
|
|
|
id: Identifier = stackframe.pop()
|
|
|
|
if not isinstance(id, Identifier):
|
|
|
|
_error("VARIANTREF consumes an identifier and a typeref")
|
|
|
|
|
|
|
|
t: TypeRef = stackframe.pop()
|
|
|
|
if not isinstance(id, TypeRef):
|
|
|
|
_error("VARIANTREF consumes an identifier and a typeref")
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-06-16 16:55:18 +00:00
|
|
|
type = mod.types[t.name]
|
|
|
|
if id.name not in type.constructors:
|
|
|
|
_error(f"VARIANTREF given {id.name!r} which does not name a constructor within {type!r}")
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-06-16 16:55:18 +00:00
|
|
|
stackframe.push(VariantRef(t, id.name))
|
|
|
|
|
|
|
|
case Opcode.VARIANT(n):
|
|
|
|
armref: VariantRef = stackframe.pop()
|
|
|
|
if not isinstance(armref, VariantRef):
|
|
|
|
_error("VARIANT must be given a valid constructor reference")
|
|
|
|
|
|
|
|
ctor = mod.types[armref.type.name].constructors[armref.arm]
|
|
|
|
if n != len(ctor):
|
|
|
|
_error("VARIANT given n-args inconsistent with the type constructor")
|
|
|
|
|
|
|
|
if n > len(stackframe):
|
2022-04-12 07:49:12 +00:00
|
|
|
_error("Stack size violation")
|
|
|
|
|
2022-06-16 16:55:18 +00:00
|
|
|
# FIXME: Where does type variable to type binding occur?
|
|
|
|
# Certainly needs to be AT LEAST here, where we also need to be doing some typechecking
|
|
|
|
v = Variant(armref.type.name, armref.arm, tuple(stackframe._stack[:n]))
|
|
|
|
stackframe.drop(n)
|
|
|
|
stackframe.push(v)
|
|
|
|
|
|
|
|
case Opcode.VTEST(n):
|
|
|
|
armref: VariantRef = stackframe.pop()
|
|
|
|
if not isinstance(armref, VariantRef):
|
|
|
|
_error("VTEST must be given a variant reference")
|
|
|
|
|
|
|
|
inst: Variant = stackframe.pop()
|
|
|
|
if not isinstance(inst, Variant):
|
|
|
|
_error("VTEST must be given an instance of a variant")
|
2022-04-12 07:49:12 +00:00
|
|
|
|
2022-06-16 16:55:18 +00:00
|
|
|
if inst.type == armref.type.name and inst.variant == armref.arm:
|
|
|
|
stackframe.goto(n)
|
2022-03-29 07:29:18 +00:00
|
|
|
continue
|
|
|
|
|
2022-06-11 06:28:24 +00:00
|
|
|
case Opcode.GOTO(n):
|
|
|
|
if (n < 0):
|
|
|
|
_error("Illegal branch target")
|
2022-06-15 15:21:43 +00:00
|
|
|
stackframe.goto(n)
|
2022-06-11 06:28:24 +00:00
|
|
|
continue
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
case Opcode.DUP(n):
|
2022-06-14 02:23:43 +00:00
|
|
|
if (n > len(stackframe)):
|
2022-04-12 07:49:12 +00:00
|
|
|
_error("Stack size violation")
|
|
|
|
|
2022-06-14 02:23:43 +00:00
|
|
|
stackframe.dup(n)
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
case Opcode.ROT(n):
|
2022-06-14 02:23:43 +00:00
|
|
|
if (n > len(stackframe)):
|
2022-04-12 07:49:12 +00:00
|
|
|
_error("Stack size violation")
|
|
|
|
|
2022-06-14 02:23:43 +00:00
|
|
|
stackframe.rot(n)
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
case Opcode.DROP(n):
|
2022-06-14 02:23:43 +00:00
|
|
|
if (n > len(stackframe)):
|
2022-04-12 07:49:12 +00:00
|
|
|
_error("Stack size violation")
|
|
|
|
|
2022-06-14 02:23:43 +00:00
|
|
|
stackframe.drop(n)
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-06-11 06:28:24 +00:00
|
|
|
case Opcode.SLOT(n):
|
2022-04-12 07:49:12 +00:00
|
|
|
if (n < 0):
|
2022-06-11 06:28:24 +00:00
|
|
|
_error("SLOT must have a positive reference")
|
2022-06-15 15:21:43 +00:00
|
|
|
if (n > len(stackframe) - 1):
|
2022-06-11 06:28:24 +00:00
|
|
|
_error("SLOT reference out of range")
|
2022-06-15 15:21:43 +00:00
|
|
|
stackframe.slot(n)
|
2022-04-12 07:49:12 +00:00
|
|
|
|
2022-06-11 06:28:24 +00:00
|
|
|
case Opcode.FUNREF():
|
2022-06-14 02:23:43 +00:00
|
|
|
id = stackframe.pop()
|
2022-06-11 06:28:24 +00:00
|
|
|
if not isinstance(id, Identifier):
|
|
|
|
_error("FUNREF consumes an IDENTIFIER")
|
2022-04-21 06:17:59 +00:00
|
|
|
try:
|
|
|
|
# FIXME: Verify this statically
|
2022-06-14 02:23:43 +00:00
|
|
|
stackframe.push(FunctionRef.parse(id.name))
|
2022-04-21 06:17:59 +00:00
|
|
|
except:
|
|
|
|
_error("Invalid function ref")
|
|
|
|
|
|
|
|
case Opcode.CALLF(n):
|
2022-06-14 02:23:43 +00:00
|
|
|
sig = stackframe.pop()
|
2022-04-21 06:17:59 +00:00
|
|
|
if not isinstance(sig, FunctionRef):
|
|
|
|
_error("CALLF requires a funref at top of stack")
|
2022-06-15 07:46:32 +00:00
|
|
|
fun = mod.functions[sig.name]
|
|
|
|
if n != len(fun.arguments):
|
2022-06-01 05:54:11 +00:00
|
|
|
_error("CALLF target violation; argument count missmatch")
|
2022-06-14 02:23:43 +00:00
|
|
|
if n > len(stackframe):
|
2022-06-01 05:54:11 +00:00
|
|
|
_error("Stack size violation")
|
|
|
|
|
|
|
|
try:
|
2022-06-15 15:21:43 +00:00
|
|
|
ip = mod.labels[fun.signature]
|
2022-06-01 05:54:11 +00:00
|
|
|
except KeyError:
|
|
|
|
_error("Unknown target")
|
|
|
|
|
2022-06-15 07:46:32 +00:00
|
|
|
stackframe = stackframe.call(fun, ip)
|
2022-06-01 05:54:11 +00:00
|
|
|
continue
|
|
|
|
|
2022-06-11 06:28:24 +00:00
|
|
|
case Opcode.RETURN(n):
|
2022-06-14 02:23:43 +00:00
|
|
|
if (n > len(stackframe)):
|
2022-06-11 06:28:24 +00:00
|
|
|
_error("Stack size violation")
|
|
|
|
|
2022-06-14 02:23:43 +00:00
|
|
|
if stackframe.depth == 0:
|
|
|
|
return stackframe[:n]
|
2022-06-11 06:28:24 +00:00
|
|
|
|
2022-06-15 15:21:43 +00:00
|
|
|
if (len(stackframe._fun.returns) != n):
|
2022-06-11 06:28:24 +00:00
|
|
|
_error("Signature violation")
|
|
|
|
|
2022-06-14 02:23:43 +00:00
|
|
|
stackframe = stackframe.ret(n)
|
2022-06-11 06:28:24 +00:00
|
|
|
continue
|
|
|
|
|
2022-06-01 05:54:11 +00:00
|
|
|
case Opcode.CLOSUREF(n):
|
2022-06-14 02:23:43 +00:00
|
|
|
sig = stackframe.pop()
|
2022-06-01 05:54:11 +00:00
|
|
|
if not isinstance(sig, FunctionRef):
|
|
|
|
_error("CLOSUREF requires a funref at top of stack")
|
2022-06-15 15:21:43 +00:00
|
|
|
fun = mod.functions[sig.name]
|
|
|
|
if not n <= len(fun.arguments):
|
2022-06-01 05:54:11 +00:00
|
|
|
_error("CLOSUREF target violation; too many parameters provided")
|
2022-06-14 02:23:43 +00:00
|
|
|
if n > len(stackframe):
|
2022-04-21 06:17:59 +00:00
|
|
|
_error("Stack size violation")
|
|
|
|
|
2022-06-01 05:54:11 +00:00
|
|
|
c = Closure(
|
|
|
|
sig,
|
2022-06-15 15:21:43 +00:00
|
|
|
stackframe._stack[:n]
|
2022-06-01 05:54:11 +00:00
|
|
|
)
|
2022-06-14 02:23:43 +00:00
|
|
|
stackframe.drop(n)
|
|
|
|
stackframe.push(c)
|
2022-06-01 05:54:11 +00:00
|
|
|
|
2022-06-01 06:09:59 +00:00
|
|
|
case Opcode.CLOSUREC(n):
|
2022-06-14 02:23:43 +00:00
|
|
|
c = stackframe.pop()
|
2022-06-01 06:09:59 +00:00
|
|
|
if not isinstance(c, Closure):
|
|
|
|
_error("CLOSUREC requires a closure at top of stack")
|
2022-06-15 15:21:43 +00:00
|
|
|
fun = mod.functions[c.funref.name]
|
|
|
|
if n + len(c.frag) > len(fun.arguments):
|
2022-06-01 06:09:59 +00:00
|
|
|
_error("CLOSUREC target violation; too many parameters provided")
|
2022-06-14 02:23:43 +00:00
|
|
|
if n > len(stackframe):
|
2022-06-01 06:09:59 +00:00
|
|
|
_error("Stack size violation")
|
|
|
|
|
|
|
|
c = Closure(
|
|
|
|
c.funref,
|
2022-06-15 15:21:43 +00:00
|
|
|
stackframe._stack[:n] + c.frag
|
2022-06-01 06:09:59 +00:00
|
|
|
)
|
2022-06-14 02:23:43 +00:00
|
|
|
stackframe.drop(n)
|
|
|
|
stackframe.push(c)
|
2022-06-01 06:09:59 +00:00
|
|
|
|
2022-06-01 05:54:11 +00:00
|
|
|
case Opcode.CALLC(n):
|
2022-06-14 02:23:43 +00:00
|
|
|
c = stackframe.pop()
|
2022-06-01 05:54:11 +00:00
|
|
|
if not isinstance(c, Closure):
|
|
|
|
_error("CALLC requires a closure at top of stack")
|
2022-06-15 15:21:43 +00:00
|
|
|
fun = mod.functions[c.funref.name]
|
|
|
|
if n + len(c.frag) != len(fun.arguments):
|
2022-06-01 05:54:11 +00:00
|
|
|
_error("CALLC target vionation; argument count missmatch")
|
2022-06-14 02:23:43 +00:00
|
|
|
if n > len(stackframe):
|
2022-06-07 16:32:15 +00:00
|
|
|
_error("Stack size violation")
|
2022-06-01 05:54:11 +00:00
|
|
|
|
|
|
|
# Extract the function signature
|
|
|
|
|
|
|
|
# Push the closure's stack fragment
|
2022-06-15 15:21:43 +00:00
|
|
|
stackframe._stack = c.frag + stackframe._stack
|
2022-06-01 05:54:11 +00:00
|
|
|
|
|
|
|
# Perform a "normal" funref call
|
2022-04-21 06:17:59 +00:00
|
|
|
try:
|
2022-06-15 15:21:43 +00:00
|
|
|
ip = mod.labels[fun.signature]
|
2022-04-21 06:17:59 +00:00
|
|
|
except KeyError:
|
|
|
|
_error("Unknown target")
|
|
|
|
|
2022-06-15 15:21:43 +00:00
|
|
|
stackframe = stackframe.call(fun, ip)
|
2022-04-21 06:17:59 +00:00
|
|
|
continue
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
case _:
|
|
|
|
raise Exception(f"Unhandled interpreter state {op}")
|
|
|
|
|
2022-06-15 15:21:43 +00:00
|
|
|
stackframe._ip += 1
|