Jump to content
Lee Stewart

fbForth—TI Forth with File-based Block I/O [Post #1 UPDATED: 04/13/2021]

Recommended Posts

10 hours ago, TheBF said:

I took a look at the TF loader. Are these object files relocatable?

I think I get how Mark's loader works, but I don't understand how it would relocate.

(I have some ideas on how to do the loading with my favourite /STRING word.) :) 

 

They are not relocatable. Implementing relocatable code is a good bit more complicated. The only tags implemented are those associated with

  • AORG.................9
  • DATA.................B
  • BYTE.................B
  • BSS..................B
  • DEF..................6
  • <checksum>...........7—the checksum itself is ignored
  • <end-of-file text>...:

 

All other tagged words are ignored.

 

...lee

  • Like 2

Share this post


Link to post
Share on other sites
52 minutes ago, Lee Stewart said:

 

They are not relocatable. Implementing relocatable code is a good bit more complicated. The only tags implemented are those associated with

  • AORG.................9
  • DATA.................B
  • BYTE.................B
  • BSS..................B
  • DEF..................6
  • <checksum>...........7—the checksum itself is ignored
  • <end-of-file text>...:

 

All other tagged words are ignored.

 

...lee

OK good to know.

I started studying this tag meanings in the E/A manual and made a rather large case statement.

I will prune it with your information.

 

To further check my assumptions, would a workable strategy be to load the object code into the dictionary and but do some computation on the addresses so they are correct for the location you put them at?

The objective would be to allow the Forth system to take code that was assembled for ORG = X   but re-jig it so it can load at HERE but re-compute the all the addresses to be HERE relative.

 

It would give Forth the ability to use external code modules. :)

 

  • Like 2

Share this post


Link to post
Share on other sites
4 hours ago, TheBF said:

The objective would be to allow the Forth system to take code that was assembled for ORG = X   but re-jig it so it can load at HERE but re-compute the all the addresses to be HERE relative.

 

Your loader would need to be able to understand all instructions with address references—possible, but complicated, with branch statements and statements that use symbolic address references, but I do not think possible with other uses of address labels.

 

The only reason relocatable object code can be managed (and fairly easily, at that) is that the assembler marks all relocatable addresses in the tagged object code with segment addresses and offsets in a linked chain for each unique, relocatable address reference. The loader merely needs to walk each of those chains backwards to replace the segment+offset with the relocated addresses.

 

For REF, prior loading of tagged object code containing the relevant DEF labels (per @Willsy’s loader) would provide the necessary variable definitions with the relevant addresses.

 

Regarding using DEF and REF the way I implied in my earliest post on this topic, DEF would need to define the relevant word to contain the address of the DEFed label rather than the data at that address. I suppose you could flag the loader to manage DEFs as EXECUTE objects (CFAs per @Willsy’s loader) or direct branches (BL, BLWP) that would return where they left. @Willsy’s use of CFAs is probably wisest because it makes the code part of Forth. The only down side to this is that you must have the original ALC, which must be modified to have DEFed labels contain the address of the executable code rather than its own address and the return changed to B *NEXT to get back to the Forth environment.

 

I am sure I have oversimplified (misconstrued?) some of the above, but it could be a fun project. At the very least, these musings provide something of a springboard for going forward,

 

...lee

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

Yes I came to that conclusion too, that I would need to look ahead at some instructions to find B and BL at a minimum to recompute those addresses.

 

Now that I have slightly better understanding I find myself wondering if the REF/DEF stuff could be done with a vocabulary for all DEFs.

When you encounter a 6 tag you: 

1. Extract the name from the object code

2. Search the DEF vocabulary for the tag..

3. If found it means there is already a reference to this code so take the address of that reference and patch it into the address of this new DEF. 

 

Not sure I am getting this yet. :)  That logic might be faulty.

 

Alternatively a DEF vocabulary does give you a linked list of all the names that you can walk and do the relocation updates. Maybe? 

 

I think it would easier if the TI assembler used more of those tags to give us more context.

 

  • Like 1

