Add examples for fixing locals to be dynamically allocated from the end of memory. This should allow for recursion and better handling in general.

This commit is contained in:
zongor 2026-05-07 22:25:55 -07:00
parent 86922ed86b
commit 5bf3b12f62
12 changed files with 562 additions and 16 deletions

137
docs/LANG_VM.org Normal file
View File

@ -0,0 +1,137 @@
* Languages / VM Notes about implmementations
** Instruction Order
*** Prefix
- Lisp like feel
**** Pros
- Easily parses into AST
- Easy to generate macros
**** Cons
- Unintuitive to beginners
- Hard to code read for begineers
*** Postfix
- Forth like feel
**** Pros
- Trivial to parse
- Generally small codebase
**** Cons
- Unintuitive to beginners
- Hard to code read for begineers
*** Infix
- Pretty much all others
**** Pros
- Intuitive to beginners (math like)
**** Cons
- More complex to parse
** Virtual Machine abstraction
*** Accululator
A single slot where all intermediate calculations get stored to
**** Pros
- Very simple
- Easy to implement
- Trivial to abstract
**** Cons
- Very slow due to instruction bottlenecks
*** Stack
A Last in First out, usually 2 stacks 1 operation and 1 return stack
**** Pros
- Simple to implement
- Parser trivial to build
**** Cons
- generally a stacic size, so recursion is usually unsafe.
*** Register
A set of very fast accumulators
**** Pros
- Very fast
- Closest to modern hardware
- Easy to JIT
**** Cons
- Extremly complicated
- Very tied to 'modern' hardware so hard to abstract
*** Memory to Memory
Operates on pointers to memory, everything exists in memory.
**** Pros
- Most flexable for dynamic allocation
- Easy to understand conceptually
**** Cons
- Likely slower than stack and register
** Memory abstractions
*** Manual
Manual memory management is where the language gives the developer tools to
allocate, deallocate, and move memory but does not manage logic.
**** Pros
- Simpler compiler
- Fastest
**** Cons
- Cognitive load on developer
- Higher chance of bugs due to mistakes
*** Garbage Collected
GC usually uses a 'mark and sweep' method where the programs 'vm' searches for unused blocks of memory and reclaims them.
**** Pros
- Developer does not have to worry about memory
**** Cons
- Non-deterministic pauses
*** RAII
Resource acquisition is initialization
Heap values are tied to the scope they are created in, complex rules allow for moving, coping, and borrowing memory allocations.
**** Pros
- If rules are followed creates much safer code than manual memory
- Deterministic
**** Cons
- Extremly complex memory rules that need to be followed by developer.
- Slow compilation due to static analysis of memory rules.
*** Arena
An arena is a block of memory where a series of allocations get grouped together.
It is a logical extension of manual memory managment where you allocate to an arena.
When an arena is freed all allocations are freed as a single block.
**** Pros
- Deterministic
**** Cons
- Developer still needs to think about memory
*** Stack based
All values are either pre-allocated at compile time or they grow with the stack being free'd when the scope ends for that scope.
This is how 'mission critical' software works like at NASA.
**** Pros
- Deterministic allocations
- Memory is freed when a scope ends
**** Cons
- Cannot allocate dynamic memory
- Very strict rules
*** Frame based
All memory used by the program exists in one big bump allocator.
When a function gets called, it creates a new arena in memory starting at the end of the previous frame. (or at the beginning of memory if no function has been called)
Variables allocated in the frame's arena are owned by default by the frame they are allocated in.
When variables get modified within the current frame they will get updated in that frame in their current location in the arena.
If the size of the variable changes (like adding a value to the end of a array), a link is added to the internal list that points to a new block is allocated
at the end of the arena of the size of that value, so they are variable sized blocks
When a variable is passed to a child function primitive types are pass by value, complex types (Arrays, Plexes, Strings) are pass by reference.
Variables passed as arguments to the child follow the same rules except when the size of a complex type changes, then a new value is added as a link
from the parent to the child and the new value is allocated at the end of the childs arena (but the owner is still the parent).
When a function "returns" the frame is deallocated and all values allocated by it is freed; thus freeing memory deterministically.
Only a single variable may be returned from a function. When a variable is returned if it is primitive it is just passed back to the parent.
If it is complex the value is coalesced into a single contiguous values and the link in the parent is updated to point at the "new" end of the arena.
If the parents block is at the end the entire block is coalesced into a single contiguous block.
**** Pros
- Deterministic
- "simple" from the computers POV
- Allows for dynamic memory allocation
**** Cons
- Not flexable in memory method
- Likely slower than RAII
** Computer abstraction
*** Von Neumann
Code and data exist in the same block
Example is the Uxn/Varvara VM
**** Pros
- The most "free" compiler
- Very simple to emit commands
**** Cons
- Need to "jump" over data to avoid it being run as commands
- Functions need to be declared after the "main"
*** Harvard
Seperate code and data
Most other VMs use this
**** Pros
- Safer because code is not operatable
**** Cons
- More complicated compiler for one pass
- Not as flexable

