Fleet Forth design considerations

Topics relating to various Forth models on the 6502, 65816, and related microprocessors and microcontrollers.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

I'm still working on getting multitasking and vectored I/O playing nice together. One of my test tasks is a clock that displays the time in the top right corner of the screen and I don't want the time to be printed when I am listing something or logging a session. I had thought about user deferred words but really didn't like the idea much (the user area for each task would increase.)
I'm thinking of trying something like this:

Code: Select all

' NOOP VALUE CIO  // CURRENT I/O

SCR# 32 
// CONSOLE PRINTER
: CONSOLE  ( -- )
   [ LATEST NAME> ] LITERAL IS CIO
   #LP CLOSE  CLRCHN
   ['] (EMIT) IS EMIT
   ['] (TYPE) IS TYPE
   ['] (QTYPE) IS QTYPE
   ['] (EXPECT) IS EXPECT ;
: PRINTER  ( -- )
   [ LATEST NAME> ] LITERAL IS CIO
   #LP CLOSE
   0 0  #LP DUP #LP2 (OPEN) IOERR
   ['] (PEMIT) IS EMIT
   ['] (PTYPE) IS TYPE
   ['] (PQTYPE) IS QTYPE
   ['] (EXPECT) IS EXPECT ;

SCR# 33 
// LOGGER
: LOGGER  ( -- )
   PRINTER
   [ LATEST NAME> ] LITERAL IS CIO
   ['] (LEMIT) IS EMIT
   ['] (LTYPE) IS TYPE
   ['] (LQTYPE) IS QTYPE
   ['] (LEXPECT) IS EXPECT ;
CONSOLE
CIO is a value that holds the CFA of the current I/O redirection word. Each of the three I/O redirection words in Fleet Forth stores the address of it's own CFA in CIO. If a background task has to temporarily redirect I/O, it could do something like this:

Code: Select all

... SINGLE CIO
   <REDIRECT I/O AND DO SOMETHING>
   EXECUTE  MULTI PAUSE ...
If a background task is setup for running one shot routines as needed and is normally asleep until activated, it could run a word with something like this:

Code: Select all

... SINGLE CIO
   <REDIRECT I/O AND DO SOMETHING>
   EXECUTE  MULTI STOP ;
whartung
Posts: 1004
Joined: 13 Dec 2003

Re: Fleet Forth design considerations

Post by whartung »

You're not content with some kind of semaphore system protecting the resource? (Printer, screen, etc.)
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

whartung wrote:
You're not content with some kind of semaphore system protecting the resource? (Printer, screen, etc.)
I haven't added semaphores to the multitasker yet, but my understanding of their use is if you have more than one task ( or computer) and a single shared resource ( such as a printer), each task gets exclusive use until it finishes on a first access, first serve basis. In my system, the programmer ( the main or foreground task) has full use of the screen and printer. TYPE and EMIT use the Commodore 64 kernel routines for output. Here are the I/O redirection words as they were without multitasker support.

Code: Select all

SCR# 2E 
// OUTPUT REDIRECTION
HEX
// PRINTER DEVICE NUMBER
4 VALUE #LP
// PRINTER SECONDARY ADDRESS
0 VALUE #LP2
: (PEMIT)  ( C -- )
   #LP (CHKOUT) IOERR
   (EMIT) CLRCHN ;
: (PTYPE)  ( ADR U -- )
   #LP (CHKOUT) IOERR  (TYPE) ;

SCR# 2F 
// <PQTYPE>
HEX
: <PQTYPE>  ( ADR CNT -- )
   #LP (CHKOUT) IOERR
   >ASSEM
   2 # LDA,  SETUP JSR,
   BEGIN,
      BEGIN,
         N CPY,
         0= IF,
            N 1+ LDA,
            0= IF,
               ' CLRCHN @ JMP,
            THEN,
            N 1+ DEC,
         THEN,

SCR# 30 
// <PQTYPE> (PQTYPE)
         N 2+ )Y LDA,
         BL # CMP, CS IF,
            7B # CMP, CS IF,
               0C1 # CMP, CS IF,
               0DB # CMP, CS ELIF,
         CS-ROT THEN,
            5F # LDA,
            THEN,
         THEN,
         ' (EMIT) @ 8 + JSR,  INY,
      0= UNTIL,
      N 3 + INC,
   AGAIN,  END-CODE
: (PQTYPE)  ( ADR CNT -- )
   <PQTYPE> PAUSE ;

SCR# 31 
// LOGGER REDIRECTION
HEX
: (LEMIT)  ( B -- )
   DUP DEMIT  (PEMIT) ;
: (LTYPE)  ( ADR CNT -- )
   2DUP DTYPE  (PTYPE) ;
: (LQTYPE)  ( ADR CNT -- )
   2DUP #OUT @ -ROT <PQTYPE> #OUT !
   (QTYPE) ;
: (LEXPECT)  ( ADR CNT -- )
   OVER SWAP (EXPECT)
   #LP (CHKOUT) IOERR
   SPAN @ DTYPE BL DEMIT CLRCHN ;

SCR# 32 
// CONSOLE PRINTER
: CONSOLE  ( -- )
   #LP CLOSE  CLRCHN
   ['] (EMIT) IS EMIT
   ['] (TYPE) IS TYPE
   ['] (QTYPE) IS QTYPE
   ['] (EXPECT) IS EXPECT ;
: PRINTER  ( -- )
   #LP CLOSE
   0 0  #LP DUP #LP2 (OPEN) IOERR
   ['] (PEMIT) IS EMIT
   ['] (PTYPE) IS TYPE
   ['] (PQTYPE) IS QTYPE
   ['] (EXPECT) IS EXPECT ;

SCR# 33 
// LOGGER
: LOGGER  ( -- )
   PRINTER
   ['] (LEMIT) IS EMIT
   ['] (LTYPE) IS TYPE
   ['] (LQTYPE) IS QTYPE
   ['] (LEXPECT) IS EXPECT ;
CONSOLE
There are two variables to help with output formatting, #OUT and #LINE. PAGE turns both off. CR ( or 13 ( $D ) EMIT ) turns #OUT off and increments #LINE. Outputting other characters increments #OUT. (TYPE) JSR's into the main part of (EMIT) to maintain these variables.
DEMIT and DTYPE are versions of (EMIT) and (TYPE) that do not affect the formatting variables. They are for communicating with a device such as a disk drive.
QTYPE ( quote type) is for those occasions when listing a BLOCK or getting an INDEX ( showing the first line of a range of blocks ) and the block(s) in question may hold data rather than source code. QTYPE displays the control characters as reverse video characters ( or an underscore on the printer ) rather than allowing the control characters to affect the display.
(TYPE) and (QTYPE) call CLRCHN , to clear the channel, and PAUSE.
#OUT must only be incremented once for each character displayed ( or printed) and #LINE must only be incremented once for each carriage return sent or the output from something like WORDS would look rather strange.
Keep in mind that this I/O redirection worked great before the introduction of multitasking ( and works well with tasks that don't need to display anything.)
The programmer has typed LOGGER and pressed the enter key. TYPE and EMIT ( and couple of others ) are redirected to send output to both the screen and the printer ( the programmer wants a record of his ( or her) interactive Forth session). Now lets introduce multitasking. Here is a background task to print the time at the top right corner of the display. There are no fancy windows, this is a Commodore 64. Never mind that if I wanted the time displayed, I would write it directly to the screen with the task updating a small buffer with the correct screen data every minute, but copying that buffer to the correct screen address each time it ran ( or the time display would scroll away whenever the main task caused the screen to scroll.) In this example, the task uses TYPE to display the time. It uses XY and AT-XY to save, manipulate, and restore the cursor position. the problem is that each time the time display task updates the time display, a copy of the time is sent to the printer as well. If there is a semaphore to block access to the printer if another task is using it ( like the main task) then wouldn't this task just sit there and PAUSE each time it ran? If that is the case, then the time would never get displayed on the screen either. This defeats the purpose of having a time display task.
Whartung, If you have an elegant solution to this, I'd like to see it. Without code or diagrams I'm having a difficult time wrapping my mind around using semaphores to handle this.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

In FORTH MULTITASKING IN A NUTSHELL, Brad Rodriguez mentions a rule: do not FORGET tasks!
I implemented my multitasker a bit differently than F83's.
The task creation word, TASK , does not link the new task into the round-robin list. A created task returns its address.
LINK-TASK is a word that takes the address of a task and links it into the round-robin list.
UNLINK-ALL is a word that empties the list, restoring it to its start-up condition. The desired tasks can then be linked into the list.
In Fleet Forth, since all tasks can be unlinked, a task can be safely forgotten but only if it is not linked.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

I went ahead and wrote code for an auxiliary stack this weekend. The Y-register is the stack pointer, which is stored in memory. The fist thing the stack access words do is load the Y-register from that location and the last thing they do is store it back to that location. Since the only auxiliary stack operators I need transfer items between the data stack and the auxiliary stack, it can grow towards higher memory or towards lower memory. On one hand, I'm used to stacks that grow down toward lower memory like the data stack and return stack. On the other hand, the area of memory I'm using for the auxiliary stack is automatically cleared to all zeros by one of the Commodore 64 kernel routines that gets called during the cold start. A stack that grows toward higher memory would already be initialized by the cold start routine.
Here is prototype code of an auxiliary stack that grows toward higher memory. the stack pointer points to the next free byte.

Code: Select all

SCR# 77 
// AUX STACK
HEX
229 CONSTANT APOINTER
22B CONSTANT ABASE
: ADEPTH  ( -- N )
   APOINTER @ 2/ ;
: AP!  ( -- )  APOINTER OFF ;
: .AS  ( -- )
   ADEPTH DUP 0
   ?DO
      I 2* ABASE + @
      5 U.R SPACE
   LOOP
   ?EXIT
   ." EMPTY " ;

SCR# 78 
// AUX STACK
HEX
CODE >A  ( S: N -- ) ( A: -- N )
   APOINTER LDY,
   0 ,X LDA,  ABASE ,Y STA,  INY,
   1 ,X LDA,  ABASE ,Y STA,  INY,
   APOINTER STY,
   POP JMP,  END-CODE
CODE A>  ( S: -- N ) ( A: N -- )
   APOINTER LDY,  DEX,  DEX,
   DEY,  ABASE ,Y LDA,  1 ,X STA,
   DEY,  ABASE ,Y LDA,  0 ,X STA,
   APOINTER STY,
   NEXT JMP,  END-CODE

SCR# 79 
// AUX STACK
HEX
: 2>A  ( S: D -- ) ( A: -- D )
   >A >A ;
: 2A>  ( S: -- D ) ( A: D -- )
   A> A> ;
: CS>A  ( S: CS -- ) ( A: -- CS )
   2>A ; IMMEDIATE
: A>CS  ( S: -- CS ) ( A: CS -- )
   2A> ; IMMEDIATE
As is, the code has no bounds checking ( it is prototype code) and would have to be used as cautiously as the return stack. Bounds checking can be added because the auxiliary stack isn't as speed critical as the data stack ( it would probably only be used to manipulate control flow data during compiling or assembling.) The area of memory I've chosen has room for 23 16 bit cells or 11 items of control flow data.
If there is a use that would make it better to implement the Auxiliary stack so it grows toward lower memory, I would like to hear about it.
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Fleet Forth design considerations

Post by barrym95838 »

Forth 2012 defines 2>R as "Semantically equivalent to SWAP >R >R" but you have left out the SWAP in your 2>A. Is this because:
1) A grows upward,
2) You are blazing your own path,
3) Both of the above,
4) Neither of the above.

??

(I always thought that 2>R should have been called D>R because of the SWAP, but I have been mistaken on several occasions.)
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

barrym95838 wrote:
Forth 2012 defines 2>R as "Semantically equivalent to SWAP >R >R" but you have left out the SWAP in your 2>A. Is this because:
1) A grows upward,
Yes, because A grows upward. In the version where A grows downward, there is a SWAP in 2>A and 2A> . According to the Forth-83 Standard,
Quote:
Double numbers are represented on the stack
with the most-significant 16 bits (with sign) most
accessible. Double numbers are represented in memory by two
consecutive 16-bit numbers. The address of the least
significant 16 bits is two greater than the address of the
most significant 16 bits.
Also, I've noticed that 2>R and 2R> preserve the cell order of the double number.

Code: Select all

: RTEST   1234.5678 2>R RP@ 1+ 10 DUMP 2R> 2DROP ;  OK
RTEST 
 1F8  34 12 78 56 BC 21  9 22   C 30 30 2C 20 4F 4B 2C   4_.V_!_"_00, OK,
 OK
AP!  OK
1234.5678 2>A  OK
ABASE 10 DUMP 
 22B  34 12 78 56  0  0  0  0   0  0  0  0  0  0  0  0   4_.V____________
 OK
1234.5678  OK
SP@ 10 DUMP 
  7A  34 12 78 56  0  0  0  0   0  0  4 B6  0 D3 59 DD   4_.V_________.__
 OK
As can be seen in these memory dumps, the cell order ( and byte order) for the hexadecimal double number, 1234.5678, is the same on the data stack, the return stack, and the auxiliary ( or aux) stack.
Here is the code with some error checking added:

Code: Select all

SCR# 77 
// AUX STACK
HEX
229 CONSTANT APOINTER
22B CONSTANT ABASE
 2E CONSTANT APLIM
: ADEPTH  ( -- N )
   APOINTER @ 2/ ;
: AP!  ( -- )  APOINTER OFF ;
: .AS  ( -- )
   ADEPTH DUP 0
   ?DO
      I 2* ABASE + @
      5 U.R SPACE
   LOOP
   ?EXIT
   ." EMPTY " ;

SCR# 78 
// AUX STACK
HEX
CODE >A  ( S: N -- ) ( A: -- N )
   APOINTER LDY,  APLIM 1- # CPY,
   0< NOT IF,
      >FORTH  AP!
      TRUE ABORT" AUX STACK FULL!"
      >ASSEM
   THEN,
   0 ,X LDA,  ABASE ,Y STA,  INY,
   1 ,X LDA,  ABASE ,Y STA,  INY,
   APOINTER STY,
   POP JMP,  END-CODE

SCR# 79 
// AUX STACK
HEX
CODE A>  ( S: -- N ) ( A: N -- )
   APOINTER LDY,  2 # CPY,
   0< IF,
      >FORTH  AP!
      TRUE ABORT" AUX STACK EMPTY!"
      >ASSEM
   THEN,
   DEX,  DEX,
   DEY,  ABASE ,Y LDA,  1 ,X STA,
   DEY,  ABASE ,Y LDA,  0 ,X STA,
   APOINTER STY,
   NEXT JMP,  END-CODE

SCR# 7A 
// AUX STACK
HEX
: 2>A  ( S: D -- ) ( A: -- D )
   >A >A ;
: 2A>  ( S: -- D ) ( A: D -- )
   A> A> ;
: CS>A  ( S: CS -- ) ( A: -- CS )
   2>A ; IMMEDIATE
: A>CS  ( S: -- CS ) ( A: CS -- )
   2A> ; IMMEDIATE
Quote:
(I always thought that 2>R should have been called D>R because of the SWAP, but I have been mistaken on several occasions.)
Well, maybe not because of the SWAP but because it is moving a double number to the return stack. I suppose the Standards team chose to prefix some of the double number words with a '2' instead of a 'D' to remind us that those double number words will also work with a pair of single numbers as well.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

Here is the version of the Auxiliary stack where it grows toward lower memory. One of the Commodore 64 kernel routines called by the cold start routine initializes this area of memory ( among others) to all zeros. the constant APOINTER has been removed and ABASE has been moved down. The pointer now resides at ABASE and the lowest valid value for the pointer is 2. If the pointer value is zero ( like it will be when Fleet Forth first boots up) , >A and A> will "empty the aux stack".

Code: Select all

SCR# 74 
// AUX STACK
HEX
 30 CONSTANT APMAX
229 CONSTANT ABASE
: ADEPTH  ( -- N )
   APMAX  ABASE @  - 2/ ;
: AP!  ( -- )  APMAX ABASE ! ;
: .AS  ( -- )
   ADEPTH DUP 0
   ?DO
      APMAX ABASE + I 2* - 2-
      @ 5 U.R SPACE
   LOOP
   ?EXIT
   ." EMPTY " ;

SCR# 75 
// AUX STACK
HEX
CODE >A  ( S: N -- ) ( A: -- N )
   ABASE LDY,
   0= IF,  APMAX # LDY,  THEN,
   4 # CPY,
   0< IF,
      >FORTH
      TRUE ABORT" AUX STACK FULL!"
      >ASSEM
   THEN,
   DEY,  1 ,X LDA,  ABASE ,Y STA,
   DEY,  0 ,X LDA,  ABASE ,Y STA,
   ABASE STY,
   POP JMP,  END-CODE

SCR# 76 
// AUX STACK
HEX
CODE A>  ( S: -- N ) ( A: N -- )
   ABASE LDY,
   0= IF,  APMAX # LDY,  THEN,
   APMAX 1- # CPY,
   0< NOT IF,
      >FORTH
      TRUE ABORT" AUX STACK EMPTY!"
      >ASSEM
   THEN,
   DEX,  DEX,
   ABASE ,Y LDA,  0 ,X STA,  INY,
   ABASE ,Y LDA,  1 ,X STA,  INY,
   ABASE STY,
   NEXT JMP,  END-CODE

SCR# 77 
// AUX STACK
HEX
: 2>A  ( S: D -- ) ( A: -- D )
   SWAP >A >A ;
: 2A>  ( S: -- D ) ( A: D -- )
   A> A> SWAP ;
: CS>A  ( S: CS -- ) ( A: -- CS )
   2>A ; IMMEDIATE
: A>CS  ( S: -- CS ) ( A: CS -- )
   2A> ; IMMEDIATE
You may notice that the error handling does not clear the auxiliary stack. I thought it would be better if the auxiliary stack was cleared on all errors. In Fleet Forth, (ABORT") , the word compiled by ABORT" , calls ABORT. ABORT is defined like this:

Code: Select all

: ABORT  ( -- )
   ERR SP! QUIT ; -2 ALLOT
ERR is a deferred word that normally is set to NOOP , a no-op. The phrase -2 ALLOT is to reclaim memory used by an EXIT that will never be reached ( nothing comes back from QUIT .)
The auxiliary stack can be set up to be cleared on any error ( any ABORT" or ABORT ) with the following:

Code: Select all

' AP! IS ERR
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

I added to my system loader the version of the Auxiliary stack that grows toward lower memory. It seemed like a better fit with all three stacks growing toward lower memory. I added some code to my kernel to clear the Auxiliary stack on error ( and cold start.)
Now I get to play around with it and see just how useful it really is. :D
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

I've mentioned before some of the Forth-83 Standard words in Fleet Forth, but to be more specific it has:
1) All of the Required Words.
2) All of the Double Number Extension Words.
3) All of the System Extension Words.
4) All of the Assembler Extension Words.
5) All but four words from the Controlled Reference Words.
6) And some of Uncontrolled Reference Words.

These are the four words not included from the Controlled Reference Words:
--> I think that THRU is better especially since on my system a colon definition can cross a block boundary ( I dislike that extremely dense horizontal formatting.)
END This is just a synonym for UNTIL .
OCTAL I don't really see a need for this one.
These three words can be defined anytime they are needed. The fourth word I didn't include from the Controlled Reference Words is OFFSET . It would have to be defined in the kernel because BLOCK would need to access it. I didn't include it because I can't really think of a situation where it would be very useful.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

Want to see something scary? With Fleet Forth I can do this.

Code: Select all

0 >R CR .S CR .AS R> CR . CR 
EMPTY 
EMPTY 
0 
 OK
Here is using the much safer Auxiliary stack.

Code: Select all

0 >A CR .S CR .AS A> CR . CR 
EMPTY 
    0 
0 
 OK
Warning! The above ( using the return stack ) is not portable! It works on Fleet Forth because it ( what was typed in ) is all on one line and INTERPRET calls EXECUTE directly instead of through some other word. It would also work if it was all in one EVALUATEed string or one screen ( BLOCK ) of source.

[Edit: one clarification]
Last edited by JimBoyd on Sun Jun 09, 2019 1:31 am, edited 1 time in total.
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

JimBoyd wrote:
I added to my system loader the version of the Auxiliary stack that grows toward lower memory.
Taking multitasking into consideration, background tasks do not get their own Auxiliary stack or even a portion of the Auxiliary stack. For the purposes I envisioned, only the main task would need to use the Auxiliary stack ( help with managing control flow in the assembler and metacompiler) but this may need further investigation.
Any suggestions regarding the use of an Auxiliary stack by a background task?
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

I'm working on Fleet Forth's Kernel and I'm dealing with a matter of commenting vs. constants when using the C64 Kernal Jump Table. The C64 Jump Table isn't going anywhere and the constants I'm using are only defined in the host Forth dictionary so it is a matter of readability. Which seems more readable?
this:

Code: Select all

HEX
CODE CLOSE  ( N -- )
   0 ,X LDA,
   XSAVE STX,
   0FFC3 JSR,  // CLOSE
   XSAVE LDX,
   POP JMP,  END-CODE
or having this near the beginning of the source

Code: Select all

HEX
   .
   .

FFC3 DEFINE C64.CLOSE
   .
   .
and this later in the source?

Code: Select all

HEX
CODE CLOSE  ( N -- )
   0 ,X LDA,
   XSAVE STX,
   C64.CLOSE JSR,
   XSAVE LDX,
   POP JMP,  END-CODE
whartung
Posts: 1004
Joined: 13 Dec 2003

Re: Fleet Forth design considerations

Post by whartung »

If there are a LOT of those constants, I'd use the // CLOSE comment method rather than pollute the dictionary (any dictionary) with one-time constants. That could be a measurable amount of memory tied up in constants and dictionary space, with no real long term value (IMHO).
JimBoyd
Posts: 931
Joined: 05 May 2017

Re: Fleet Forth design considerations

Post by JimBoyd »

Only about 18 ( yeah, that is a lot).
Thanks, I'll reserve my one time constants for things in the design of Fleet Forth which could change.
Post Reply