Share this post


Link to post
Share on other sites
20 hours ago, TheBF said:

I took a look at the TF loader. Are these object files relocatable?

I think I get how Mark's loader works, but I don't understand how it would relocate.

(I have some ideas on how to do the loading with my favourite /STRING word.) :) 

Relocatable is the desired feature, yes. 
 

For symbols within the object file, there is a tag that means “add the base address to me”. 
 

Bonus features:

 

1. Create constants on the dictionary for each DEF table entry. Then caller says

 

[ THESYM ] BLWP,

 

(I am not familiar with the forth assembler) 

 

2. Resolve REF table entries by looking up constants

 

  • Like 1

Share this post


Link to post
Share on other sites

I took a run at this with the CAMEL Forth code.

Below is the logic used to deal with DEF/REF resolution.  I used a vocabulary 

 

I init the memory space to >FFFF and I stopped setting the tag 6 entries to zero.

I use that >FFFF to determine if the address has been resolved. I guess I assumed that DATA and BYTE statements will not init to >FFFF so that's flawed.

 

The ANS Forth search order control is handy with the PREVIOUS word.

 

This code does what I expect with the test object file. Do you think this close to what is required?

: DEF/REF ( addr len -- )
        PARSE# >R         ( -- addr' len') ( r: -- ref_addr)
        ALSO REFS DEFINITIONS
        /TAG 6 CHOP -TRAILING FIND-NAME ( -- nfa)
        ?DUP
        IF  ( Reference found)
           [email protected]  @ TRUE =   \ fetch the ref_addr to see if UN-resolved
           IF  ( empty reference )
                NFA>BODY @  R>  !       \ store NEW address
           ELSE
                R> DROP   DROP
           THEN
        ELSE ( new reference)
           HEADER,  COMPILE DOCON R> ,  \ make a Forth Constant
        THEN
        PREVIOUS DEFINITIONS
;

It also means I can do this: 

\ create some external system references
ALSO REFS DEFINITIONS
HEX
8300 CONSTANT PAD
83E0 CONSTANT GPLWS
8400 CONSTANT SOUND
8800 CONSTANT VDPRD
8802 CONSTANT VDPSTA
8C00 CONSTANT VDPWD
9000 CONSTANT SPCHRD
9400 CONSTANT SPCHWT
9800 CONSTANT GRMRD
9802 CONSTANT GRMRA
9C00 CONSTANT GRMWD
9C02 CONSTANT GRMWA
000E CONSTANT SCAN   \ key scan entry
0020 CONSTANT BREAK  \ break key sub-routine

DEAD CONSTANT KSCAN  \ dummies for testing DSK2.TEST-OBJ
BEEF CONSTANT VBLANK
AABB CONSTANT KSTAT

PREVIOUS DEFINITIONS
Spoiler
.( EA3 object file loader Aug 7 2021 Fox)

NEEDS WORDLIST FROM DSK1.WORDLISTS

ONLY FORTH ALSO DEFINITIONS
NEEDS .S        FROM DSK1.TOOLS
NEEDS +TO       FROM DSK1.VALUES
NEEDS CASE      FROM DSK1.CASE
NEEDS -TRAILING FROM DSK1.TRAILING
NEEDS READ-FILE FROM DSK1.ANSFILES
NEEDS S=        FROM DSK1.COMPARE

VOCABULARY REFS

FORTH DEFINITIONS
DECIMAL
0 VALUE #1  \ a file handle

