From e3e3d7613cb022ce6a4f7dbdf85bf74098eae137 Mon Sep 17 00:00:00 2001
From: "Reid D. 'arrdem' McKenzie" <me@arrdem.com>
Date: Sat, 11 Jun 2022 00:28:24 -0600
Subject: [PATCH] Eliminate CALLS, add SLOT & DEBUG, rewrite bootstrap

---
 .../shoggoth/src/python/ichor/bootstrap.py    |  64 ++++++++--
 projects/shoggoth/src/python/ichor/impl.py    |  74 +++++------
 projects/shoggoth/src/python/ichor/isa.py     | 115 +++++++++---------
 projects/shoggoth/src/python/ichor/typing.py  |   4 +
 .../test/python/ichor/test_bootstrap.py       |  55 +++++++--
 5 files changed, 197 insertions(+), 115 deletions(-)

diff --git a/projects/shoggoth/src/python/ichor/bootstrap.py b/projects/shoggoth/src/python/ichor/bootstrap.py
index ce64973..d88161e 100644
--- a/projects/shoggoth/src/python/ichor/bootstrap.py
+++ b/projects/shoggoth/src/python/ichor/bootstrap.py
@@ -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),
     ]
 )
diff --git a/projects/shoggoth/src/python/ichor/impl.py b/projects/shoggoth/src/python/ichor/impl.py
index ac57eb9..6dce03b 100644
--- a/projects/shoggoth/src/python/ichor/impl.py
+++ b/projects/shoggoth/src/python/ichor/impl.py
@@ -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):
diff --git a/projects/shoggoth/src/python/ichor/isa.py b/projects/shoggoth/src/python/ichor/isa.py
index a980740..3f9430e 100644
--- a/projects/shoggoth/src/python/ichor/isa.py
+++ b/projects/shoggoth/src/python/ichor/isa.py
@@ -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)
diff --git a/projects/shoggoth/src/python/ichor/typing.py b/projects/shoggoth/src/python/ichor/typing.py
index fe8ac89..40516fa 100644
--- a/projects/shoggoth/src/python/ichor/typing.py
+++ b/projects/shoggoth/src/python/ichor/typing.py
@@ -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
diff --git a/projects/shoggoth/test/python/ichor/test_bootstrap.py b/projects/shoggoth/test/python/ichor/test_bootstrap.py
index 9334b39..f6b6321 100644
--- a/projects/shoggoth/test/python/ichor/test_bootstrap.py
+++ b/projects/shoggoth/test/python/ichor/test_bootstrap.py
@@ -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),