86
test/add-hardcoded.tal Executable file
View File

@ -0,0 +1,86 @@
|100
LIT2r 0000 main POP2r BRK
@main ( -- )
#0001 #0001 add_ nat_to_str_ str/<println>
&return
POP2r JMP2r
@add_ ( a b -- res )
OVR2r LIT2r 0004 SUB2r
STH2kr STA2
STH2kr #0002 ADD2 STA2
STH2kr LDA2 STH2kr #0002 ADD2 LDA2 ADD2
&return
POP2r JMP2r
@sext
#80 ANDk EQU #ff MUL SWP JMP2r
@aalloc_ ( size* -- result* )
OVR2r LIT2r 0004 SUB2r STH2kr INC2 INC2 STA2
;mem_length_ LDA2 STH2kr STA2
;mem_length_ LDA2k STH2kr INC2 INC2 LDA2 ADD2 SWP2 STA2
;mem_ STH2kr LDA2 ADD2 !&return
#0000
&return
POP2r JMP2r
@amcpy_ ( length* from* -- result* )
OVR2r LIT2r 0008 SUB2r STH2kr #0006 ADD2 STA2
STH2kr #0004 ADD2 STA2
STH2kr #0004 ADD2 LDA2 aalloc_
STH2kr INC2 INC2 STA2
#0000 STH2kr STA2
&begin.1
STH2kr LDA2 STH2kr #0004 ADD2 LDA2 LTH2 #00 EQU ?&break.1
STH2kr #0006 ADD2 LDA2 STH2kr LDA2 ADD2 LDA sext
STH2kr INC2 INC2 LDA2 STH2kr LDA2 ADD2 STA
POP
&continue.1
STH2kr LDA2k INC2k ROT2 STA2
POP2 !&begin.1
&break.1
STH2kr INC2 INC2 LDA2 !&return
#0000
&return
POP2r JMP2r
@nat_to_str_ ( n* -- result* )
OVR2r LIT2r 000a SUB2r STH2kr #0008 ADD2 STA2
#0005 STH2kr STA2
&begin.1
STH2kr #0008 ADD2 LDA2 #000a OVR2 OVR2 DIV2 MUL2 SUB2 #0030 ADD2 STH2kr INC2 INC2 STH2kr LDA2k #0001 SUB2 SWP2 STA2k
POP2 ADD2 STA
POP STH2kr #0008 ADD2 LDA2k #000a DIV2 SWP2 STA2
&continue.1
#0000 STH2kr #0008 ADD2 LDA2 LTH2 ?&begin.1
&break.1
#0005 STH2kr LDA2 SUB2 STH2kr INC2 INC2 STH2kr LDA2 ADD2 amcpy_
!&return
#0000
&return
POP2r JMP2r
@str/<print> ( str* -- )
LDAk DUP ?{ POP POP2 JMP2r }
#18 DEO
INC2 !/<print>
@str/<println> ( str* -- )
str/<print> #0a #18 DEO
@mem_length_ #0000
@mem_

View File

@ -5,4 +5,6 @@ function add(int a, int b) int {
return a + b; return a + b;
} }
function main() {
print(add(1, 1) as str); print(add(1, 1) as str);
}

82
test/bang-hardcoded.tal Normal file
View File

