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
|
|
|
|
|
|
|
import sys
|
2022-05-31 15:41:10 +00:00
|
|
|
|
|
|
|
|
2022-05-12 06:21:22 +00:00
|
|
|
assert sys.version_info > (3, 10, 0), "`match` support is required"
|
|
|
|
|
2022-04-12 07:49:12 +00:00
|
|
|
from copy import deepcopy
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-04-21 06:17:59 +00:00
|
|
|
from .isa import FunctionRef, Opcode
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
def rotate(l):
|
|
|
|
return [l[-1]] + l[:-1]
|
|
|
|
|
|
|
|
|
|
|
|
class Stackframe(object):
|
2022-06-01 05:08:29 +00:00
|
|
|
def __init__(self, stack=None, name=None, ip=None, parent=None, depth=0):
|
2022-03-29 07:29:18 +00:00
|
|
|
self.stack = stack or []
|
|
|
|
self.name = name or ";unknown;;"
|
|
|
|
self.ip = ip or 0
|
|
|
|
self.parent = parent
|
2022-06-01 05:08:29 +00:00
|
|
|
self.depth = depth
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
def push(self, obj):
|
|
|
|
self.stack.insert(0, obj)
|
|
|
|
|
|
|
|
def pop(self):
|
|
|
|
return self.stack.pop(0)
|
|
|
|
|
2022-04-21 06:17:59 +00:00
|
|
|
def call(self, signature: FunctionRef, ip):
|
2022-06-01 05:08:29 +00:00
|
|
|
self.ip += 1
|
2022-03-29 07:29:18 +00:00
|
|
|
nargs = len(signature.args)
|
|
|
|
args, self.stack = self.stack[:nargs], self.stack[nargs:]
|
|
|
|
return Stackframe(
|
2022-06-01 05:08:29 +00:00
|
|
|
stack=args,
|
|
|
|
name=signature.raw,
|
|
|
|
ip=ip,
|
|
|
|
parent=self,
|
|
|
|
depth=self.depth+1
|
|
|
|
)
|
2022-03-29 07:29:18 +00:00
|
|
|
|
|
|
|
def ret(self, nargs):
|
|
|
|
self.parent.stack = self.stack[:nargs] + self.parent.stack
|
|
|
|
return self.parent
|
|
|
|
|
|
|
|
def dup(self, nargs):
|
|
|
|
self.stack = self.stack[:nargs] + self.stack
|
|
|
|
|
|
|
|
def drop(self, nargs):
|
|
|
|
self.stack = self.stack[nargs:]
|
|
|
|
|
|
|
|
def rot(self, nargs):
|
|
|
|
self.stack = rotate(self.stack[:nargs]) + self.stack[nargs:]
|
|
|
|
|
2022-04-12 07:49:12 +00:00
|
|
|
def __getitem__(self, key):
|
|
|
|
return self.stack.__getitem__(key)
|
|
|
|
|
|
|
|
def __len__(self):
|
|
|
|
return len(self.stack)
|
|
|
|
|
|
|
|
|
|
|
|
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-03-29 07:29:18 +00:00
|
|
|
def __init__(self, bootstrap_module):
|
|
|
|
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."""
|
|
|
|
|
2022-04-15 06:31:58 +00:00
|
|
|
stack = Stackframe(stack=stack)
|
2022-03-29 07:29:18 +00:00
|
|
|
mod = self.bootstrap.copy()
|
|
|
|
mod.define_function(";<entry>;;", opcodes)
|
|
|
|
stack.ip = mod.functions[";<entry>;;"]
|
|
|
|
|
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
|
|
|
|
raise InterpreterError(mod, deepcopy(stack), msg)
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
while True:
|
|
|
|
op = mod.opcodes[stack.ip]
|
2022-06-01 05:17:32 +00:00
|
|
|
print("{0}{1: <50} {2}: {3}".format(" " * stack.depth, str(stack.stack), stack.ip, op))
|
2022-06-01 05:08:29 +00:00
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
match op:
|
|
|
|
case Opcode.TRUE():
|
|
|
|
stack.push(True)
|
|
|
|
|
|
|
|
case Opcode.FALSE():
|
|
|
|
stack.push(False)
|
|
|
|
|
|
|
|
case Opcode.IF(target):
|
2022-04-12 07:49:12 +00:00
|
|
|
if len(stack) < 1:
|
|
|
|
_error("Stack size violation")
|
|
|
|
|
|
|
|
val = stack.pop()
|
|
|
|
if val not in [True, False]:
|
|
|
|
_error("Type violation")
|
|
|
|
|
|
|
|
if val is False:
|
2022-03-29 07:29:18 +00:00
|
|
|
stack.ip = target
|
|
|
|
continue
|
|
|
|
|
|
|
|
case Opcode.DUP(n):
|
2022-04-12 07:49:12 +00:00
|
|
|
if (n > len(stack)):
|
|
|
|
_error("Stack size violation")
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
stack.dup(n)
|
|
|
|
|
|
|
|
case Opcode.ROT(n):
|
2022-04-12 07:49:12 +00:00
|
|
|
if (n > len(stack)):
|
|
|
|
_error("Stack size violation")
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
stack.rot(n)
|
|
|
|
|
|
|
|
case Opcode.DROP(n):
|
2022-04-12 07:49:12 +00:00
|
|
|
if (n > len(stack)):
|
|
|
|
_error("Stack size violation")
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
stack.drop(n)
|
|
|
|
|
2022-04-21 06:17:59 +00:00
|
|
|
case Opcode.CALLS(dest):
|
2022-04-12 07:49:12 +00:00
|
|
|
try:
|
2022-04-21 06:17:59 +00:00
|
|
|
sig = FunctionRef.parse(dest)
|
2022-04-12 07:49:12 +00:00
|
|
|
except:
|
|
|
|
_error("Invalid target")
|
|
|
|
|
|
|
|
try:
|
|
|
|
ip = mod.functions[dest]
|
|
|
|
except KeyError:
|
|
|
|
_error("Unknown target")
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
stack = stack.call(sig, ip)
|
|
|
|
continue
|
|
|
|
|
|
|
|
case Opcode.RETURN(n):
|
2022-04-12 07:49:12 +00:00
|
|
|
if (n > len(stack)):
|
|
|
|
_error("Stack size violation")
|
|
|
|
|
2022-06-01 05:08:29 +00:00
|
|
|
if stack.depth == 0:
|
2022-04-12 07:49:12 +00:00
|
|
|
return stack[:n]
|
2022-03-29 07:29:18 +00:00
|
|
|
|
2022-06-01 05:08:29 +00:00
|
|
|
sig = FunctionRef.parse(stack.name)
|
|
|
|
if (len(sig.ret) != n):
|
|
|
|
_error("Signature violation")
|
|
|
|
|
|
|
|
stack = stack.ret(n)
|
|
|
|
continue
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
case Opcode.GOTO(n, _):
|
2022-04-12 07:49:12 +00:00
|
|
|
if (n < 0):
|
|
|
|
_error("Illegal branch target")
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
stack.ip = n
|
|
|
|
continue
|
|
|
|
|
2022-04-21 06:17:59 +00:00
|
|
|
case Opcode.FUNREF(funref):
|
|
|
|
try:
|
|
|
|
# FIXME: Verify this statically
|
|
|
|
stack.push(FunctionRef.parse(funref))
|
|
|
|
except:
|
|
|
|
_error("Invalid function ref")
|
|
|
|
|
|
|
|
case Opcode.CALLF(n):
|
|
|
|
sig = stack.pop()
|
|
|
|
if not isinstance(sig, FunctionRef):
|
|
|
|
_error("CALLF requires a funref at top of stack")
|
|
|
|
if not n == len(sig.args):
|
|
|
|
_error("CALLF target violation; not enough arguments provided")
|
|
|
|
if n > len(stack):
|
|
|
|
_error("Stack size violation")
|
|
|
|
|
|
|
|
try:
|
|
|
|
ip = mod.functions[sig.raw]
|
|
|
|
except KeyError:
|
|
|
|
_error("Unknown target")
|
|
|
|
|
|
|
|
stack = stack.call(sig, ip)
|
|
|
|
continue
|
|
|
|
|
2022-03-29 07:29:18 +00:00
|
|
|
case _:
|
|
|
|
raise Exception(f"Unhandled interpreter state {op}")
|
|
|
|
|
|
|
|
stack.ip += 1
|