: NFA>BODY ( addr -- addr')  NFA>CFA >BODY ;
: 2OVER    ( a b c d -- a b c d a b) 3 PICK  3 PICK ;
: 2NIP     ( a b c -- c)  NIP NIP ;

: FIND-NAME ( addr len -- nfa ) \ nfa is "name field address"
           CONTEXT @ @  ( -- NFA )
           BEGIN DUP
           WHILE ( tos<>0)
              DUP 1+ 2OVER S=
           WHILE ( compare<>0)
              NFA>LFA @   ( follow link to next name)
           REPEAT
           THEN 2NIP  ;

\ heap memory management
: HEAP! ( addr -- ) H ! ;  \ set heap pointer
: HEAP   ( -- addr) H @ ;  \ current heap pointer
: HALLOT ( n -- )  H +! ;  \ move heap pointer
: HEAP,  ( n -- )  HEAP ! 2 HALLOT ; \ compile n into heap

HEX
: NEW.   2000 HEAP!  HEAP 2000 FF FILL ;

\ string utilities
: CHOP   ( addr len n --  addr' len' addr2 len2 )
          >R                  \ Rpush n
          2DUP DROP [email protected]        \ dup $, do left$
          2SWAP               \ put original $ on top
          R> 1- /STRING       \ cut remainder string, leave tag at front
          2SWAP               \ put chopped string (output) on top
;

: /TAG     ( addr len -- addr' len') 1 /STRING ; \ cut tag character

: PARSE# ( addr len -- n )
        BASE @ >R
        HEX /TAG  4 CHOP NUMBER? ABORT" Bad number"
        R> BASE ! ;

: DEF/REF ( addr len -- )
        PARSE# >R         ( -- addr' len') ( r: -- ref_addr)
        ALSO REFS DEFINITIONS
        /TAG 6 CHOP -TRAILING FIND-NAME ( -- nfa)
        ?DUP
        IF  ( Reference found)
           [email protected]  @ TRUE =   \ fetch the ref_addr to see if UN-resolved
           IF  ( empty reference )
                NFA>BODY @  R>  !       \ store NEW address
           ELSE
                R> DROP   DROP
           THEN
        ELSE ( new reference)
           HEADER,  COMPILE DOCON R> ,  \ make a Forth Constant
        THEN
        PREVIOUS DEFINITIONS
;

VARIABLE PROGLENGTH
CREATE PROGNAME  10 ALLOT

: PROG-ID  ( addr len -- addr len)
          PARSE# PROGLENGTH !
          8 CHOP  PROGNAME PLACE ;

: .TOOLVER  ( addr len -- addr 0)
          /TAG  40 CHOP -TRAILING CR TYPE  DROP 0 ;

: ParseLine ( add len -- )
      BEGIN
        DUP ( len<>0)
      WHILE
        OVER [email protected] ( 1stChar)
        CASE
          [CHAR] 0 OF  PROG-ID        ENDOF
          [CHAR] 6 OF  DEF/REF        ENDOF
          [CHAR] 7 OF  DROP 0         ENDOF
          [CHAR] 9 OF  PARSE# HEAP!   ENDOF
          [CHAR] B OF  PARSE# HEAP,   ENDOF
          [CHAR] : OF  .TOOLVER       ENDOF
        ENDCASE
        1 /STRING 0 MAX   \ advance to next char
     REPEAT
     2DROP ;

: ?PATH ( addr len -- addr len)
       2DUP [CHAR] . SCAN NIP 0= ABORT" Path expected" ;

DECIMAL
: EA3LOAD ( "DSKx.FILE" -- )
      ?PATH DV80 R/O OPEN-FILE ?FILERR  TO #1
      NEW.
      BEGIN
         #1 EOF
      0= WHILE
         PAD DUP 80 #1 READ-LINE ( pad len ? ior) ?FILERR  DROP
       ( pad len ) ParseLine
      REPEAT
      #1 CLOSE-FILE ABORT" CLOSE error"
;

\ create some external system references
ALSO REFS DEFINITIONS
HEX
8300 CONSTANT PAD
83E0 CONSTANT GPLWS
8400 CONSTANT SOUND
8800 CONSTANT VDPRD
8802 CONSTANT VDPSTA
8C00 CONSTANT VDPWD
9000 CONSTANT SPCHRD
9400 CONSTANT SPCHWT
9800 CONSTANT GRMRD
9802 CONSTANT GRMRA
9C00 CONSTANT GRMWD
9C02 CONSTANT GRMWA
000E CONSTANT SCAN   \ key scan entry
0020 CONSTANT BREAK  \ break key sub-routine

DEAD CONSTANT KSCAN  \ dummies for testing DSK2.TEST-OBJ
BEEF CONSTANT VBLANK
AABB CONSTANT KSTAT

PREVIOUS DEFINITIONS
.( Usage: S" DSKx.FILENAME" EA3LOAD)

 

 

  • Like 1

Share this post


Link to post
Share on other sites
3 minutes ago, FarmerPotato said:

Relocatable is the desired feature, yes. 
 

For symbols within the object file, there is a tag that means “add the base address to me”. 
 

Bonus features:

 

1. Create constants on the dictionary for each DEF table entry. Then caller says

 

[ THESYM ] BLWP,

 

(I am not familiar with the forth assembler) 

 

2. Resolve REF table entries by looking up constants

 

The version I posted here is maybe close.

It does have a lot of what you want.

 

The references are in a separate vocabulary.

I need to learn more about that other tag. Is it supported in the Assembler?

Can you Assemble your routines into an object file and posted it or PM it to me?

Calling is pretty simple if you never want to come back.

 

MYREF @@ B,  

If they are sub-routines end them with B *R11.

Call from FbForth with:

 

ASM: NEWORD     MYREF @@ BL,   ;ASM

 

  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, TheBF said:
\ create some external system references 
ALSO REFS DEFINITIONS
HEX 
8300 CONSTANT PAD 
83E0 CONSTANT GPLWS
\ ...

 

 

You should not need to anticipate these references. Simply include them as EQUates and, if they are used, they will appear tagged with ‘6’ at the end of the tagged object file and will be created by your loader’s code.

 

...lee

Share this post


Link to post
Share on other sites
17 minutes ago, Lee Stewart said:

 

You should not need to anticipate these references. Simply include them as EQUates and, if they are used, they will appear tagged with ‘6’ at the end of the tagged object file and will be created by your loader’s code.

 

...lee

Ok. So they would come along for the ride if the program uses them. That's easier.

 

If you get  a minute, do you think the logic in that DEF/REF word looks correct?

I am out of my depth on how these things actually work. 

 

Share this post


Link to post
Share on other sites
2 hours ago, TheBF said:

If you get  a minute, do you think the logic in that DEF/REF word looks correct?

I am out of my depth on how these things actually work. 

 

Surely. I am treading water here a bit myself. 

 

...lee

Share this post


Link to post
Share on other sites

Ok. We can shuffle along together then. :)

