Renaming variant operations

This commit is contained in:
Reid 'arrdem' McKenzie 2022-08-09 09:39:33 -06:00
parent a7b236fa5f
commit 2557ff1e4f
12 changed files with 98 additions and 83 deletions

View file

@ -1,12 +0,0 @@
#!/usr/bin/env python
"""
Import everything out of the Ichor modules.
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

@ -61,8 +61,8 @@ class FuncBuilder(object):
case isa.GOTO(isa.Label(_) as l):
assembled.append(isa.GOTO(labels[l]))
case isa.VTEST(isa.Label(_) as l):
assembled.append(isa.VTEST(labels[l]))
case isa.ATEST(isa.Label(_) as l):
assembled.append(isa.ATEST(labels[l]))
case o:
assembled.append(o)
@ -84,7 +84,18 @@ class LocalBuilder(FuncBuilder):
self._labels = {}
def _write(self, op):
pass
super()._write(op)
match op:
case isa.DROP(n):
self._stack -= n
case isa.DUP(n):
self._stack += n
case isa.ROT(_):
pass
case _:
self._stack -= getattr(op, 'nargs', 0)
self._stack += 1
def write_local(self, label: isa.Label):
pass

View file

@ -25,20 +25,20 @@ NOT1 = BOOTSTRAP.define_function(
isa.TYPEREF(), # <typeref bool> a
isa.DUP(),
isa.IDENTIFIERC("true"),
isa.VARIANTREF(), # <variantref true:bool> <typeref bool> a
isa.ARMREF(), # <variantref true:bool> <typeref bool> a
isa.DUP(),
isa.SLOT(0),
isa.ROT(2),
isa.VTEST(11),
isa.ATEST(11),
isa.VARIANT(0),
isa.RETURN(1),
isa.ARM(0),
isa.RETURN(),
isa.DROP(1),
isa.IDENTIFIERC("false"),
isa.VARIANTREF(),
isa.VARIANT(0),
isa.RETURN(1),
isa.ARMREF(),
isa.ARM(0),
isa.RETURN(),
],
)

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""The Ichor VM implementation.
"""The Ichor VM.interpreterementation.
The whole point of Shoggoth is that program executions are checkpointable and restartable. This requires that rather than
using a traditional recursive interpreter which is difficult to snapshot, interpretation in shoggoth occur within a
@ -35,7 +35,7 @@ class InterpreterError(Exception):
class Interpreter(object):
"""A shit simple instruction pointer based interpreter."""
"""A shit .interpretere instruction pointer based interpreter."""
def __init__(self, bootstrap_module: Module):
self.bootstrap = bootstrap_module
@ -89,7 +89,7 @@ class Interpreter(object):
stackframe.push(TypeRef(id.name))
case isa.VARIANTREF():
case isa.ARMREF():
id: Identifier = stackframe.pop()
if not isinstance(id, Identifier):
_error("VARIANTREF consumes an identifier and a typeref")
@ -104,7 +104,7 @@ class Interpreter(object):
stackframe.push(VariantRef(t, id.name))
case isa.VARIANT(n):
case isa.ARM(n):
armref: VariantRef = stackframe.pop()
if not isinstance(armref, VariantRef):
_error("VARIANT must be given a valid constructor reference")
@ -122,7 +122,7 @@ class Interpreter(object):
stackframe.drop(n)
stackframe.push(v)
case isa.VTEST(n):
case isa.ATEST(n):
armref: VariantRef = stackframe.pop()
if not isinstance(armref, VariantRef):
_error("VTEST must be given a variant reference")
@ -194,7 +194,8 @@ class Interpreter(object):
stackframe = stackframe.call(fun, ip)
continue
case isa.RETURN(n):
case isa.RETURN():
n = 1 # FIXME: clean this up
if (n > len(stackframe)):
_error("Stack size violation")
@ -265,6 +266,10 @@ class Interpreter(object):
stackframe = stackframe.call(fun, ip)
continue
case isa.BREAK():
# FIXME: let users override this / set custom handlers
return stackframe._stack
case _:
raise Exception(f"Unhandled interpreter state {op}")

View file

