Integrated into ARMForth is a debugger. The debugger has its own window, in which it displays ARMForth words as they are being executed along with displays of the computation and/or return stacks. As a consequence of its window-based operation, it only works in the Wimp mode. Execution of the debugger in the non-Wimp mode will have no effect.
The debugger is invoked by the command DEBUG. It expects two numeric parameters on the stack. On top of the stack is a bit significant parameter which determines the mode of the debugger. The bits have the following functions.
bit value function
0 0 print the computation stack in hexadecimal
0 1 print the computation stack in decimal
1 0 do not print the return stack
1 1 print the return stack in hexadecimal
2 0 do not pause after execution of each word
2 1 pause after execution of each word until
left ALT key pressed
All other bits have no effect on the debugger.
The parameter below the mode parameter on the stack determines the speed of execution of the words. Specifically, there is a delay between the execution of each word, this delay being affected by the speed parameter. If this parameter is zero, the delay is zero. A speed parameter of ???? gives approximately one second delay between the execution of words.
If DEBUG finds that there are less than two parameters on the stack, by default it assumes that both parameters are zero, so invoking DEBUG by the command
DEBUG
is the same as invoking it using
0 0 DEBUG
8.1 DEBUG In Action
-------------------
The debugger window is 40 characters wide by 10 rows. The printing format is
EXEC: <name of word>
SP: <computation stack printout in hex or decimal>
RPP: <return stack printout in hex> [this line is optional]
It prints this information before executing each word i.e. the stacks are shown in their states before the word is executed.
If bit 2 of the 'mode' parameter was zero, the debugger output simplt scrolls off the top of the screen at a speed regulated by the 'delay' parameter. To temporarily halt the scrolling, the right hand ALT key can be pressed. Execution will be halted until the key is released. Similarly, if the left ALT key is pressed, the scrolling will be halted even when this key is released. Now upon each depression of the left ALT key, one word will be executed and stack output sent to the debugger screen. This can be used as a 'single step' facility. To resume constant scrolling the right ALT key should be depressed.
The single stepping mode is automatically entered if bit 2 of the mode parameter was set.
Note that after DEBUG has been executed, the first information to be printed on the screen will be associated with the internal workings of the ARMForth system as it begins interpreting the next word in sequence after DEBUG. It is important that the user knows which words being executed are part of the user program, and which are concerned with the system itself. This is explained more fully in section ????.
8.2 Affect On Programs
----------------------
The debugger is designed to affect normal running of programs as little as possible. All output to the main terminal screen will appear as normal when the debugger is in operation. Any window management being performed by the user program by interrupting POLL-ESCAPE will function as normal. Coroutine (section ????) execution is also unaffected by the debugger, in fact coroutines are one area where it may be most useful.
One command that will not work when the debugger is running is the ASSIGN ... TO-DO command. This is an unavoidable consequence of the way the debugger intercepts execution of words.
8.3 Leaving The Debugger
------------------------
The debugger can be terminated by executing the word NODEBUG, or by clicking on the 'quit' icon in the debugger window. Quitting the debugger using the mouse will cause a continuation of the word which was executing when this occurred, so if a program was running it will continue to run.
8.4 How The Debugger Works
--------------------------
The following is a much simplified description of the operation of the debugger.
The most important criterion for the design of the debugger was that it should not slow word execution down at all when it was not being used. This has been achieved by, when DEBUG is executed, overwriting the first instruction of every word with a branch to the debugger. Of course the original first instructions have to be stored somewhere or else they will be lost when the debugger is terminated. They are in fact stored in the 32 bit memory location immediately before the name field address. Each word in the dictionary has an empty slot before the name field address which is used for this purpose. Now, when a word is executed it will immediately branch to the debugger before it does anything else. The debugger finds which word is being executed by looking at various machine registers, prints the word name and the stack contents, then it fetches the instruction which was originally the first instruction of the word (i.e. the one that is saved before the word's nfa), and constructs a code sequence which consists of this saved instruction followed by a branch back to the second instruction of the word. It then executes this two-instruction sequence, causing the code of the word to be re-entered. Now, when the execution of this word causes a branch to the first instruction of another word, the debugger will again be entered, and the process begins again.
Complications arise when vectored words are being executed, and when code which manipulates windows by calling the Wimp manager is running. Thus the operation of the debugger is not as simple as that described above, although that is the basic model used.
9. File Handling
----------------
ARMForth implements the usual Forth "screens" for compatibility with previous Forth systems. However it is recognised that Forth screens are not conducive to quick software development and it is expected that most programs will be composed with a normal text editor. ARMForth in conjunction with a window based text editor gives rise to a fast method of program production. The screen based editor will be described first, before a discussion of the facilities for loading text files into the system.
9.1 ARMForth Screens
--------------------
As is the case with numerous other Forth systems, by default each screen is 64 characters wide by 16 characters deep. This gives a 1024 byte buffer area for the screen text. The editor used to manipulate text on a screen is the standard Fig-Forth based editor. To load a screen the LOAD command is used, which expects the screen number on the stack.
9.1.1 CREATE-SCREEN
-------------------
To save each screen on disk as a separate file would produce disks with enormous numbers of files on them, so the approach taken is to store many screens in each file. As part of the ARMForth system there is a constant called S/FILE which holds the number of screens contained within each file. This is set to 32 by default, but can be changed if necessary by altering the constant. The files which are used to hold the screens are named "SCRxxxx" where xxxx is a four digit hexadecimal number. Thus the file holding screens 0-31 is named SCR0000, and the file holding screens 32-63 is named SCR0001, etc.
The user must initially create a screen before he uses it. This maps to filling the space in the file for that particular screen with zeros and if necessary the creation of the appropriate file first. The CREATE-SCREEN word is used to achieve this. The specification of this word is
scrnum CREATE-SCREEN
where scrnum is the number of the screen to be created.
9.2 Loading Text Files
----------------------
There is a command FLOAD" which loads text files into ARMForth exactly as if the contents of the file had been typed in from the keyboard. The syntax of the command is
FLOAD" filename
where filename is the name of the text file to load.
There is variant called FLOAD which expects a string variable address on the stack and loads text from the file name held in the string. For example
str FLOAD
FLOAD can itself be used inside text files, so a file in the process of being loaded can load another file halfway through. This gives a means of producing libraries of code, with one text file containing Forth words relevant to a particular application area (e.g. window manager) being FLOADed by any application that needs the library routines.
10. Coroutines
--------------
Coroutines have been implemented in ARMForth. In certain situations it is more natural for a problem to be programmed using coroutines rather than the conventional way via "procedure" (in a Forth manner) calls.
Coroutines are words which run a certain distance down their list of calls on other words, then they "give up" the processor to allow another coroutine to run. This other coroutine can then run part of its sequence of subroutine calls, then it too can give up the processor, whereupon control returns back to the previous interrupted word, but not at the beginning; it returns to the point in the code after the first word relinquished control. There may not necessarily be only two words running as coroutines, there can be as many as desired, but they are always given control in a round-robin order unless some of the words have terminated their execution, in which case those words are missed out of the sequence. There is one higher level word which determines the sequence in which the coroutines will be executed, this being called the "scheduler" word. It defines a set of coroutines which are to run together.
An important point about coroutines is that each one has its own computation and return stacks. Thus information cannot be passed from one coroutine to another via the stack. Any communication must be achieved by the use of shared variables.
We will now illustrate an example of coroutines in action. The example is a contrived one, since coroutine code tends to be quite long and complicated.
Firstly, the code for each coroutine which is involved in the coroutine execution sequence is written. Instead of using the normal : and ; defining words TASK: amd TASK; are used. At the point in each coroutine where ownership of the processor is to be given up, the word PAUSE is called. That is all that is required for the coroutines themselves. Examine the two coroutines defined below.
Notice that we haven't yet said which of the words will be the initial one to start the coroutine sequence. Nor have we said anything about the stacks used by the coroutines. Now we show the code for the "scheduler" word, which will answer these questions.
The scheduler word comprises a list of triples, one for each of the coroutines. Each triple contains in sequence, the computation stack size to be used by the coroutine, the return stack size, and the name of the coroutine itself. Like the coroutines themselves, the scheduler word is not enclosed within : and ;. It uses the defining words TEX: and TEX;. For the above example we could define a scheduler word as
TEX: SCHED 100 100 CO1 100 100 CO2 TEX;
This definition tells us that both CO1 and CO2 use 100 bytes of computation stack space and return stack space each, and that CO1 is to be the first one to execute in the sequence.
To start off the coroutine sequence in this example we execute the word SCHED. The output will appear as below.
LEAVING 1 LEAVING 2 RE-ENTERING 1 RE-ENTERING 2
The thread of execution of the word SCHED is as follows. CO1 runs, then PAUSEs, causing CO2 to run. CO2 then PAUSEs, and CO1 is re-entered. CO1 continues running to completion since there are no more PAUSEs in its code. After CO1 terminates CO2 still has some code left to execute, so it is re-entered until it runs to completion. If CO2 had more PAUSEs in its code these would have no effect since the only possible coroutine that could be executed is CO1, which has already terminated. A PAUSE which has no coroutine to re-enter continues with the caller of PAUSE.
9. BASIC Emulation Words
------------------------
ARMForth has a series of words which emulate certain of the BASIC keywords. This section describes those words. They can be split up into various groups according to their functionality. A convention used for BASIC words which can take varying numbers of parameters is to terminate the Forth word with a number (1, 2 onwards), so there is a different word for each of the possible BASIC variants. This is due to the ungainliness of Forth words which can accept a different number of parameters. Some of the keywords such as OPENIN have two variants in which one expects the address of a string on the stack, and the other, terminated by a quote (e.g. OPENIN") expects the string to appear next in the input stream.
9.1 Sound Functions
-------------------
BEAT ( ... n)
n = current beat value
BEATS= (n ... )
n = new value of beat counter
BEATS ( ... n)
n = current value of beat counter
SOUND-ON ( ... )
SOUND-OFF ( ... )
SOUND1 (n1\n2\n3\n4 ... )
n1 = channel number
n2 = amplitude
n3 = pitch
n4 = duration
SOUND2 (n1\n2\n3\n4\n5 ... )
n1 = channel number
n2 = amplitude
n3 = pitch
n4 = duration
n5 = delay
STEREO (n1\n2 ... )
n1 = channel number
n2 = stereo position
TEMPO= (n ... )
n = new beat counter rate
TEMPO ( ... n)
n = current beat counter rate
VOICES (n ... )
n = number of sound channels
9.2 File Handling
-----------------
BGET# (n1 ... n2)
n1 = file handle
n2 = byte read from file
BPUT#1 (n1\n2 ... )
n1 = file handle
n2 = byte to write to file
BPUT#2 (n\addr ... )
n = file handle
addr = address of string to write to file
CLOSE# (n ... )
n = file handle
EOF# (n1 ... n2)
n1 = file handle
n2 = non zero if at end of file, else zero
EXT# (n1 ... n2)
n1 = file handle
n2 = current file extent
EXT#= (n1\n2 ... )
n1 = file handle
n2 = new file extent
GET$# (addr\n ... )
addr = address of string to write to
n = file handle
OPENIN" / OPENIN (addr ... n)
addr = address of string containing file name
n = file handle (zero on failure)
OPENOUT" / OPENOUT (addr ... n)
addr = address of string containing file name
n = file handle (zero on failure)
OPENUP" / OPENUP (addr ... n)
addr = address of string containing file name
n = file handle (zero on failure)
PRINT#1" / PRINT#1 (n\addr ... )
n = file handle
addr = address of string to write to file
PRINT#2 (n1\n2 ... )
n1 = file handle
n2 = integer to write to file
PTR# (n1 ... n2)
n1 = file handle
n2 = file position
PTR#= (n1\n2 ... )
n1 = file handle
n2 = new file position
9.3 Graphics Functions
----------------------
CIRCLE (n1\n2\n3 ... )
n1 = centre x
n2 = centre y
n3 = radius
CIRCLE-FILL (n1\n2\n3 ... )
n1 = centre x
n2 = centre y
n3 = radius
CLG ( ... )
CLS ( ... )
COLOUR1 (n ... )
n = logical colour
COLOUR2 (n1\n2 ... )
n1 = logical colour
n2 = physical colour
COLOUR3 (n1\n2\n3\n4 ... )
n1 = logical colour
n2 = red component
n3 = green component
n4 = blue component
DRAW (n1\n2 ... )
n1 = abs x
n2 = abs y
DRAW-BY (n1\n2 ... )
n1 = rel x
n2 = rel y
ELLIPSE (n1\n2\n3\n4 ... )
n1 = centre x
n2 = centre y
n3 = semi-major axis length
n4 = semi-minor axis length
ELLIPSE-FILL (n1\n2\n3\n4 ... )
n1 = centre x
n2 = centre y
n3 = semi-major axis length
n4 = semi-minor axis length
FLOOD-FILL (n1\n2 ... )
n1 = abs x
n2 = abs y
FLOOD-FILL-BY (n1\n2 ... )
n1 = rel x
n2 = rel y
GCOL1 (n ... )
n = logical colour
GCOL2 (n1\n2 ... )
n1 = gcol action (i.e. OR, EOR, AND etc)
n2 = colour
LINE (n1\n2\n3\n4 ... )
n1 = start x
n2 = start y
n3 = end x
n4 = end y
MODE (n ... )
n = new mode
MOVE (n1\n2 ... )
n1 = abs x
n2 = abs y
MOVE-BY (n1\n2 ... )
n1 = rel x
n2 = rel y
ORIGIN (n1\n2 ... )
n1 = x
n2 = y
PLOT (n1\n2\n3 ... )
n1 = plot action
n2 = x
n3 = y
POINT (n1\n2 ... )
n1 = abs x
n2 = abs y
POINT-BY (n1\n2 ... )
n1 = rel x
n2 = rel y
RECTANGLE (n1\n2\n3\n4 ... )
n1 = corner x
n2 = corner y
n3 = width
n4 = height
RECTANGLE-FILL (n1\n2\n3\n4 ... )
n1 = corner x
n2 = corner y
n3 = width
n4 = height
RECTANGLE-TO (n1\n2\n3\n4\n5\n6 ... )
n1 = corner x
n2 = corner y
n3 = width
n4 = height
n5 = destination lower left x
n6 = destination lower left y
RECTANGLE-FILL-TO (n1\n2\n3\n4\n5\n6 ... )
n1 = corner x
n2 = corner y
n3 = width
n4 = height
n5 = destination lower left x
n6 = destination lower left y
TINT= (n ... )
n = new tint value
Only sets tint for graphics foreground colour
TINT (n1\n2 ... n3)
n1 = x
n2 = y
n3 = tint value
VDU1 (n ... )
n = byte to send to vdu driver
VDU2 (n ... )
n = 16 bit value to send to vdu driver (low byte first)
VDU3 (n ... )
n = 32 bit value to send to vdu driver (low bye first)
VDU4 (n2\___\n1 ... )
n2 = start of sequence of bytes to send to vdu driver (n2 first)
n1 = number of bytes to send
VDU5 (n2\___\n1 ... )
n2 = start of sequence of 16 bit words to send to vdu driver (n2 first)
n1 = number of 16 bit words to send
VDU6 (n2\___\n1 ... )
n2 = start of sequence of 32 bit words to send to vdu driver (n2 first)
n1 = number of 32 bit words to send
WAIT ( ... )
9.4 Keyboard Scanning Functions
-------------------------------
GET ( ... n)
n = ASCII value of key pressed
GET$ (addr ... addr)
addr = address of string to receive char
INKEY (n1 ... n2)
n1 = centi-second time limit
n2 = ASCII code or FF hex if timeout
or
n1 = INKEY key number
n2 = FF hex if key (or ESCAPE) pressed, else 0
9.5 Mouse Functions
-------------------
MOUSE ( ... n1\n2\n3)
n1 = current mouse x
n2 = current mouse y
n3 = mouse button state
MOUSE-ON ( ... )
MOUSE-OFF ( ... )
MOUSE-COLOUR (n1\n2\n3\n4 ... )
n1 = logical colour
n2 = red component
n3 = green component
n4 = blue component
MOUSE-TO (n1\n2 ... )
n1 = new mouse x
n2 = new mouse y
MOUSE-STEP1 (n ... )
n = x and y multiplier
MOUSE-STEP2 (n1\n2 ... )
n1 = x multiplier
n2 = y multiplier
MOUSE-RECTANGLE (n1\n2\n3\n4 ... )
n1 = left x
n2 = bottom y
n3 = right x
n4 = top y
9.6 Time Functions
------------------
TIME= (n ... )
n = new time value
TIME ( ... n)
n = current time value
TIME$= (addr ... )
addr = address of string containing new time
TIME$ (addr ... addr)
addr = address of string to fill with current time
9.7 Text Mode Functions
-----------------------
POS ( ... n)
n = x cursor position
VPOS ( ... n)
n = y cursor position
TAB1 (n ... )
n = new x cursor position
TAB2 (n1\n2 ... )
n1 = new x cursor position
n2 = new y cursor position
9.8 Expression Evaluation
-------------------------
VAL (addr ... )
addr = address of string containing expression
SUM1 (addr ... n)
addr = address of numeric array
n = sum of elements
SUM2 (addr1\addr2 ... addr1)
addr1 = address of destination string
addr2 = address of source string
9.9 Miscellaneous
-----------------
FALSE ( ... n)
n = 0
TRUE ( ... n)
n = 1
10. The ARMForth Macro Assembler
--------------------------------
A macro assembler built into ARMForth gives the user the ultimate flexibility in designing the code for words. The assembler accepts all standard ARM opcodes (floating point opcodes are not supported), and integrates seamlessly with the rest of the ARMForth system.
The assembler follows the usual Forth convention in that the operands appear before the mnemonic. The operands themselves are written in the "normal" order i.e. destination operand first. In general the instructions are expressed in the same way as their BASIC assembler counterparts, but with the mnemonic at the end. All mnemonics end in a comma (this is a conventional Forth representation). We now show the ARMForth syntax of the different classes of ARM instructions.
10.1 Arithmetic and Logical Instructions
----------------------------------------
10.1.1 Three Operand Instructions
---------------------------------
The mnemonics which fall into this class are
ADC,
ADD,
SBC,
SUB,
RSC,
RSB,
AND,
BIC,
ORR,
EOR,
The instruction format for these instructions is shown below.
The instructions concerned with multiplication are
MUL,
MLA,
The instruction format for MUL is
<Rd> <Rm> <Rs> ["S"] [cond] "MUL"
The instruction format for MLA is
<Rd> <Rm> <Rs> <Rn> ["S"] [cond] "MLA"
10.4 Branching Instructions
---------------------------
The branch instructions are
B,
BL,
Both these instructions expect an absolute address to precede them. The operand format is
<exp> [cond] <mnemonic>
10.5 Single Register Load/Save Instructions
-------------------------------------------
The two instructions in this category are
LDR,
LDRB,
STR,
STRB,
The operand formats involve slightly more complexity than the previous cases due to the different forms for pre-index or post-indexed addressing. The alternatives for the operand forms are expressed below.
reglist = a list of register names (i.e. Rx) separated by spaces
10.6 Defining Assembly Language Words
-------------------------------------
ARMForth adopts the usual Forth convention of using the CODE and ENDCODE words to delimit machine code word definitions. For example a definition of a"DROP" type word which simply pops a value off the stack is
CODE MCDROP
SP ! { R0 } LDMFD,
PC R14 MOV,
ENDCODE
10.7 Pseudo Instructions
------------------------
For some of the commonest machine code instructions used in ARMForth programs such as popping values off the stack there are some pseudo words available which perform these instructions. They are given below along with their equivalent "long" form instruction
reglist POP, => SP ! { reglist } LDMFD,
reglist PUSH, => SP ! { reglist } STMFD,
ENTER, => RPP ! { R14 } STMFD,
LEAVE, => RPP ! { PC } LDMFD,
The ENTER pseudo instruction is always placed at the start of a word defined by a colon definition. It saves the address where the word was called from on the computation stack. LEAVE retrieves this address, and is placed as the last instruction in a colon definition by the compiler.
10.8 Loop Constructs in the Assembler
-------------------------------------
Like the rest of Forth, the assembler has facilities for conditional branching constructs. Those available are
IF, ... ELSE, ... THEN,/ENDIF,
BEGIN, ... AGAIN,
BEGIN, ... UNTIL,
BEGIN, ... WHILE, ... REPEAT,
Before the test words (IF, UNTIL, WHILE,) a condition is placed in the code. The condition can be any of the conditions mentioned in section 10.1.1. An example of code using conditional branching is below.
CODE DELAY
R0 32768 # MOV,
BEGIN,
R0 R0 1 # S SUB,
EQ UNTIL, ( Loop until R0 reaches zero )
ENDCODE
10.9 Calling Other Words From Within Machine Code Definitions
ARMForth provides an easy way for code inside machine code definitions to call other forth words. For this it uses the CALL word. Suppose that inside a machine code word we wanted to print a character to the screen i.e. we want to call EMIT. We do this as follows.
CODE WORD-CALL-EXAMPLE
.
.
.
CALL EMIT ( Assumes previous code has put character on stack )
.
.
.
ENDCODE
The "CALL EMIT" sequence will simply plant a branch (with link) to the EMIT code. The same effect could have been achieved with
' EMIT BL,
but with a loss in readability.
10.10 Macros
-----------
Macros are a way of defining a machine code word such that, when that word appears in another word definition, instead of placing a branch to the former word, it actually duplicates the code sequence of that word inside the new word. An example to make macros clearer is given. Consider that we needed frequently to place the top two stack values into R0 and R1, but with the top stack value in R1 and the one beneath (i.e. at the higher memory address) in R0. Because of the way the LDMFD, instruction works we cannot do this in one instruction, so we have to perform two LDMFD,s. A word could be defined thus
CODE POP-R1R0-V1
R1 POP,
R0 POP,
PC R14 MOV,
ENDCODE
We could now call this word every time we wanted to perform this double pop operation. So
CODE CALLER-V1
.
.
.
CALL POP-R1R0-V1
.
.
.
ENDCODE
If speed was of the necessity this would be a slow way of achieving our aim, since two extra instructions (a branch and a return) would be executed every time we wanted to pop into R0 and R1.
A solution is to define the pop word as a macro.
MACRO POP-R1R0-V2
R1 POP,
R0 POP,
ENDMACRO
The corresponding version of the calling word is
CODE CALLER-V2
.
.
.
POP-R1R0-V2
.
.
.
ENDCODE
This will place the two pop instructions directly into the CALLER-V2 word instead of placing a branch to POP-R1R0-V2.
Of course the code sequence for CALLER-V2 is now one instruction longer than it was for the non-macro version. There is always a trade-off between speed and code size, and it is up to the programmer to decide which method is the most applicable in any situation.
11 Structure Of A Dictionary Entry
----------------------------------
11.1 Dictionary Entry Header
------------------------------
The first 32 bit word in a dictionary entry is used to store the first instruction of the code sequence. This is because the debugger overwrites the first instruction and needs to replace it after debugging.
The next 32 bit word contains bit-significant information concerning the dictionary entry i.e. the length of the name, whether it is an immediate Forth word. The address of this word is the 'name field address' (nfa) of the dictionary entry. The exact meaning of each of the bits in this word are shown below.
bit 0 = 1 if immediate
bit 1-7 = number of characters in word name
bit 31 = 1
The characters of the word name follow in the ensuing bytes, the last character having its top bit set.
At the next 32-bit aligned address there is a link to the previous word that was placed in the dictionary. The value of this link is the offset in bytes from its address to the nfa of the previous word.
That is the end of the header information for a dictionary entry, and is the same no matter how the word has been defined. Next comes the actual code sequence for the word.
11.2 Colon Definition
---------------------
11.2.1 The Code Sequence Of A Dictionary Entry
----------------------------------------------
For a colon definition, the first instruction stores the address from where the word was called on the return stack (rpp). Thus the first instruction is always
stmfd rpp!, {r14}
Correspondingly, the last instruction in a word must fetch this address from the stack and put it into the program counter. The last instruction planted is
ldmfd rpp!, {pc}
Calls to other words in the instrcution sequence are simply branch with link (bl) instructions. As an example, a definition of a word
: CONFUSE
KEY
1+
EMIT
;
will produce the following code for the word
stmfd rpp!, {r14}
bl key
bl 1+
bl emit
ldmfd rpp!, {pc}
where 'key', '1+', and 'emit' are the addresses of the appropriate words.
11.3 Constants
--------------
Constants have the following format for their code sequence.
stmfd rpp!, {r14}
bl docon
'value of constant'
'docon' is a routine which fetches the value of the constant and places it on the stack.
11.4 Variables
--------------
The code sequence for variables is as follows.
stmfd rpp!, {r14}
bl dovar
'value of variable'
The 'dovar' routine places the address of the variables value on the stack.
11.5 User Variables
-------------------
User variables have the code sequence below.
stmfd rpp!, {r14}
bl douse
offset from start of user variables
The 'douse' routine fetches the offset into the user variable table, adds it to the start of this table (thus calculating the address of that particular user variable), and pushes the result on the stack.
12 Inline Code and SPEED
------------------------
Some of the system words in ARMForth plant inline code to perform their function, instead of a call being planted to them at run-time. These words are IMMEDIATE Forth words, since they need to take control at compile-time to insert the appropriate inline code. Certain of the these words are not normally IMMEDIATE words in most Forth implementations, but the added speed gained in ARMForth justifies the decision.
12.1 Loop Constructs
---------------------
A major area where inline code is used is the execution control words. These are listed below.
BEGIN ... END
BEGIN ... WHILE ... REPEAT
IF ... ELSE ... ENDIF/THEN
DO ... LOOP
DO ... +LOOP
The programmer is given a choice as to whether he requires optimum speed in the use of these constructs. The means of achieving this selection is via the user variable SPEED.
If the value of SPEED is zero, then inline code will be planted for all the above control constructs. A non-zero SPEED will cause branches to the run-time versions of these words to be placed in the code, as is the normal case in most Forth systems. There is a trade-off between speed of execution and space taken up by the code sequences i.e. the inline code uses more instructions than the branch to a run-time routine, but the number of instructions is minimal anyway, so it is felt that with the large amounts of memory available for programs on the Archimedes, virtually all programs will resolve the conflict in favour of the higher speed gained by the inline code. This is reflected in the default value of SPEED being zero.
Related to the above control words (specifically the DO ... LOOP/+LOOP constructs) are the loop index extraction words I and J. The actions of these two words are also dependent on the value of SPEED.
12.1.1 Setting the value of SPEED
---------------------------------
Since SPEED is simply a user variable, it can be altered by the code sequence below
0 SPEED ! ( Causes fast inline code to be planted )
12.2 Literals
-------------
Literals are conventionally placed on the stack during the execution of a word by calling the LIT word, with next address after the call holding the value of the literal to be pushed onto the stack. The code would look like the following.
bl lit
'value of literal'
Again, the policy in ARMForth is to gain maximum speed of execution, so instead of the above code, a sequence of four instructions is planted, which is faster than that above. This sequence is
ldr r0, [pc], #4
'unused word'
'value of literal'
stmfd sp!, {r0}
SPEED also controls the choice of the above two policies. A zero SPEED will result in the fast but longer code, a non-zero value forces the branch instruction to be planted.
12.3 Other Optimised Words
--------------------------
12.3.1 DROP and 2DROP
---------------------
The words DROP and 2DROP are IMMEDIATE in ARMForth. The reason for this is that popping a value from the stack involves only one instruction anyway, so nothing is lost in terms of code size, and a large speed increase is gained.
13 RISCOS Libraries
-------------------
Two libraries of RISCOS sotware interrupt (SWI) words exist in the ARMForth application directory. Both the libraries are in the form of vocabularies corresponding to the major categories of SWI calls, the names of the vocabularies being the initial part of the SWI name before the underscore (e.g. OS, Wimp, Sound, etc).
13.1 High Level RISCOS Library
------------------------------
The code for the high level library is in the file RISCOS inside the ARMForth directory. This library allows application programs to call RISCOS SWI's, passing register parameters via the stack.
The most common interface to these words is to place the registers on the stack in order of increasing register number (i.e. R0 first). The registers which a particular SWI call uses varies, so the Programmers Reference Manual is an essential acquisition to discover the purpose of each register. Output parameters of SWI calls (i.e. those registers that hold results from the call) are placed on the stack in decreasing register number (i.e. R0 will be at the top of the stack), since R0 usually contains the main result of the call.
The second kind of interface occurs when a SWI call can take a varying number of register parameters depending on the value of another register (e.g. OS_File). Instead of trying to make the ARMForth word intelligent enough to discover just how many parameters are required for a particular call, the policy chosen is for the application to pass the address of a buffer on the stack. This buffer contains (at least) 16 32-bit words, one for each ARM register. The application must fill the required parts of this buffer, then place a pointer to it on the stack, before calling the SWI word. The called word will fill the ARM registers with the corresponding words from the buffer before performing the SWI call. Note that not all the registers have to be loaded from the buffer, since the maximum number of registers which will be used in the call is not usually greater than about 5.
All RISCOS words together with their entry and exit parameters are listed in Appendix B.
13.2 RISCOS Constant Library
----------------------------
The other RISCOS library (called RISCOS_C in the ARMForth directory) simply defines the SWI words as constants, with the value of the SWI number. It is anticipated that the main use for these words will be in machine code word definitions, where the name can be placed before a SWI, mnemonic, for example
Wimp CreateWindow SWI,
The first word sets the CONTEXT vocabulary to "Wimp", so that the following word "CreateWindow" will be found in a dictionary search. Any following words which are part of the Wimp vocabulary do not have to be preceded by the "Wimp" word, since the CONTEXT vocabulary will still be the Wimp vocabulary. Of course, if a word from a different vocabulary (such as Sound) is used after the above line, the vocabulary specifier is needed. The code fragment below shows this in more detail.
Wimp DeleteWindow SWI, ( sets CONTEXT vocab to Wimp )
CloseDown SWI, ( CONTEXT is still Wimp )
RO 0 # MOV,
Sound Enable SWI, ( CONTEXT is now Sound )
14 ARMForth Extensions
----------------------
This section characterizes those ARMForth words which are not normally in integral part of other Forth systems, so they may be unfamiliar to users. These words can be conveniently be divided into categories according to their utilization.
14.1 Control Flow
-----------------
14.1.1 CASE: and PCASE:
-----------------------
The CASE: and PCASE words perform a similar duty to their Pascal counterpart, i.e. they cause a sequence of code to be executed according to the value of an expression. More specifically, ARMForth passes control to one of a number of possible words depending on the value on top of the stack.
The CASE: and PCASE: words are defining words, and they each have a corresponding definition terminator word, CASE; and PCASE; respectively.
The simplest (and quickest in execution speed) technique of achieving a multi-way branch is via the CASE: word. An example of a CASE: definition is
CASE: M-WAY-BR1
ZEROCASE
ONECASE
TWOCASE
THREECASE
CASE;
Within the CASE: definition, there is a list of words which may be the destination of a branch. Whenever M-WAY-BR1 is executed, it passes control to the nth word, where n is the top stack value. Thus a zero on the stack effectuates a branch to ZEROCASE, whereas a stack value of 3 would pass control to THREECASE. After termination of the target word, control passes to the word after M-WAY-BR1. Note that M-WAY-BR1 expects the top stack value to be in the range 0-3 inclusive. An out of range value will have unpredictable effects.
The alternative to CASE: is PCASE:, a positional case word. A PCASE: definition looks similar to a CASE: definition, with the addition of integer values before each of the target words. During the execution of the PCASE: defined word, a target word will be executed if the top stack value matches that specified in the definition. The following example illustrates this.
PCASE: M-WAY-BR2
75 TARGET-ONE
1569 TARGET-TWO
-32 TARGET-THREE
0 TARGET-FOUR
PCASE;
A top stack value of 1569 when M-WAY-BR2 is executed will cause a branch to TARGET-TWO.
The values before each of the target words must be literals, since they are not evaluated at execution time.
PCASE: provides more safety-checking mechanisms than CASE:, in that a stack value which does not match with any in the definition causes termination with an error message.
14.2 Stack Manipulation
-----------------------
14.2.1 NDROP
------------
The NDROP word is used to remove a larger number of items from the stack than can be achieved with the usual DROP and 2DROP words. NDROP expects to find the number of items to be discarded on top of the stack. This number does not include the parameter to NDROP itself.
14.2.2 INSERT
-------------
INSERT places an item at a particular position in the stack, shunting the rest of the items to accommodate the new value. INSERT takes two stack parameters, the position to insert above the value to be inserted. If the stack was in the following state
45 91 351 22 -43
then
165 3 INSERT
would transform the stack to
45 91 165 351 22 -43
14.2.3 SHUFFLE and QSHUFFLE
---------------------------
There are many occasions within a Forth program when it is desirable to call a word, and the stack holds all the parameters that the word requires, but they are in the wrong order. Usually this entails a sequence of unreadable stack manipulation word calls to shuffle the stack parameters until they are in the correct order that the called word demands. The SHUFFLE and QSHUFFLE words are a solution to this problem. They are very similar, QSHUFFLE being an optimised version of SHUFFLE, so the latter will be described in detail.
SHUFFLE expects that there are n items on the stack, and they are to be rearranged so that there are still n items on the stack, but they are in a different order (this is not essential).
Upon entry to shuffle, n is on top of the stack. Below this are n numbers. These numbers indicate the desired new layout of the stack in terms of its current layout. The number directly below n gives the current position of the item whose destination is position n. The number below this is the current position of the item which will end up at position n-1. The nth number indicates the current position of the value destined for the top of the stack. An example is shown below.
If the stack contains
825 -13 444 -92 15
then the command
3 5 1 4 2 5 SHUFFLE
will transform the stack to
-92 -13 15 825 444
QSHUFFLE is a faster version of SHUFFLE. Instead of expecting control information consisting of n, the number of values to rearrange, followed by n numbers, it simply requires two numbers. The top of the stack should still be n, but the permutation information is entirely encapsulated in the next stack value. This value is comprised of 4-bit fields, each field holding equivalent information to the parameters to SHUFFLE. The least significant 4 bits give the current position of the item which is destined for position n, and the nth field contains the current position of the value which will end up on top of the stack. Although QSHUFFLE is faster than SHUFFLE, it has the obvious limitation that n must be 8 or less, this restriction being imposed by the number of 4-bit fields in a 32-bit value.
If the positional control value is given as a hexadecimal number, it looks very similar to the equivalent SHUFFLE command. Using the same example as for SHUFFLE, the command
HEX 35142 DECIMAL 5 QSHUFFLE
will achieve the same transformation as above.
14.2.4 R, >R>, N>R, R<N
-----------------------
The words in this group are used to transfer data to and from the return stack.
The word R places a copy of the top value on the return stack onto the computation stack.
>R> places a copy of the computation stack onto the return stack.
N>R moves the top n values from the computation stack to the return stack. The number of items to move, n, is expected on top of the stack. The items are placed onto the return stack in such a fashion that they will be in the reverse order in which they appeared on the computation stack, so if a value was popped from the return stack it would be the nth value that was previously on the computation stack.
R<N also moves the top n values from the computation stack to the return stack, but in the reverse order to that of N>R, thus, after the operation has been performed, the top return stack value will be the value that was previously on top of the computation stack. As for N>R, the number of items to move, n, is expected on top of the stack on entry to the word.
14.3 Arithmetic Operations
--------------------------
14.3.1 >=, <=, <>
-----------------
The comparison operators >= (greater or equal), <= (less or equal), and <> (not equal) are provided along with the normal Forth comparison words. They work in the same way as the standard Forth comparisons.
14.3.2 <<, >>, >>>
------------------
These words enable bit shifts to be performed on the stack items. They act like the other binary operators, taking two parameters, the second value on the stack being the value to be shifted, and the top value being the amount to shift.
<< is a left shift
>> is a logical right shift (Zeros entered at the most significant end)
>>> is a arithmetic right shift (Sign bit preserved)
14.3.3 4*, 4/, 4+, 4-
---------------------
These words are supplied to speed up the common operations which they refer to. The operations they accomplish are quite prevalent in ARMForth due to the fact that it is a 32-bit implementation. Each of the words takes a value from the stack to act upon.
14.3.4 ><, >><<
---------------
These words are not strictly arithmetic operators, but are more correctly described as bit-manipulation words.
>< reverses the two 16-bit parts of a 32-bit value on the stack.
>><< reverses the two 8-bit parts of a 16-bit value on the stack.
14.4 Recursion
--------------
Normally, Forth definitions cannot be recursive, since a word being defined cannot be found in a dictionary search until the terminating ';' is interpreted. The definition word R: overcomes this restriction. It is used in exactly the same way as the usual colon definition, the only proviso being that a R: definition must be terminated by R;. To illustrate, we will use the well known recursive definition of factorial.
R: FACTORIAL
-DUP
0=
IF
1
ELSE
DUP
1- FACTORIAL *
THEN
R;
14.5 Keyboard Handling
----------------------
14.5.1 ?KEY, ?KEYBOARD
----------------------
These two words can be used to scan the keyboard for key presses. ?KEY is used when we are interested in whether a particular key has been pressed. Upon entry to the word the internal key number of the key to be checked is on the stack, and a boolean flag is left depending on whether that particular key was pressed at that moment.
?KEYBOARD is more general. It takes no parameters, and leaves the internal key number of any key that is being pressed, or zero if no key is pressed.
14.6 Screen Output
------------------
14.6.1 H., DEC.
---------------
H. and DEC. are the same as '.', but disregard the current base, and print their values in hexadecimal and decimal respectively. The current base is left unchanged.
14.6.2 .S, .RS
--------------
.S performs a non-destructive print of all the values on the computation stack.
.RS peforms a non-destructive print of all the values on the return stack.
14.7 Program Construction Tools
-------------------------------
14.7.1 HIDE
-----------
The HIDE word is used to conceal a word in the dictionary, so that it cannot be used by any other words. When a program is constructed, it is good practice to split it up into separate modules, each module being responsible for a particular part of the functionality of the program. It is important that these modules be as independent as possible. A module should provide a number of interface words which can be called from other modules. Typically, it will also have numerous other words defined within it which are not part of its interface, and should not be available to other modules. It is desirable to prevent these frowned-upon accesses, and this can be achieved by HIDEing these auxiliary words. The use of HIDE is illustrated below.
HIDE LOCAL-WORD
Here LOCAL-WORD is a private word to the module in which it is defined. This command ensures that LOCAL-WORD cannot be accessed by another module. In fact it cannot be referred to at any point after this statement. All words within the module in which it is defined which refer to it still work, since its code is still there, but it cannot be found in a dictonary search.