So one thing you could do better than me by far is provide some normal ALC code assembled to obj file.

If you already have some code that consists of a few sub-routines we should be able to figure out how well these ideas work to make callable words from normal ALC code.

Then whatever we get working we can port to both systems.

The code will diverge in file words and dynamic word creation but the other stuff can be quite similar I think.

 

It's always been a weakness of Forth that it doesn't play nice in the sandbox with other languages so this might be a way to fix that.

In theory we might even be able to take GCC output, if it can generate TI object code.

 

  • Like 2

Share this post


Link to post
Share on other sites
14 hours ago, TheBF said:

Ok. We can shuffle along together then. :)

 

The attached ALC from Thierry Nouspikel’s disassembly of the Editor/Assembler utilities may aid in our effort:

 

EditorAssemblerLoMemUtilities_Nouspikel.a99

 

I changed all register references from numbers to ‘R’ notation. Hopefully, I got them all without screwing up non-register numbers.

 

The loader starts at A23BA. The tag jump table is near the end at A2662. You probably guessed correctly that the labels are the actual addresses of the assembled code with a prefix of ‘A’.

 

...lee

  • Like 1

Share this post


Link to post
Share on other sites
1 minute ago, Lee Stewart said:

 

The attached ALC from Thierry Nouspikel’s disassembly of the Editor/Assembler utilities may aid in our effort:

 

EditorAssemblerLoMemUtilities_Nouspikel.a99 21.57 kB · 1 download

 

I changed all register references from numbers to ‘R’ notation. Hopefully, I got them all without screwing up non-register numbers.

 