@ -20,16 +20,17 @@ class Opcode(ABC):
class GOTO(Opcode):
"""() -> ()
Branch to another point within the same bytecode segment. The target MUST be within the same module range as the
current function. Branching does NOT update the name or module of the current function.
Branch to another point within the same bytecode segment. The target MUST be
within the same module range as the current function. Branching does NOT
update the name or module of the current function.
"""
target: int
####################################################################################################
################################################################################
# Stack manipulation
####################################################################################################
################################################################################
# https://wiki.laptop.org/go/Forth_stack_operators
# https://www.forth.com/starting-forth/2-stack-manipulation-operators-arithmetic/
# https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5.swap
@ -70,16 +71,17 @@ class DROP(Opcode):
class SLOT(Opcode):
"""(..., A) -> (A, ..., A)
Copy the Nth (counting up from 0 at the bottom of the stack) item to the top of the stack.
Intended to allow users to emulate (immutable) frame local slots for reused values.
Copy the Nth (counting up from 0 at the bottom of the stack) item to the top
of the stack. Intended to allow users to emulate (immutable) frame local
slots for reused values.
"""
target: int
####################################################################################################
################################################################################
# Functional abstraction
####################################################################################################
################################################################################
@dataclass
class IDENTIFIERC(Opcode):
@ -123,23 +125,24 @@ class CALLF(Opcode):
class RETURN(Opcode):
"""(... A) -> ()
Return to the source of the last `CALL`. The returnee will see the top `nargs` values of the present stack
appended to theirs. All other values on the stack will be discarded.
Return to the source of the last `CALL`. The returnee will see the top
`nargs` values of the present stack appended to theirs. All other values on
the stack will be discarded.
Executing a `RETURN` pops (restores) the name and module path of the current function back to that of the caller.
Executing a `RETURN` pops (restores) the name and module path of the current
function back to that of the caller.
If the call stack is empty, `RETURN` will exit the interpreter.
"""
nargs: int
@dataclass
class CLOSUREF(Opcode):
"""(`FUNREF<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
Construct a closure over the function reference at the top of the stack. This may produce nullary closures.
Construct a closure over the function reference at the top of the stack.
This may produce nullary closures.
"""
@ -150,7 +153,8 @@ class CLOSUREF(Opcode):
class CLOSUREC(Opcode):
"""(`CLOSURE<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
Further close over the closure at the top of the stack. This may produce nullary closures.
Further close over the closure at the top of the stack. This may produce
nullary closures.
"""
@ -162,9 +166,9 @@ class CALLC(Opcode):
Call [closure]
Make a dynamic call to the closure at the top of stack.
The callee will see a stack containg only the provided `nargs` and closed-overs.
A subsequent RETURN will return execution to the next point.
Make a dynamic call to the closure at the top of stack. The callee will see
a stack containg only the provided `nargs` and closed-overs. A subsequent
RETURN will return execution to the next point.
Executing a `CALL` pushes the name and module path of the current function.
@ -172,9 +176,9 @@ class CALLC(Opcode):
nargs: int = 0
####################################################################################################
################################################################################
# Structures
####################################################################################################
################################################################################
# FIXME: This lacks any sort of way to do dynamic type/field references
@ -188,8 +192,8 @@ class TYPEREF(Opcode):
@dataclass
class VARIANTREF(Opcode):
"""(IDENTIFIER, TYPEREF) -> (VARIANTREF)
class ARMREF(Opcode):
"""(TYPEREF, IDENTIFIER) -> (VARIANTREF)
Produce a VARIANTREF to an 'arm' of the given variant type.
@ -197,23 +201,25 @@ class VARIANTREF(Opcode):
@dataclass
class VARIANT(Opcode):
"""(VARIANTREF<a ⊢ A ⊂ B>, ...) -> (B)
class ARM(Opcode):
"""(ARMTREF<a ⊢ A ⊂ B>, ...) -> (B)
Construct an instance of an 'arm' of a variant.
The type of the 'arm' is considered to be the type of the whole variant.
The name and module path of the current function MUST match the name and module path of `VARIANTREF`.
The arity of this opcode MUST match the arity of the arm.
The signature of the arm MUST match the signature fo the top N of the stack.
The name and module path of the current function MUST match the name and
module path of `VARIANTREF`. The arity of this opcode MUST match the arity
of the arm. The signature of the arm MUST match the signature fo the top N
of the stack.
"""
nargs: int = 0
@dataclass
class VTEST(Opcode):
"""(VARIANTREF<a ⊢ A ⊂ B>, B) -> ()
class ATEST(Opcode):
"""(ARMREF<a ⊢ A ⊂ B>, B) -> ()
Test whether B is a given arm of a variant A .
If it is, branch to the given target.
@ -225,12 +231,12 @@ class VTEST(Opcode):
@dataclass
class VLOAD(Opcode):
"""(VARIANTREF<a ⊢ A ⊂ B>, B) -> (A)
class ALOAD(Opcode):
"""(ARMREF<a ⊢ A ⊂ B>, B) -> (A)
Load the value of the variant arm.
VLOAD errors (undefined) if B is not within the variant.
VLOAD errors (undefined) if the value in B is not an A - use VTEST as needed.
Load the value of the variant arm. VLOAD errors (undefined) if B is not
within the variant. VLOAD errors (undefined) if the value in B is not an A -
use VTEST as needed.
"""

