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

272 lines
10 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
2022-06-28 04:35:21 +00:00
from textwrap import indent
2022-03-29 07:29:18 +00:00
2022-07-16 01:33:32 +00:00
from ichor import isa
2022-07-16 01:37:34 +00:00
from ichor.state import (
Closure,
FunctionRef,
Identifier,
Module,
Stackframe,
TypeRef,
Variant,
VariantRef,
)
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."""
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-28 04:35:21 +00:00
clock: int = 0
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-06-28 04:35:21 +00:00
def _debug():
b = []
b.append(f"clock {clock}:")
b.append(" stack:")
2022-07-16 01:33:32 +00:00
for offset, it in zip(range(0, len(stackframe), 1), stackframe):
2022-06-28 04:35:21 +00:00
b.append(f" {offset: <3} {it}")
b.append(f" op: {op}")
print(indent("\n".join(b), " " * stackframe.depth))
2022-03-29 07:29:18 +00:00
while True:
2022-06-15 15:21:43 +00:00
op = mod.codepage[stackframe._ip]
2022-06-28 04:35:21 +00:00
_debug()
clock += 1
2022-06-01 05:08:29 +00:00
2022-03-29 07:29:18 +00:00
match op:
2022-07-16 01:33:32 +00:00
case isa.IDENTIFIERC(name):
2022-06-28 04:35:21 +00:00
if not (name in mod.functions
or name in mod.types
or any(name in t.constructors for t in mod.types.values())):
2022-06-16 16:55:18 +00:00
_error("IDENTIFIERC references unknown entity")
stackframe.push(Identifier(name))
2022-07-16 01:33:32 +00:00
case isa.TYPEREF():
2022-06-16 16:55:18 +00:00
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))
2022-07-16 01:33:32 +00:00
case isa.VARIANTREF():
2022-06-16 16:55:18 +00:00
id: Identifier = stackframe.pop()
if not isinstance(id, Identifier):
_error("VARIANTREF consumes an identifier and a typeref")
t: TypeRef = stackframe.pop()
2022-06-28 04:35:21 +00:00
if not isinstance(t, TypeRef):
2022-06-16 16:55:18 +00:00
_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))
2022-07-16 01:33:32 +00:00
case isa.VARIANT(n):
2022-06-16 16:55:18 +00:00
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):
_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
2022-06-28 04:35:21 +00:00
v = Variant(armref.type.name, armref.arm, tuple(stackframe[:n]))
2022-06-16 16:55:18 +00:00
stackframe.drop(n)
stackframe.push(v)
2022-07-16 01:33:32 +00:00
case isa.VTEST(n):
2022-06-16 16:55:18 +00:00
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-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-07-16 01:33:32 +00:00
case isa.GOTO(n):
if (n < 0):
_error("Illegal branch target")
2022-06-15 15:21:43 +00:00
stackframe.goto(n)
continue
2022-07-16 01:33:32 +00:00
case isa.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
2022-07-16 01:33:32 +00:00
case isa.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
2022-07-16 01:33:32 +00:00
case isa.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
2022-07-16 01:33:32 +00:00
case isa.SLOT(n):
if (n < 0):
_error("SLOT must have a positive reference")
2022-06-15 15:21:43 +00:00
if (n > len(stackframe) - 1):
_error("SLOT reference out of range")
2022-06-15 15:21:43 +00:00
stackframe.slot(n)
2022-07-16 01:33:32 +00:00
case isa.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")
2022-07-16 01:33:32 +00:00
case isa.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:
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")
stackframe = stackframe.call(fun, ip)
2022-06-01 05:54:11 +00:00
continue
2022-07-16 01:33:32 +00:00
case isa.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-15 15:21:43 +00:00
if (len(stackframe._fun.returns) != n):
_error("Signature violation")
2022-06-14 02:23:43 +00:00
stackframe = stackframe.ret(n)
continue
2022-07-16 01:33:32 +00:00
case isa.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):
_error("Stack size violation")
2022-06-01 05:54:11 +00:00
c = Closure(
sig,
2022-06-28 04:35:21 +00:00
stackframe[: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-07-16 01:33:32 +00:00
case isa.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-28 04:35:21 +00:00
stackframe[: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-07-16 01:33:32 +00:00
case isa.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-07-16 01:33:32 +00:00
stackframe._stack = c.frag + stackframe._stack
2022-06-01 05:54:11 +00:00
# Perform a "normal" funref call
try:
2022-06-15 15:21:43 +00:00
ip = mod.labels[fun.signature]
except KeyError:
_error("Unknown target")
2022-06-15 15:21:43 +00:00
stackframe = stackframe.call(fun, ip)
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