MINT - a Minimal Interpreter for resource limited cpus

MINT is also available written in C by Jason C.J. Tay.

He has ported it to several processors including 8051, ESP-32, ARM M0, ARM M4, RISC-V, AVR, dsPIC and provided benchmarks for 1 million empty loops timed using the millisecond command.

The source code and benchmark results are in the readme of the following Github repo:

No word from AGSB yet, but Iā€™m thinking of working in the truest spirit of ā€œminimalā€ and implementing a version that does not include Johnā€™s ā€œalternateā€ command set. My holy grail of one KB is still a possibility for the ROMable executable, but itā€™s obviously going to need several hundred bytes of RAM for the stacks, TIB, global variables and function pointers. The rest of whatever RAM is available can be allocated to the heap. No garbage collection will initially be offered, so the more the merrier.

Ken, as far as you know, is MINT horribly crippled in any way without Johnā€™s extra ā€œop-codesā€? I want to at least execute your Christmas tree code and experiment with some small games of skill or chance. Maybe even plot a Mandelbrot or calculate a few dozen digits of pi ā€¦

1 Like

Good morning Michael,

Happy Thanksgiving weekend.

John and I have recently discussed the direction of travel of MINT.

John wants to extend the language with new features, however as a hardware engineer, I want to reduce the primitive set, to something I can actually implement in hardware or on a typical 1970s 8-bit cpu.

We are actively talking on how best to manage this divergence.

I am reviewing the MINT instruction set, and deciding what are genuine primitives, and what are convenient constructs.

I wish to retain the essential primitives, such as ADD, SUB, AND, OR, XOR (plus 20 others) - which are easily implemented on any 8-bit cpu in assembly language.

The constructs, such as loops, arrays, switch statements, number base conversion and character input and output are not common instructions on most cpus - and we are looking to implement these using small routines composed of MINT primitives.

This will reduce the size of the MINT core, and thus simplify the task of porting to other processors.

This task is not without precedent, Charles Moore, (Novix NC4016) Dr. C.H. Ting (eForth) and James Bowman (J1 Forth CPU) all used a similar approach to build a full featured Forth on top of a CPU with a minimal instruction set. (MISC).

Over the next couple of months, using the current Z80 MINT implementation as the target, we hope to strip out the constructs, and leave a basic instruction set, which will then be easier to code into alternative targets.

This is an open discussion - so if you have any ideas to share, please feel free to proffer them.

Cheers, Ken.

Although I firmly share your opinion, I donā€™t think that itā€™s impossible for two versions (mini and deluxe) to co-exist. As long as well-behaved mini code executes unchanged on the deluxe version without blowing up, and deluxe code errors out gracefully on the mini version, Iā€™m completely on board.

This is not unlike the situation with VTL-2. The original 6800 version from 1977 didnā€™t have the performance enhancements and additional features of VTL02C, but VTL02C will correctly execute well-behaved VTL-2 code from deep inside the last century (for some reasonable definition of well-behaved). Unfortunately, VTL doesnā€™t know what an ā€œerrorā€ is, so executing VTL02C code on the earlier versions can easily result in nasal demons or other undefined behavior.

Michael,

I want to create a subset of the MINT primitives - for obscure reasons, which I will disclose later, this subset will be known as MORK (as in Mork and Mindy).

MORK only contains the instructions that you would find in a typical 8-bit micro, so it massively simplifies the VM. There are about 25 but here are the principal:

Arithmetic:

ADD
SUB
NEG
SHL
SHR
EQ Equality

Logic:

AND
OR
XOR

CALL
RET
JMP conditional

Memory and registers:

FETCH
STORE

Stack:

DUP
DROP
SWAP
OVER

HEX - swap BASE from 10 to 16

I/O:

IN
OUT

Misc:
BYTE Op

Loops, arrays, switch statements, multiply, divide, hex and decimal input and output conversion and printing are all written in Mork primitives

I have a word based cpu design a tad more adavanced than a PDP 8.
The alu primitives are NOT, ADD,SUB,ADC,SBC,AND,OR,LD,ST.
SZR,SCR,SZL,SCL and ASSORTED jumps and INDIRECT.
Leaving out a carry flag of some kind, seems like a sin that all high level
languges do. Adding in ADC,SBC and SCR,SCL is only 4 more primitives
and gives you a extendable word length.
If it where not for the 64kb limit of 8/16 bit cpuā€™s, it would be nice to able to assign
variable lengths for variables. pi:1000000 for 1 milliion digits of pi.
I hope MORK catches on, programing in KLINGON is a pain.
Ben.

I have taken a second look at that and come to the realization that this is not the most efficient way to do it on the 6502, because the RTS dispatch technique wants the high address rather early in the process. My new (untested) method is similar, but uses the MINT ā€œop-codeā€ to divide the primitive code block roughly in half, using the result of the compare to decide on page 4 or page 5:

; *************************************************************
; Dispatch Routine.
; 
; Increment the Instruction Pointer.
; Get the next char, look up the primitive code offset address
;   from the opcode table.  Push the handler routine address-1
;   and rts to it like it's 1977, with the char still in y.
; Individual handler routines will deal with each category:
; 1. Detect chars A-Z and jump to the User Command handler
; 2. Detect chars a-z and jump to the variable handler
; 3. Remaining punctuation chars jump to the associated
;   primitive code.
; *************************************************************
next:                   ; 16 bytes
    jsr  ipfetch        ; Get the next char @ (++IP)
    tay                 ; Index for low jump address table
    lda  #>page4        ; high jump address
    cpy  #"0"           ; split primitives at '0' ('/' is in
    adc  #0             ;   "page4", '0' is in following page)
    pha                 ; push it
    lda  opcodes,y      ; look up low jump address-1
    pha                 ; push it
    rts                 ; Jump to the handler routine
; *************************************************************

I can adjust the split point easily if necessary. Itā€™s not amazingly fast, but itā€™s compact, and quicker than a CASE ladder, typically. And those padding NOPs were mildly irritating ā€¦

Michael,

It sounds like you have found a good compromise solution.

I was not aware of the rts technique, but then I donā€™t have a 6502 background.

Iā€™m assuming that you test for numbers too, and have a separate handler for them.

The ā€œWhen is an RTS a JSRā€ technique was used by Woz for Sweet16. Not the fastest but very compact - the down-side was that fixed ā€œpageā€ variable which made porting Sweet16 to other platforms somewhat tricky - and that all the code had to live inside one 256 byte page of RAM, or branch out for more space. But Woz (and MB) are masters of squeezing every byte so that works :slight_smile:

-Gordon

2 Likes

I want to create a subset of the MINT primitives - for obscure reasons, which I will disclose later, this subset will be known as MORK (as in Mork and Mindy).

Loops, arrays, switch statements, multiply, divide, hex and decimal input and output conversion and printing are all written in Mork primitives

This the more Forthy way. If you look in FigForth and eForth for example, much of the language, including the interpreter, is coded in Forth. Itā€™s precompiled Forth defined as a string of addresses to consecutive Forth word definitions. You could lift nearly all of what you want from there. It makes porting to new architectures easy.

Paul

BTW, can you explain whatā€™s going on here with these 12 bytes?

; ****************************************************************
; DEFS Table - holds 26 addresses of user routines
; ****************************************************************
.align $40
.org $-12
DS 12 ; vars for group 0
defs: DS GRPSIZE * NUMGRPS

They immediately precede the NUMGRPS multiple of the user definition pointers. How do you know that backing up 12 bytes doesnā€™t collide with the user variables area before it?

Also, there are some rats and mice after the system variables. Can you explain what they are?

            DS 2                ; 
vByteMode:  DS 2                ; 
            DS $30
tbPtr   :   DS 2                ; reserved for tests

Can you also briefly describe how to use groups?

Thanks,

Paul

@pdr0663 Interesting questions, for which I donā€™t have any convenient answers. Maybe someone with better carnal knowledge of the Z80 version can chime in.

I let my 6502 port slip to the back burner before I found a need to address those concerns, due to a bunch of other stuff competing for my attention, but I posted this thread back to the top to tell Ken that AGSB finally e-mailed me about a possible collaboration. I explained to him that when I finally get back on track Iā€™ll be going for the bare minimum, and that I wonā€™t be sharing any code publicly without checking in with him and Ken first.

AGSB has a full version in the works, with all of Johnā€™s bells and whistles, far more than I was attempting:

If I ever get back to cooking, mine will still be MINT, in the same way that TINY BASIC is still BASIC. At least thatā€™s the plan for now ā€¦