Joined: Fri May 05, 2017 9:27 pm Posts: 895
|
I've talked about Fleet Forth's words BLOCK and BUFFER in other posts and I thought it would be good to go into them in a bit more depth here. First is a quick look at R/W , Fleet Forth's block read/write word. Depending on the block number requested, R/W will either access a disk drive (for lower numbered blocks) or access RAM (for higher numbered blocks). R/W is not a deferred word, but the word to access the disk, DR/W , and the word to access RAM, RR/W , are. R/W , and therefore DR/W and RR/W , take the address of a buffer, a block number, the read/write flag, and a count. This differs from Blazin' Forth. Blazin' Forth's R/W only takes three parameters, the address of a buffer, a block number, and the read/write flag. Fleet Forth's R/W was written to require a count to support a general version of INDEX . INDEX is a handy utility word that, given a starting block number and an ending block number, displays the first line for each block in that range. Ideally, INDEX only reads in one line as well (64 bytes). Fleet Forth's INDEX does so it only needs 64 bytes of memory at PAD. Since it uses R/W directly, it will work with different drive access methods, for different drive types, just like BLOCK and BUFFER .
Next is a look at the buffer table. The inspiration for the table came from Blazin' Forth. Fleet Forth, like Blazin' Forth, can support a variable number of blocks, configurable on the fly. There should never be less than one buffer, unless there will be no block access. There can be as many buffers as will fit in memory. The table consists of one entry per buffer plus one, the phantom entry. The entries are arranged in order from the phantom entry then the most recently used to the least recently used. There are two system values used to manage the table, MRU and LRU. MRU points to the phantom entry. LRU points to the entry for the least recently used block. LRU is also headerless, since it is only used in the Forth kernel. Each entry consists of six cells: the block number, the buffer address, and an update flag. R/W does not update the order of the entries in the table. BLOCK and BUFFER handle that. The following shows four blocks with the resulting 5 table entries.
Code: MRU: 5 | $C800 | FLAG 5 | $C800 | FLAG 42 | $C000 | FLAG 137 | $C400 | FLAG LRU: 1 | $CC00 | FLAG
A quick reminder that Fleet Forth is an ITC Forth for the Commodore 64 and when a CODE word is executed, the Y-register is set to zero. BLOCK starts out as a CODE word.
Code: CODE BLOCK ( BLK -- ADR ) DEY, BLK/BUF STY, ' MRU >BODY LDA, N STA, ' MRU >BODY 1+ LDA, N 1+ STA, 6 # LDY, N )Y LDA, 0 ,X CMP, 0= IF, INY, N )Y LDA, 1 ,X CMP, 0= IF, INY, N )Y LDA, PHA, INY, N )Y LDA, PUT JMP, THEN, THEN,
The first thing BLOCK does is decrement the Y-register and store it's value for (possible) later use. BUFFER is a bodiless CODE word with a code field that points one byte into the body of BLOCK. BLOCK then checks to see if the requested block is the most recently used. If so, it replaces the block number with the address of the buffer used for that block. Up to this point BLOCK behaves like a CODE word and jumps back to NEXT by way of PUT and we're done.
If the requested block is not the most recently used, the jump to PUT is branched over.
Code: >FORTH #BUF 1+ 2 ?DO DUP I >BT @ = IF DROP I UNLOOP AHEAD CS>A THEN LOOP
>FORTH causes a transition to high level code. The ?DO LOOP checks if the requested block is in the table. If it is, the index into the table is saved on the stack (not to be confused with Forth's word INDEX ), the loop parameters are discarded and AHEAD branches out of the loop to elsewhere in BLOCK .
Code: LRU 2+ 2+ @ IF LRU 2@ 0 B/BUF R/W LRU 2+ 2+ OFF THEN
If the requested block is not in the table, the least recently used block is written to disk (or ram) if its update flag is true. The update flag is not cleared until after the block is written in case the write fails.
Code: BLK/BUF C@ IF LRU ON LRU 2+ @ OVER 1 B/BUF R/W THEN LRU ! #BUF
The value of the Y-register that was saved earlier is fetched to see if this code was called as BLOCK or BUFFER . If this is BLOCK , if the read fails, the contents of the buffer will not be valid (most likely) so the entry for the block number of the least recently used block is set to TRUE (-1) to indicate an unused buffer. The new block is read in to the least recently used buffer. Both BLOCK and BUFFER then store the requested block number in the block number entry for the least recently used block/buffer and leave the index to the entry for the least recently used block/buffer on the stack.
Code: A>CS THEN DUP >BT MRU 6 CMOVE MRU 1 >BT ROT 6 * CMOVE> MRU 2+ @ ;
The branch out of the ?DO LOOP branches to this section of code. At this point one of two index values will be on the data stack. If the requested block number was found in the table the index to the table entry with the desired block number is on the data stack. If it was not found in the table the index of the least recently used block/buffer is on the data stack. This table entry is copied to the phantom entry. The phantom entry and all entries above the one with its index on the stack are now slid down by one entry. If a block that was not in the table, such as 123, was requested, this is what it would look like:
Code: MRU: 123 | $CC00 | 0 5 | $C800 | FLAG 42 | $C000 | FLAG 137 | $C400 | FLAG LRU: 123 | $CC00 | 0
Code: MRU: 123 | $CC00 | 0 123 | $CC00 | 0 5 | $C800 | FLAG 42 | $C000 | FLAG LRU: 137 | $C400 | FLAG
If however a block that was in the table, such as 42, was requested, this is the result:
Code: MRU: 42 | $C000 | FLAG 5 | $C800 | FLAG 42 | $C000 | FLAG 137 | $C400 | FLAG LRU: 1 | $CC00 | FLAG
Code: MRU: 42 | $C000 | FLAG 42 | $C000 | FLAG 5 | $C800 | FLAG 137 | $C400 | FLAG LRU: 1 | $CC00 | FLAG
|
|