Eliminate CALLS, add SLOT & DEBUG, rewrite bootstrap

This commit is contained in:
Reid D. 'arrdem' McKenzie 2022-06-11 00:28:24 -06:00
parent 17f40a198f
commit e3e3d7613c
5 changed files with 197 additions and 115 deletions

View file

@ -39,8 +39,16 @@ OR3 = BOOTSTRAP.define_function(
";or;bool,bool,bool;bool",
[
# A B C
Opcode.CALLS(OR2), # A|B C
Opcode.CALLS(OR2), # A|B|C
Opcode.IDENTIFIERC(OR2),
Opcode.FUNREF(),
# FIXME: This could be tightened by using ROT maybe...
Opcode.SLOT(0),
Opcode.SLOT(1),
Opcode.SLOT(3),
Opcode.CALLF(2), # A|B
Opcode.SLOT(2),
Opcode.SLOT(3),
Opcode.CALLF(2), # A|B|C
Opcode.RETURN(1),
]
)
@ -62,8 +70,15 @@ AND3 = BOOTSTRAP.define_function(
";and;bool,bool,bool;bool",
[
# A B C
Opcode.CALLS(AND2), # A&B C
Opcode.CALLS(AND2), # A&B&C
Opcode.IDENTIFIERC(AND2),
Opcode.FUNREF(),
Opcode.SLOT(0),
Opcode.SLOT(1),
Opcode.SLOT(3),
Opcode.CALLF(2), # A&B C
Opcode.SLOT(2),
Opcode.SLOT(3),
Opcode.CALLF(2), # A&B&C
Opcode.RETURN(1),
],
)
@ -71,18 +86,30 @@ AND3 = BOOTSTRAP.define_function(
XOR2 = BOOTSTRAP.define_function(
";xor;bool,bool;bool",
[
Opcode.IDENTIFIERC(AND2),
Opcode.FUNREF(),
Opcode.IDENTIFIERC(NOT1),
Opcode.FUNREF(),
Opcode.SLOT(0),
Opcode.SLOT(1),
Opcode.DUP(nargs=2),
# !A && B
Opcode.CALLS(NOT1),
Opcode.CALLS(AND2),
Opcode.IF(target=6),
Opcode.SLOT(3), # not
Opcode.CALLF(1),
Opcode.SLOT(2), # and
Opcode.CALLF(2),
Opcode.IF(target=14),
Opcode.TRUE(),
Opcode.RETURN(1),
# !B && A
Opcode.ROT(2),
Opcode.CALLS(NOT1),
Opcode.CALLS(AND2),
Opcode.IF(target=12),
Opcode.SLOT(3), # not
Opcode.CALLF(1),
Opcode.SLOT(2), # and
Opcode.CALLF(2),
Opcode.IF(target=22),
Opcode.TRUE(),
Opcode.RETURN(1),
Opcode.FALSE(),
@ -94,16 +121,27 @@ XOR2 = BOOTSTRAP.define_function(
XOR3 = BOOTSTRAP.define_function(
";xor;bool,bool,bool;bool",
[
Opcode.IDENTIFIERC(XOR2),
Opcode.FUNREF(),
Opcode.IDENTIFIERC(OR2),
Opcode.FUNREF(),
Opcode.SLOT(0),
Opcode.SLOT(1),
Opcode.SLOT(2),
# A B C
Opcode.ROT(nargs=3), # C A B
Opcode.ROT(nargs=3), # B C A
Opcode.DUP(nargs=1), # B B C A
Opcode.ROT(nargs=4), # A B B C
Opcode.CALLS(XOR2), # A^B B C
Opcode.SLOT(3),
Opcode.CALLF(2), # A^B B C
Opcode.ROT(nargs=3), # C A^B B
Opcode.ROT(nargs=3), # B C A^B
Opcode.CALLS(XOR2), # B^C A^B
Opcode.CALLS(OR2), # A^B|B^C
Opcode.SLOT(3),
Opcode.CALLF(2), # B^C A^B
Opcode.SLOT(4),
Opcode.CALLF(2), # A^B|B^C
Opcode.RETURN(1),
]
)

View file

@ -11,12 +11,14 @@ context (a virtual machine) which DOES have an easily introspected and serialize
import sys
from ichor.typing import Identifier
assert sys.version_info > (3, 10, 0), "`match` support is required"
from copy import deepcopy
from .isa import Closure, FunctionRef, Opcode
from .isa import Closure, FunctionRef, Opcode, Identifier
def rotate(l):
@ -120,6 +122,13 @@ class Interpreter(object):
stack.ip = target
continue
case Opcode.GOTO(n):
if (n < 0):
_error("Illegal branch target")
stack.ip = n
continue
case Opcode.DUP(n):
if (n > len(stack)):
_error("Stack size violation")
@ -138,45 +147,26 @@ class Interpreter(object):
stack.drop(n)
case Opcode.CALLS(dest):
try:
sig = FunctionRef.parse(dest)
except:
_error("Invalid target")
try:
ip = mod.functions[dest]
except KeyError:
_error("Unknown target")
stack = stack.call(sig, ip)
continue
case Opcode.RETURN(n):
if (n > len(stack)):
_error("Stack size violation")
if stack.depth == 0:
return stack[:n]
sig = FunctionRef.parse(stack.name)
if (len(sig.ret) != n):
_error("Signature violation")
stack = stack.ret(n)
continue
case Opcode.GOTO(n, _):
case Opcode.SLOT(n):
if (n < 0):
_error("Illegal branch target")
_error("SLOT must have a positive reference")
if (n > len(stack.stack) - 1):
_error("SLOT reference out of range")
stack.push(stack.stack[len(stack) - n - 1])
stack.ip = n
continue
case Opcode.IDENTIFIERC(name):
if not (name in mod.functions or name in mod.types):
_error("IDENTIFIERC references unknown entity")
case Opcode.FUNREF(funref):
stack.push(Identifier(name))
case Opcode.FUNREF():
id = stack.pop()
if not isinstance(id, Identifier):
_error("FUNREF consumes an IDENTIFIER")
try:
# FIXME: Verify this statically
stack.push(FunctionRef.parse(funref))
stack.push(FunctionRef.parse(id.name))
except:
_error("Invalid function ref")
@ -197,6 +187,20 @@ class Interpreter(object):
stack = stack.call(sig, ip)
continue
case Opcode.RETURN(n):
if (n > len(stack)):
_error("Stack size violation")
if stack.depth == 0:
return stack[:n]
sig = FunctionRef.parse(stack.name)
if (len(sig.ret) != n):
_error("Signature violation")
stack = stack.ret(n)
continue
case Opcode.CLOSUREF(n):
sig = stack.pop()
if not isinstance(sig, FunctionRef):

View file

@ -10,6 +10,8 @@ class Opcode:
####################################################################################################
# Logic
####################################################################################################
# FIXME: This should become an instantiation of the BOOL enum
class TRUE(t.NamedTuple):
"""() -> (bool)
@ -17,6 +19,7 @@ class Opcode:
"""
# FIXME: This should become an instantiation of the BOOL enum
class FALSE(t.NamedTuple):
"""() -> (bool)
@ -24,6 +27,7 @@ class Opcode:
"""
# FIXME: This should become a `VTEST` macro ... or may be replaceable
class IF(t.NamedTuple):
"""(bool) -> ()
@ -35,12 +39,23 @@ class Opcode:
# not, and, or, xor etc. can all be functions given if.
class GOTO(t.NamedTuple):
"""() -> ()
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
class DUP(t.NamedTuple):
"""(A, B, ...) -> (A, B, ...)
@ -68,62 +83,39 @@ class Opcode:
nargs: int = 1
####################################################################################################
# Functional abstraction
####################################################################################################
class CALLS(t.NamedTuple):
"""(... A) -> (... B)
class SLOT(t.NamedTuple):
"""(..., A) -> (A, ..., A)
Call [static]
Branch to `target` pushing the current point onto the call stack.
The callee will see a stack containg only the provided `nargs`.
A subsequent RETURN will return execution to the next point.
Executing a `CALL` pushes the name and module path of the current function.
.. note::
CALLS is equvalent to `FUNREF; CALLF`
"""
funref: str
class RETURN(t.NamedTuple):
"""(... 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.
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
class GOTO(t.NamedTuple):
"""() -> ()
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.
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
anywhere: bool = False
####################################################################################################
# Functional abstraction
####################################################################################################
class IDENTIFIERC(t.NamedTuple):
"""() -> (IDENTIFIER)
An inline constant which produces an identifier to the stack.
Identifiers name functions, fields and types but are not strings.
They are a VM-internal naming structure with reference to the module.
"""
val: str
class FUNREF(t.NamedTuple):
"""() -> (`FUNREF<... A to ... B>`)
"""(IDENTIFIER) -> (`FUNREF<... A to ... B>`)
Construct a reference to a static codepoint.
"""
funref: str
class CALLF(t.NamedTuple):
"""(`FUNREF<... A to ... B>`, ... A) -> (... B)
@ -139,6 +131,21 @@ class Opcode:
nargs: int = 0
class RETURN(t.NamedTuple):
"""(... 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.
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
class CLOSUREF(t.NamedTuple):
"""(`FUNREF<A, ... B to ... C>`, A) -> (`CLOSURE<... B to ... C>`)
@ -176,19 +183,7 @@ class Opcode:
# Structures
####################################################################################################
# FIXME: This lacks any sort of way to do dynamic field references
class IDENTIFIERC(t.NamedTuple):
"""() -> (CONST)
An inline constant which produces an identifier to the stack.
Identifiers name functions, fields and types but are not strings.
They are a VM-internal naming structure with reference to the module.
"""
val: str
# FIXME: This lacks any sort of way to do dynamic type/field references
class TYPEREF(t.NamedTuple):
"""(IDENTIFIER) -> (TYPEREF)
@ -316,6 +311,10 @@ class Opcode:
####################################################################################################
class BREAK(t.NamedTuple):
pass
class Module(t.NamedTuple):
opcodes: list = []
functions: dict = {}
@ -339,7 +338,7 @@ class Module(t.NamedTuple):
assert start <= d < end
return Opcode.IF(d)
case Opcode.GOTO(t, anywhere=False):
case Opcode.GOTO(t):
d = t + start
assert start <= d < end
return Opcode.GOTO(d)

View file

@ -91,3 +91,7 @@ class Struct(t.NamedTuple):
name: str
type_params: list
children: t.Mapping[str, t.Any]
class Identifier(t.NamedTuple):
name: str

View file

@ -11,7 +11,12 @@ import pytest
[[True], [False]],
])
def test_not(vm, stack, ret):
assert vm.run([Opcode.CALLS(NOT1), Opcode.RETURN(1)], stack = stack) == ret
assert vm.run([
Opcode.IDENTIFIERC(NOT1),
Opcode.FUNREF(),
Opcode.CALLF(1),
Opcode.RETURN(1)
], stack = stack) == ret
@pytest.mark.parametrize("stack,ret", [
@ -21,7 +26,12 @@ def test_not(vm, stack, ret):
[[True, True], [True]],
])
def test_or(vm, stack, ret):
assert vm.run([Opcode.CALLS(OR2), Opcode.RETURN(1)], stack = stack) == ret
assert vm.run([
Opcode.IDENTIFIERC(OR2),
Opcode.FUNREF(),
Opcode.CALLF(2),
Opcode.RETURN(1)
], stack = stack) == ret
@pytest.mark.parametrize("stack,ret", [
@ -31,7 +41,12 @@ def test_or(vm, stack, ret):
[[True, True], [True]],
])
def test_and(vm, stack, ret):
assert vm.run([Opcode.CALLS(AND2), Opcode.RETURN(1)], stack = stack) == ret
assert vm.run([
Opcode.IDENTIFIERC(AND2),
Opcode.FUNREF(),
Opcode.CALLF(2),
Opcode.RETURN(1)
], stack = stack) == ret
@pytest.mark.parametrize("stack,ret", [
@ -41,7 +56,12 @@ def test_and(vm, stack, ret):
[[True, True], [False]],
])
def test_xor2(vm, stack, ret):
assert vm.run([Opcode.CALLS(XOR2), Opcode.RETURN(1)], stack = stack) == ret
assert vm.run([
Opcode.IDENTIFIERC(XOR2),
Opcode.FUNREF(),
Opcode.CALLF(2),
Opcode.RETURN(1)
], stack = stack) == ret
@pytest.mark.parametrize("stack,ret", [
@ -54,21 +74,36 @@ def test_xor2(vm, stack, ret):
[[False, False, True], [True]],
])
def test_xor3(vm, stack, ret):
assert vm.run([Opcode.CALLS(XOR3), Opcode.RETURN(1)], stack = stack) == ret
assert vm.run([
Opcode.IDENTIFIERC(XOR3),
Opcode.FUNREF(),
Opcode.CALLF(3),
Opcode.RETURN(1)
], stack = stack) == ret
@pytest.mark.parametrize("stack,ret", [
[[], [FunctionRef.parse(NOT1)]]
])
def test_funref(vm, stack, ret):
assert vm.run([Opcode.FUNREF(NOT1), Opcode.RETURN(1)], stack = stack) == ret
assert vm.run([
Opcode.IDENTIFIERC(NOT1),
Opcode.FUNREF(),
Opcode.RETURN(1)
], stack = stack) == ret
@pytest.mark.parametrize("stack,ret", [
[[], [True]]
])
def test_callf(vm, stack, ret):
assert vm.run([Opcode.FALSE(), Opcode.FUNREF(NOT1), Opcode.CALLF(1), Opcode.RETURN(1)], stack = stack) == ret
assert vm.run([
Opcode.FALSE(),
Opcode.IDENTIFIERC(NOT1),
Opcode.FUNREF(),
Opcode.CALLF(1),
Opcode.RETURN(1)
], stack = stack) == ret
@pytest.mark.parametrize("stack,ret", [
@ -79,7 +114,8 @@ def test_callf(vm, stack, ret):
])
def test_callc(vm, stack, ret):
assert vm.run([
Opcode.FUNREF(XOR2),
Opcode.IDENTIFIERC(XOR2),
Opcode.FUNREF(),
Opcode.CLOSUREF(1),
Opcode.CALLC(1),
Opcode.RETURN(1),
@ -97,7 +133,8 @@ def test_callc(vm, stack, ret):
])
def test_closurec(vm, stack, ret):
assert vm.run([
Opcode.FUNREF(XOR3),
Opcode.IDENTIFIERC(XOR3),
Opcode.FUNREF(),
Opcode.CLOSUREF(1),
Opcode.CLOSUREC(1),
Opcode.CALLC(1),