source/projects/shoggoth/src/python/ichor/impl.py

263 lines
8.9 KiB
Python
Raw Normal View History

#!/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.
"""
from copy import deepcopy
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
from ichor.state import Closure, FunctionRef, Identifier, Module, Function
2022-03-29 07:29:18 +00:00
def rotate(l):
return [l[-1]] + l[:-1]
class Stackframe(object):
def __init__(self,
stack=None,
fun: t.Optional[Function] = 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)
def call(self, fun: Function, ip) -> "Stackframe":
2022-06-01 05:08:29 +00:00
self.ip += 1
nargs = len(fun.arguments)
2022-03-29 07:29:18 +00:00
args, self.stack = self.stack[:nargs], self.stack[nargs:]
return Stackframe(
2022-06-01 05:08:29 +00:00
stack=args,
name=fun.signature,
2022-06-01 05:08:29 +00:00
ip=ip,
parent=self,
depth=self.depth+1
)
2022-03-29 07:29:18 +00:00
2022-06-14 02:23:43 +00:00
def ret(self, nargs) -> "Stackframe":
2022-03-29 07:29:18 +00:00
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:]
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):
"""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."""
2022-06-14 02:23:43 +00:00
stackframe = Stackframe(stack=stack)
2022-03-29 07:29:18 +00:00
mod = self.bootstrap.copy()
stackframe.ip = mod.labels[mod.define_function(";<main>;;", opcodes)]
2022-06-01 06:09:59 +00:00
print(mod)
2022-03-29 07:29:18 +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-03-29 07:29:18 +00:00
while True:
2022-06-14 03:11:15 +00:00
op = mod.codepage[stackframe.ip]
2022-06-14 02:23:43 +00:00
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:
case Opcode.TRUE():
2022-06-14 02:23:43 +00:00
stackframe.push(True)
2022-03-29 07:29:18 +00:00
case Opcode.FALSE():
2022-06-14 02:23:43 +00:00
stackframe.push(False)
2022-03-29 07:29:18 +00:00
case Opcode.IF(target):
2022-06-14 02:23:43 +00:00
if len(stackframe) < 1:
_error("Stack size violation")
2022-06-14 02:23:43 +00:00
val = stackframe.pop()
if val not in [True, False]:
_error("Type violation")
if val is False:
2022-06-14 02:23:43 +00:00
stackframe.ip = target
2022-03-29 07:29:18 +00:00
continue
case Opcode.GOTO(n):
if (n < 0):
_error("Illegal branch target")
2022-06-14 02:23:43 +00:00
stackframe.ip = n
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)):
_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)):
_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)):
_error("Stack size violation")
2022-06-14 02:23:43 +00:00
stackframe.drop(n)
2022-03-29 07:29:18 +00:00
case Opcode.SLOT(n):
if (n < 0):
_error("SLOT must have a positive reference")
2022-06-14 02:23:43 +00:00
if (n > len(stackframe.stack) - 1):
_error("SLOT reference out of range")
2022-06-14 02:23:43 +00:00
stackframe.push(stackframe.stack[len(stackframe) - n - 1])
case Opcode.IDENTIFIERC(name):
if not (name in mod.functions or name in mod.types):
_error("IDENTIFIERC references unknown entity")
2022-03-29 07:29:18 +00:00
2022-06-14 02:23:43 +00:00
stackframe.push(Identifier(name))
case Opcode.FUNREF():
2022-06-14 02:23:43 +00:00
id = stackframe.pop()
if not isinstance(id, Identifier):
_error("FUNREF consumes an IDENTIFIER")
try:
# FIXME: Verify this statically
2022-06-14 02:23:43 +00:00
stackframe.push(FunctionRef.parse(id.name))
except:
_error("Invalid function ref")
case Opcode.CALLF(n):
2022-06-14 02:23:43 +00:00
sig = stackframe.pop()
if not isinstance(sig, FunctionRef):
_error("CALLF requires a funref at top of stack")
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:
ip = mod.codepage[sig.raw]
2022-06-01 05:54:11 +00:00
except KeyError:
_error("Unknown target")
stackframe = stackframe.call(fun, ip)
2022-06-01 05:54:11 +00:00
continue
case Opcode.RETURN(n):
2022-06-14 02:23:43 +00:00
if (n > len(stackframe)):
_error("Stack size violation")
2022-06-14 02:23:43 +00:00
if stackframe.depth == 0:
return stackframe[:n]
2022-06-14 02:23:43 +00:00
sig = FunctionRef.parse(stackframe.name)
if (len(sig.ret) != n):
_error("Signature violation")
2022-06-14 02:23:43 +00:00
stackframe = stackframe.ret(n)
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")
if not n <= len(sig.args):
_error("CLOSUREF target violation; too many parameters provided")
2022-06-14 02:23:43 +00:00
if n > len(stackframe):
_error("Stack size violation")
2022-06-01 05:54:11 +00:00
c = Closure(
sig,
2022-06-14 02:23: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")
if n + len(c.frag) > len(c.funref.args):
_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-14 02:23: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")
if n + len(c.frag) != len(c.funref.args):
_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
sig = c.funref
# Push the closure's stack fragment
2022-06-14 02:23:43 +00:00
stackframe.stack = c.frag + stackframe.stack
2022-06-01 05:54:11 +00:00
# Perform a "normal" funref call
try:
ip = mod.functions[sig.raw]
except KeyError:
_error("Unknown target")
2022-06-14 02:23:43 +00:00
stackframe = stackframe.call(sig, ip)
continue
2022-03-29 07:29:18 +00:00
case _:
raise Exception(f"Unhandled interpreter state {op}")
2022-06-14 02:23:43 +00:00
stackframe.ip += 1