Renaming variant operations
This commit is contained in:
parent
a7b236fa5f
commit
2557ff1e4f
12 changed files with 98 additions and 83 deletions
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -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}")
|
||||
|
|
@ -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.
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from ichor import FuncBuilder, isa
|
||||
from ichor import isa
|
||||
from ichor.assembler import FuncBuilder
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
"""Tests covering the Ichor ISA and state model."""
|
||||
|
||||
from ichor.impl import Stackframe
|
||||
from ichor.interpreter import Stackframe
|
||||
import pytest
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue