Monday, November 5, 2012

Super Forth 64 – dynamic load and execution of machine language object.


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