The loader starts at A23BA. The tag jump table is near the end at A2662. You probably guessed correctly that the labels are the actual addresses of the assembled code with a prefix of ‘A’.

 

...lee

Thank you. This will be just what I needed to get some corner cases.

 

I am just writing up a test I did with a tiny program found on the Assembly topic by mathew180.

I am making progress. :)

 

  • Like 1

Share this post


Link to post
Share on other sites

TI 99 Object code load and run from Forth.

 

With @mathew180 's little training program plus a couple of lines of code the loader works.

We need AORG >2000   because Forth takes offense if we load code on top of it. :) 

And to return to Forth we just B *R10. 

We can do this because CAMEL99 does not use R0,R1,R2 or R3.

 

* Training program by @mathew180
        AORG >2000
        
        DEF  START

* VDP Memory Map
*
VDPRD   EQU  >8800       * VDP read data
VDPSTA  EQU  >8802       * VDP status
VDPWD   EQU  >8C00       * VDP write data
VDPWA   EQU  >8C02       * VDP set read/write address

* Workspace
WRKSP   EQU  >8300       * Workspace
R0LB    EQU  WRKSP+1     * R0 low byte reqd for VDP routines

* Program execution starts here
START   LIMI 0
*        LWPI WRKSP
        CLR  R0                * Set the VDP address to zero
        MOVB @R0LB,@VDPWA      * Send low byte of VDP RAM write address
        ORI  R0,>4000          * Set read/write bits 14 and 15 to write (01)
        MOVB R0,@VDPWA         * Send high byte of VDP RAM write address

        LI   R1,>2000          * Set high byte to 32 (>20)
        LI   R2,768            * Set every screen tile name to >20
CLS     MOVB R1,@VDPWD    * Write byte to VDP RAM
        DEC  R2
        JNE  CLS
        B    *R10              * return to Forth

        END

The DEF START lines creates the Forth word START . It is put into a separate name space called REFS just for assembly language labels. (Maybe that should be called DEFS ?) 

 

To run START we wrap in a Forth Code definition:

ALSO ASSEMBLER ALSO REFS   ( expose the name spaces to Forth)

CODE  CLEAR     START @@ B,   ENDCODE 

 

 

 

 

  • Like 1

Share this post


Link to post
Share on other sites

One thing I didn't know.

Object files are are DISPLAY FIXED 80  format.

Had to change that in the loader code as well. 

Share this post


Link to post
Share on other sites

Lee,

 

Your code assembled and the loader seems to have loaded it.

I looked a the table at the upper end of low ram and it looks correct.

I removed the extra logic that I put in yesterday in the DEF/REF word.

I will address that when something stops working. 

 

So we can do AORG >2000 programs or AORG >E000 for short programs.

In we invoke SAMS memory we could put a lot of CODE in low RAM and make the Forth wrapper do the page selection before calling it.

 

Spoiler has the working version which includes the assembler now as well.

Spoiler
CR .( EA3 object file loader Aug 7 2021 Fox)

NEEDS WORDLIST FROM DSK1.WORDLISTS

VOCABULARY REFS
VOCABULARY ASSEMBLER

ONLY FORTH DEFINITIONS
NEEDS .S        FROM DSK1.TOOLS
NEEDS +TO       FROM DSK1.VALUES
NEEDS CASE      FROM DSK1.CASE
NEEDS -TRAILING FROM DSK1.TRAILING
NEEDS READ-FILE FROM DSK1.ANSFILES
NEEDS S=        FROM DSK1.COMPARE


ALSO ASSEMBLER DEFINITIONS
INCLUDE DSK1.ASM9900


ONLY FORTH DEFINITIONS
DECIMAL
0 VALUE #1  \ a file handle