@ -0,0 +1,82 @@
|100
LIT2r 0000 main POP2r BRK
@const_str_0 "flag 20 "is 20 "false 0a
@main ( -- )
OVR2r LIT2r 0001 SUB2r
#0000 STH2kr STA2
STH2kr LDA2 #0000 EQU2 #03 JCN ?{ ;const_str_0 str/<print>
!&if_end.0 } &if_end.0 BRK
&return
afree_ POP2r JMP2r
@sext
#80 ANDk EQU #ff MUL SWP JMP2r
%afree_ ( -- ) {
#0000 ;mem_length_ STA2 }
@aalloc_ ( size* -- result* )
OVR2r LIT2r 0004 SUB2r STH2kr INC2 INC2 STA2
;mem_length_ LDA2 STH2kr STA2
;mem_length_ LDA2k STH2kr INC2 INC2 LDA2 ADD2 SWP2 STA2
;mem_ STH2kr LDA2 ADD2 !&return
#0000
&return
POP2r JMP2r
@amcpy_ ( length* from* -- result* )
OVR2r LIT2r 0008 SUB2r STH2kr #0006 ADD2 STA2
STH2kr #0004 ADD2 STA2
STH2kr #0004 ADD2 LDA2 aalloc_
STH2kr INC2 INC2 STA2
#0000 STH2kr STA2
&begin.1
STH2kr LDA2 STH2kr #0004 ADD2 LDA2 LTH2 #00 EQU ?&break.1
STH2kr #0006 ADD2 LDA2 STH2kr LDA2 ADD2 LDA sext
STH2kr INC2 INC2 LDA2 STH2kr LDA2 ADD2 STA
POP
&continue.1
STH2kr LDA2k INC2k ROT2 STA2
POP2 !&begin.1
&break.1
STH2kr INC2 INC2 LDA2 !&return
#0000
&return
POP2r JMP2r
@nat_to_str_ ( n* -- result* )
OVR2r LIT2r 000a SUB2r STH2kr #0008 ADD2 STA2
#0005 STH2kr STA2
&begin.1
STH2kr #0008 ADD2 LDA2 #000a OVR2 OVR2 DIV2 MUL2 SUB2 #0030 ADD2 STH2kr INC2 INC2 STH2kr LDA2k #0001 SUB2 SWP2 STA2k
POP2 ADD2 STA
POP STH2kr #0008 ADD2 LDA2k #000a DIV2 SWP2 STA2
&continue.1
#0000 STH2kr #0008 ADD2 LDA2 LTH2 ?&begin.1
&break.1
#0005 STH2kr LDA2 SUB2 STH2kr INC2 INC2 STH2kr LDA2 ADD2 amcpy_
!&return
#0000
&return
POP2r JMP2r
@str/<print> ( str* -- )
LDAk DUP ?{ POP POP2 JMP2r }
#18 DEO
INC2 !/<print>
@mem_length_ #0000
@mem_

89
test/fib-hardcoded.tal Normal file
View File

@ -0,0 +1,89 @@
|100
LIT2r 0000 main POP2r BRK
@main ( -- )
#0017 fib nat_to_str_ str/<println>
&return
POP2r JMP2r
@fib ( n -- n )
OVR2r LIT2r 0002 SUB2r STH2kr STA2
STH2kr LDA2 #0002 LTH2 #03 JCN !{ STH2kr LDA2 !&return !&if_end.0 }
&if_end.0
STH2kr LDA2 #0002 SUB2 fib
STH2kr LDA2 #0001 SUB2 fib
ADD2
&return
POP2r JMP2r
@sext
#80 ANDk EQU #ff MUL SWP JMP2r
@aalloc_ ( size* -- result* )
OVR2r LIT2r 0004 SUB2r STH2kr INC2 INC2 STA2
;mem_length_ LDA2 STH2kr STA2
;mem_length_ LDA2k STH2kr INC2 INC2 LDA2 ADD2 SWP2 STA2
;mem_ STH2kr LDA2 ADD2 !&return
#0000
&return
POP2r JMP2r
@amcpy_ ( length* from* -- result* )
OVR2r LIT2r 0008 SUB2r STH2kr #0006 ADD2 STA2
STH2kr #0004 ADD2 STA2
STH2kr #0004 ADD2 LDA2 aalloc_
STH2kr INC2 INC2 STA2
#0000 STH2kr STA2
&begin.1
STH2kr LDA2 STH2kr #0004 ADD2 LDA2 LTH2 #00 EQU ?&break.1
STH2kr #0006 ADD2 LDA2 STH2kr LDA2 ADD2 LDA sext
STH2kr INC2 INC2 LDA2 STH2kr LDA2 ADD2 STA
POP
&continue.1
STH2kr LDA2k INC2k ROT2 STA2
POP2 !&begin.1
&break.1
STH2kr INC2 INC2 LDA2 !&return
#0000
&return
POP2r JMP2r
@nat_to_str_ ( n* -- result* )
OVR2r LIT2r 000a SUB2r STH2kr #0008 ADD2 STA2
#0005 STH2kr STA2
&begin.1
STH2kr #0008 ADD2 LDA2 #000a OVR2 OVR2 DIV2 MUL2 SUB2 #0030 ADD2 STH2kr INC2 INC2 STH2kr LDA2k #0001 SUB2 SWP2 STA2k
POP2 ADD2 STA
POP STH2kr #0008 ADD2 LDA2k #000a DIV2 SWP2 STA2
&continue.1
#0000 STH2kr #0008 ADD2 LDA2 LTH2 ?&begin.1
&break.1
#0005 STH2kr LDA2 SUB2 STH2kr INC2 INC2 STH2kr LDA2 ADD2 amcpy_
!&return
#0000
&return
POP2r JMP2r
@str/<print> ( str* -- )
LDAk DUP ?{ POP POP2 JMP2r }
#18 DEO
INC2 !/<print>
@str/<println> ( str* -- )
str/<print> #0a #18 DEO
@mem_length_ #0000
@mem_

View File

@ -1,9 +1,9 @@
/** /**
* Recursively calculate fibonacci * Recursively calculate fibonacci
*/ */
function fib(int n) int { function fib(nat n) nat {
if (n < 2) { return n; } if (n < 2) { return n; }
return fib(n - 2) + fib(n - 1); return fib(n - 2) + fib(n - 1);
} }
print(fib(35) as str); print(fib(23) as str);

14
test/global-hardcoded.tal Normal file
View File

@ -0,0 +1,14 @@
|0100
LIT2r 0000 main POP2r BRK
%putchar ( -- ) {
#18 DEO }
@main ( -- )
OVR2r LIT2r 0004 SUB2r
#007a STH2kr ( #0000 ADD2 ) STA2
#0020 STH2kr #0002 ADD2 STA2
STH2kr ( #0000 ADD2 ) LDA2 STH2kr #0002 ADD2 LDA2 SUB2 putchar
&return
POP2r JMP2r

View File

@ -1,13 +0,0 @@
|100
!{ @i $2 } #0000 ;i STA2
&while.1 [ ;i LDA2 #0008 LTH2 ] #03 JCN !{ ( Loop while iterator is less than limit. )
;i LDA2 print-num ( Run function to print number )
;i LDA2 INC2 ;i STA2 ( incriment counter )
!&while.1 }
BRK ( Halt. )
@print-num ( int -- )
LIT "0 ADD ( Add number to ascii character 0 )
#18 DEO ( Send to Console/write )
JMP2r

20
test/hello-hardcoded.tal Normal file
View File

@ -0,0 +1,20 @@
|0100
LIT2r 0000 main POP2r BRK
@msg 6e 75 71 6e 65 48 20 27 75 27 3f 00
@main ( -- )
OVR2r LIT2r 0002 SUB2r
;msg STH2kr STA2
STH2kr LDA2 str/<println>
&return
POP2r JMP2r
@str/<print> ( str* -- )
LDAk DUP ?{ POP POP2 JMP2r }
#18 DEO
INC2 !/<print>
@str/<println> ( str* -- )
str/<print> #0a #18 DEO

27
test/if-hardcoded.tal Normal file
View File

@ -0,0 +1,27 @@
|100
LIT2r 0000 main POP2r BRK
@const_str_0 78 20 69 73 20 31 30 00
@const_str_1 78 20 69 73 20 32 30 00
@const_str_2 78 20 69 73 20 73 6f 6d 65 74 68 69 6e 67 20 65 6c 73 65 00
@main ( -- )
OVR2r LIT2r 0002 SUB2r
#0014 STH2kr STA2
STH2kr LDA2 #000a EQU2 #03 JCN !{ ;const_str_0 str/<println> !&if_end.0 }
STH2kr LDA2 #0014 EQU2 #03 JCN !{ ;const_str_1 str/<println> !&if_end.0 }
;const_str_2 str/<println>
&if_end.0
&return
POP2r JMP2r
@str/<print> ( str* -- )
LDAk DUP ?{ POP POP2 JMP2r }
#18 DEO
INC2 !/<print>
@str/<println> ( str* -- )
str/<print> #0a #18 DEO

85
test/str-hardcoded.tal Normal file
View File

@ -0,0 +1,85 @@
|0100
LIT2r 0000 main POP2r BRK
@sext
#80 ANDk EQU #ff MUL SWP JMP2r
%afree_ ( -- ) {
#0000 ;mem_length_ STA2 }
@msg 20 64 61 6d 61 67 65 20 69 6e 66 6c 69 63 74 65 64 21 0a 00
@main ( -- )
OVR2r LIT2r 0006 SUB2r
#000e STH2kr ( #0000 ADD2 ) STA2
#0096 STH2kr #0002 ADD2 STA2
STH2kr ( #0000 ADD2 ) LDA2 STH2kr #0002 ADD2 LDA2 MUL2 #0014 DIV2 #0003 SUB2 STH2kr #0006 ADD2 STA2
STH2kr #0006 ADD2 LDA2 nat_to_str_ str/<print>
;msg str/<print>
&return
afree_ POP2r JMP2r
@aalloc_ ( size* -- result* )
OVR2r LIT2r 0004 SUB2r STH2kr INC2 INC2 STA2
;mem_length_ LDA2 STH2kr STA2
;mem_length_ LDA2k STH2kr INC2 INC2 LDA2 ADD2 SWP2 STA2
;mem_ STH2kr LDA2 ADD2 !&return
#0000
&return
POP2r JMP2r
@amcpy_ ( length* from* -- result* )
OVR2r LIT2r 0008 SUB2r STH2kr #0006 ADD2 STA2
STH2kr #0004 ADD2 STA2
STH2kr #0004 ADD2 LDA2 aalloc_
STH2kr INC2 INC2 STA2
#0000 STH2kr STA2
&begin.1
STH2kr LDA2 STH2kr #0004 ADD2 LDA2 LTH2 #00 EQU ?&break.1
STH2kr #0006 ADD2 LDA2 STH2kr LDA2 ADD2 LDA sext
STH2kr INC2 INC2 LDA2 STH2kr LDA2 ADD2 STA
POP
&continue.1
STH2kr LDA2k INC2k ROT2 STA2
POP2 !&begin.1
&break.1
STH2kr INC2 INC2 LDA2 !&return
#0000
&return
POP2r JMP2r
@nat_to_str_ ( n* -- result* )
OVR2r LIT2r 000a SUB2r STH2kr #0008 ADD2 STA2
#0005 STH2kr STA2
&begin.1
STH2kr #0008 ADD2 LDA2 #000a OVR2 OVR2 DIV2 MUL2 SUB2 #0030 ADD2 STH2kr INC2 INC2 STH2kr LDA2k #0001 SUB2 SWP2 STA2k
POP2 ADD2 STA
POP STH2kr #0008 ADD2 LDA2k #000a DIV2 SWP2 STA2
&continue.1
#0000 STH2kr #0008 ADD2 LDA2 LTH2 ?&begin.1
&break.1
#0005 STH2kr LDA2 SUB2 STH2kr INC2 INC2 STH2kr LDA2 ADD2 amcpy_
!&return
#0000
&return
POP2r JMP2r
@str/<print> ( str* -- )
LDAk DUP ?{ POP POP2 JMP2r }
#18 DEO
INC2 !/<print>
@mem_length_ #0000
@mem_

17
test/while-hardcoded.tal Normal file
View File

@ -0,0 +1,17 @@
|100
LIT2r 0000 main POP2r BRK
@main ( -- )
OVR2r LIT2r 0002 SUB2r ( setup number of locals; in this case 1 u16 named 'i' )
#0000 STH2kr STA2 ( store the intial value into i )
&while.1 [ STH2kr LDA2 #0008 LTH2 ] #03 JCN !{ ( Loop while iterator is less than limit. )
STH2kr LDA2 print-num ( Run function to print number )
STH2kr LDA2 INC2 STH2kr STA2 ( incriment counter )
!&while.1 }
@print-num ( int -- )
LIT "0 ADD ( Add number to ascii character 0 )
#18 DEO ( Send to Console/write )
JMP2r