View file

@ -158,7 +158,7 @@ class Variant(t.NamedTuple):
class Closure(t.NamedTuple):
# Note that a closure over a closure is equivalent to a single closure which extends the captured stack fragment, so
# there's no need for a union here as we can simply convert nested closures.
# there's no need for a union here as we can .interpretery convert nested closures.
funref: FunctionRef
frag: t.List[t.Any]
@ -189,10 +189,10 @@ class Module(t.NamedTuple):
def translate(start: int, end: int, i: isa.Opcode):
# FIXME: Consolidate bounds checks somehow
match i:
case isa.VTEST(t):
case isa.ATEST(t):
d = t + start
assert start <= d < end
return isa.VTEST(d)
return isa.ATEST(d)
case isa.GOTO(t):
d = t + start

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""The implementation of Shogoth's lexical analyzer."""
"""The.interpreterementation of Shogoth's lexical analyzer."""
from abc import ABC
from dataclasses import dataclass

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python3
from ichor import BOOTSTRAP, Interpreter
from ichor.bootstrap import BOOTSTRAP
from ichor.interpreter import Interpreter
import pytest

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python3
from ichor import FuncBuilder, isa
from ichor import isa
from ichor.assembler import FuncBuilder
import pytest

View file

@ -2,7 +2,8 @@
from .fixtures import * # noqa
from ichor import FALSE, isa, NOT1, TRUE
from ichor import isa
from ichor.bootstrap import FALSE, NOT1, TRUE
import pytest
@ -15,7 +16,7 @@ def test_not(vm, stack, ret):
isa.IDENTIFIERC(NOT1),
isa.FUNREF(),
isa.CALLF(1),
isa.RETURN(1)
isa.RETURN()
], stack = stack) == ret

View file

@ -4,47 +4,49 @@ Tests coverign the VM interpreter
from .fixtures import * # noqa
from ichor import (
from ichor.bootstrap import (
FALSE,
InterpreterError,
isa,
TRUE,
)
from ichor import isa
from ichor.interpreter import InterpreterError
import pytest
def test_return(vm):
assert vm.run([isa.RETURN(0)], stack=[TRUE, FALSE]) == []
assert vm.run([isa.RETURN(1)], stack=[TRUE, FALSE]) == [TRUE]
assert vm.run([isa.RETURN(2)], stack=[TRUE, FALSE]) == [TRUE, FALSE]
with pytest.raises(InterpreterError):
vm.run([isa.RETURN()], stack=[])
assert vm.run([isa.RETURN()], stack=[FALSE, TRUE]) == [FALSE]
assert vm.run([isa.DROP(1), isa.RETURN()], stack=[TRUE, FALSE]) == [TRUE]
def test_dup(vm):
assert vm.run([isa.DUP(1), isa.RETURN(3)], stack=[FALSE, TRUE]) == [FALSE, TRUE, TRUE]
assert vm.run([isa.DUP(2), isa.RETURN(4)], stack=[FALSE, TRUE]) == [FALSE, TRUE, FALSE, TRUE]
assert vm.run([isa.DUP(1), isa.BREAK()], stack=[FALSE, TRUE]) == [FALSE, TRUE, TRUE]
assert vm.run([isa.DUP(2), isa.BREAK()], stack=[FALSE, TRUE]) == [FALSE, TRUE, FALSE, TRUE]
def test_rot(vm):
assert vm.run([
isa.ROT(2),
isa.RETURN(2)
isa.BREAK()
], stack=[FALSE, TRUE]) == [TRUE, FALSE]
assert vm.run([
isa.ROT(2),
isa.RETURN(5)
isa.BREAK()
], stack=[TRUE, TRUE, TRUE, FALSE, TRUE]) == [TRUE, TRUE, TRUE, TRUE, FALSE]
assert vm.run([
isa.ROT(3),
isa.RETURN(3)
isa.BREAK()
], stack=[FALSE, TRUE, FALSE]) == [FALSE, FALSE, TRUE]
def test_drop(vm):
assert vm.run([
isa.DROP(1),
isa.RETURN(1)
isa.BREAK()
], stack=[TRUE, FALSE]) == [TRUE]

View file

@ -2,7 +2,7 @@
"""Tests covering the Ichor ISA and state model."""
from ichor.impl import Stackframe
from ichor.interpreter import Stackframe
import pytest