: NFA>BODY ( addr -- addr')  NFA>CFA >BODY ;
: 2OVER    ( a b c d -- a b c d a b) 3 PICK  3 PICK ;
: 2NIP     ( a b c -- c)  NIP NIP ;

: FIND-NAME ( addr len -- nfa ) \ nfa is "name field address"
           CONTEXT @ @  ( -- NFA )
           BEGIN DUP
           WHILE ( tos<>0)
              DUP 1+ 2OVER S=
           WHILE ( compare<>0)
              NFA>LFA @   ( follow link to next name)
           REPEAT
           THEN 2NIP  ;

\ heap memory management
: HEAP! ( addr -- ) H ! ;  \ set heap pointer
: HEAP   ( -- addr) H @ ;  \ current heap pointer
: HALLOT ( n -- )  H +! ;  \ move heap pointer
: HEAP,  ( n -- )  HEAP ! 2 HALLOT ; \ compile n into heap

HEX
: NEW.   2000 HEAP!  HEAP 2000 FF FILL ;

\ string utilities
: CHOP   ( addr len n --  addr' len' addr2 len2 )
          >R                  \ Rpush n
          2DUP DROP [email protected]        \ dup $, do left$
          2SWAP               \ put original $ on top
          R> 1- /STRING       \ cut remainder string, leave tag at front
          2SWAP               \ put chopped string (output) on top
;

: /TAG     ( addr len -- addr' len') 1 /STRING ; \ cut tag character

: PARSE# ( addr len -- n )
        BASE @ >R
        HEX /TAG  4 CHOP NUMBER? ABORT" Bad number"
        R> BASE ! ;

: DEF/REF ( addr len -- )
        PARSE# >R         ( -- addr' len') ( r: -- ref_addr)
        REFS DEFINITIONS
        /TAG 6 CHOP 2DUP CR ." DEF: " TYPE  -TRAILING
        HEADER,  COMPILE DOCON R> ,  \ make a Forth Constant
        FORTH DEFINITIONS
;

VARIABLE PROGLENGTH
CREATE PROGNAME  10 ALLOT

: PROG-ID  ( addr len -- addr len)
          PARSE# PROGLENGTH !
          8 CHOP  PROGNAME PLACE ;

: .TOOLVER  ( addr len -- addr 0)
          /TAG  40 CHOP -TRAILING CR TYPE  DROP 0 ;

: ParseLine ( add len -- )
      BEGIN
        DUP ( len<>0)
      WHILE
        OVER [email protected] ( 1stChar)
        CASE
          [CHAR] 0 OF  PROG-ID        ENDOF
          [CHAR] 6 OF  DEF/REF        ENDOF
          [CHAR] 7 OF  DROP 0         ENDOF
          [CHAR] 9 OF  PARSE# HEAP!   ENDOF
          [CHAR] B OF  PARSE# HEAP,   ENDOF
          [CHAR] : OF  .TOOLVER       ENDOF
        ENDCASE
        1 /STRING 0 MAX   \ advance to next char
     REPEAT
     2DROP ;

: ?PATH ( addr len -- addr len)
       2DUP [CHAR] . SCAN NIP 0= ABORT" Path expected" ;

DECIMAL
: DIS/FIX    DISPLAY SEQUENTIAL 80 FIXED ; \ TI ASM object file format

: EA3LOAD ( "DSKx.FILE" -- )
      ?PATH DIS/FIX  R/O OPEN-FILE ?FILERR  TO #1
      NEW.
      BEGIN
         #1 EOF
      0= WHILE
         PAD DUP 80 #1 READ-LINE ( pad len ? ior) ?FILERR  DROP
       ( pad len ) ParseLine
      REPEAT
      #1 CLOSE-FILE ABORT" CLOSE error"
;

.( Usage: S" DSK?.FILENAME" EA3LOAD)

 

 

 

image.thumb.png.651dc866ce4b819015eb918b4110505c.png

  • Like 1

Share this post


Link to post
Share on other sites
13 hours ago, idflyfish said:

Hi Lee,

I just received my copy of the fbForth 2.0 Manual and thumbed through it. Fantastic job sir!

 

Thank you, kindly. I wondered where you might have gone, but then I saw your signature.

 

...lee

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...