Eliminate CALLS, add SLOT & DEBUG, rewrite bootstrap
This commit is contained in:
parent
f382afd9f1
commit
6916715123
5 changed files with 197 additions and 115 deletions
|
@ -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),
|
||||
]
|
||||
)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue