Super
Forth 64 – dynamic load and execution of machine language object.
I
struggled with some project for the past week and finally I figured
it out. Forth system is great to have huge dictionary of commands at
user's disposal, which all reside in memory and are as a result able
to load and execute fast. However it would be also nice to have
ability to load the ML code from disk to memory buffer, execute it
and return control to Forth. After all, you can not stuff everything
in 20+ kB of RAM.
Super
Forth 64 has ability to do just that with dictionary definitions SYS
and SYSCALL:
SYS
(.A .X .Y ADDR --- .A .X .Y STATUS)
Above
word interfaces with ML subroutines which are external to FORTH
system (e.g: C64 KERNAL routines).
.A,
.X and .Y are values which will be loaded to corresponding MOS6510
registers prior to performing JSR to the routine at ADDR.
Upon
return, CPU registers and status word will be put on parameter stack.
The
called routine must end with RTS and the hardware stack must be left
in the same condition as when the routine was first called.
SYSCALL
(.A .X .Y ADDR --- )
Alternative
to SYS, which does not return CPU registers and status on the
parameter stack.
I
wanted my machine language object to be fully relocatable so FORTH
could load it to any memory location that was available, preferably
to predefined buffer. To achieve that, jumps to absolute address
locations within program can not be performed and the access to data
must be performed via zero page vector.
The
design assumes that ML program contains at the beginning 3 bytes long unconditional branch jump to the main routine and between that jump
instruction and the code, data section is located. Byte #4 (index=3,
assuming byte #1 is at offset 0) in ML object contains zero page
address that ML program will use to access its data section. Starting
at byte #5 (index=4 from the beginning of the object) data section is
located.
I
used Turbo Assembler to write my ML demo program and save its object to
disk. It does not matter what the starting address is during
compilation, since program does not use absolute addresses to access
its own data and code. I assembled program to $C000, however it will be
loadable to any address:
;
MODULE: HELLOSF64
;
PURPOSE: THE DEMO OF RELOCATEABLE ML
;
PROGRAM RUNNING IN SUPER
;
FORTH 64 ENVIRONMENT.
;
DATE: 11/05/2012
;
AUTHOR: MAREK KARCZ
ZPGl = $02
CHROUT
= $FFD2
*= $C000
CLC
BCC
MAIN
;
ZERO PAGE POINTER, TO BE READ BY SF64
;
CALLING ROUTINE TO DETERMINE WHERE TO
;
PUT DATA SECTION ADDRESS.
;
CALCULATED AT RUNTIME BY ADDING 3 TO
;
THE PROGRAM LOAD ADDRESS.
ZPGPTR
.BYTE
ZPG1
;
DATA SECTION, CALCULATED BY ADDING 4
;
TO THE PROGRAM LOAD ADDRESS.
DATA
.BYTE
$0D
.TEXT
"HELLO FORTH!"
.BYTE
$0D,$00
.TEXT
"GOODBYE FORTH!"
.BYTE
$0D,$FF
MAIN
LDY
#0
LOOP
LDA
(ZPG1), Y
BEQ
TX2
CMP
#$FF
BEQ
END
JSR
CHROUT
TX2
INY
BNE
LOOP
END
RTS
As
mentioned before, the first byte after branch jump contains zero page
address (ZPGPTR). This is information for Super Forth's run time where ML
program will read its data section vector from. It is important
because Super Forth's routine calling the ML object code will have to
save and then restore values at that memory location, otherwise SF64
will crash.
Now
we need to define framework in SF64 supporting dynamic ML load and
execution and then return to SF64 interpreter. We will need buffer for code (MLBUF), a function that
loads the ML object from disk to the buffer (LOAD-ML) and function
that executes the code (EXEC-ML). The EXEC-ML function saves values
at zero page location that will be used by ML program as a data
vector, then loads to that location the calculated data section of
the ML program thus creating the actual data section vector for a ML
program. Then the function executes ML code and upon return restores
zero page address to its former values. Below is the FORTH code:
Load above definitions with:
1
LOAD
Please see screenshots below from Turbo Assembler and
SF64 sessions testing the concept:
Turbo Assembler - program code edit. |
Code assembled to disk file. |
FORTH code loaded. |
ML code buffer properly initialized. |
Switch to disk #9 where ML object resides. |
ML object loaded and MLBUF dumped for verification. |
...and execute - Success! |
Both functions (loading and executing) can be of course combined into one. Also, the ML object name prompt can be replaced with reading file name from standard stream used by FORTH. Anyway, concept is proven to work and after refinement can be a valuable tool while working in FORTH's environment.
Thanks for visiting my blog.
Marek Karcz
11/5/2012