Assembler/Disassembler Reference

LOADing the source code

The default block file forth.blk, which is included in the StrongForth package, contains the source code of an assembler and a disassembler. The assembler's source code is contained in blocks 100 to 129, while the source code of the disassembler is in blocks 130 to 163. The disassembler uses a few words from the Programming-Tools word set. Before using the assembler, type:

100 129 THRU \ Assembler

To make the disassembler available, type:

130 163 THRU \ Disassembler

Note that CODE, which initiates a code definition, is part of the StrongForth core. There's a rather subtile reason for this. CODE is also the name of a data type, namely the data type of code space addresses. Since the defining word CODE takes no input parameters, while the data type CODE has both input and output parameters, the first one could prevent finding the second one in the dictionary. Therefore, SEARCH has to come across the data type CODE before it comes across the defining word CODE. Of course, this has nothing to do with operator overloading. It is just an unfortunate coincidence, that both words have the same name.

First step

Let's begin with a very simple code definition:

CODE 2+ ( INTEGER -- 1ST )
 OK
AX POP, AX INC, AX INC, AX PUSH, NEXT, END-CODE
 OK
4 2+ .
6 OK

As usual, defining a new StrongForth word requires supplying a stack diagram. That's nothing new. Other than normal assemblers, Forth assemblers prefer postfix notation, which means the operands precede the instruction words, as in

AX POP,

A comma is appended to the names of all instruction words in order to mark the end of an assembly instruction, and to indicate that something is appended to the code space. NEXT, is not a single assembly instruction, but a macro consisting of three assembly instructions. It should always be compiled as the last instruction of a code definition, because it performs the semantics of the inner interpreter, which fetches the next token and jumps to the corresponding runtime code. Finally, END-CODE links the new code definition to the current compilation word list.

Note that a code definition is compiled while staying in interpretation state. Since CODE does not switch to compilation state, all assembly instructions are immediately executed, although they are not immediate words.

Now, let's try the disassembler:

DISASSEMBLE 2+
1AA9: AX POP,
1AAA: AX INC,
1AAB: AX INC,
1AAC: AX PUSH,
1AAD: ES: LODSW,
1AAF: BX AX MOV,
1AB1: ES: [BX] JMP,
 OK

We can easily recognise the first four assembly instructions.But what about the last three? Yes, thats the code generated by NEXT, or, in other words, these three assembly instructions constitute the inner interpreter. We'll get back to the inner interpreter later.

Second step

Our second example is slightly more complicated than the first one, because it uses ;CODE to start a sequence of assembly instructions:

: :N+ ( INTEGER -- )
CREATE , ;CODE ( INTEGER -- 1ST )
 OK
AX POP, AX ES: 2 [BX]+ ADD, AX PUSH, NEXT, END-CODE
 OK
CONST-SPACE
 OK
3 :N+ 3+
 OK
4 3+ .
7  OK

:N+ is a defining word that creates words that add a constant value to any integer. It stores the constant value in the data field of the new definition. ;CODE is succeeded by the stack diagram of the definitions created by :N+. These definitions simply expect an item of data type INTEGER on the data stack and return an item of the same data type as output parameter.

AX POP,

pops this integer from the data stack into AX. The next assembler instruction adds the constant value stored in the definitions data field to the value in AX:

AX ES: 2 [BX]+ ADD,

According to the usual 8086 assembler syntax, the destination operand (AX) comes first. The source operand consists of three parts. First, ES: tells the processor that this operand is located in the extra segment, i .e., in the constant data space. [BX]+ is the addressing mode, BX indirect with displacement, with 2 as the actual displacement. This means, the address of the operand is calculated by adding 2 to the content of the BX register. As will be shown in the next paragraph, the inner interpreter jumps to a definition's runtime code with BX pointing to the code field. The data field starts immediately after the code field, which is 2 bytes (= 1 cell) long. And the data field contains the constant value that is to be added to the input parameter.

The remaining instructions are the same as in the first example: Push the result onto the data stack, execute the inner interpreter and terminate :N+.

Excursion: The Inner Interpreter

The inner interpreter is responsible for interpreting compiled StrongForth code. Some Forth systems do not require an inner interpreter, because they compile machine code to be executed directly by the processor. However, StrongForth compiles indirect threaded code and thus needs an inner interpreter. A StrongForth colon definition is compiled into a sequence of tokens, where each token is a pointer to the code field of a definition. A definition's code field is a memory cell that contains the address of the definition's runtime code. In a code definition, the runtime code is the machine code corresponding to the assembler instructions supplied after CODE. In colon definitions, the runtime code is always the same. It performs a kind of subroutine call that pushes the instruction pointer of the inner interpreter onto the return stack, and reloads the instruction pointer with the address of the parameter field of the colon definition. Since a colon definition's data field contains a new sequence of tokens, the inner interpreter just continues until it stumbles into the token of the Forth word (EXIT). (EXIT) is a code definition that pops the instruction pointer off the return stack, so that the inner interpreter continues where it left the original sequence of tokens. We'll have a closer look at the runtime code of colon definitions and of (EXIT) at the end of this section.

On common 16- and 32-bit processors, the inner interpreter is rather small. In StrongForth, it fits into 3 assembly instructions with a total length of 7 bytes. Therefore, StrongForth simply appends a copy of the inner interpreter to the end of each code definition, instead of compiling a jump instruction to a single occurance of the inner interpreter. That's exactly what NEXT, does:

: NEXT, ( -- )
  ES: LODSW, BX AX MOV, ES: [BX] JMP, ; OK

To understand what's happening in the inner interpreter, we have to investigate StrongForth's register usage. The 8086 processor has 14 16-bit registers:

AX
BX
CX
DX
general purpose registers
SP
BP
SI
DI
data stack pointer
return stack pointer
inner interpreter instruction pointer
reserved
IP
F
8086 instruction pointer
8086 flag register
CS
DS
SS
ES
code space segment register
data space segment register
data space segment register
constand data space segment register

The four general purpose registers have no dedicated functionality in StrongForth. They can be used freely without being saved or restored inbetween words. Therefore, we can just use AX and BX in the inner interpreter without bothering what these registers contained before.

SP and BP are StrongForth's data and return stack pointers, respectively. SI contains the instruction pointer of the inner interpreter. DI is reserved for future usage by StrongForth. IP and F are the 8086 instruction pointer and flag register, so they are not available for dedicated usage by StrongForth.

The last four registers are segment registers. StrongForth uses separate memory segments for code, data and name spaces, and the data space is additionally splitted in a RAM area called data space (in a narrow sense) and a ROM area called constant data space. DS and SS both point to the data space, because data and return stack are located in the RAM area together with all system and user variables. The data fields of colon definitions are always located in the constant data space, which can be accessed by using the ES segment register.

Looking again at the definition of NEXT, we can see that the first assembly instruction fetches the next token from the address in the constant data space, where the inner interpreter's instruction pointer (SI) points to. LODSW, automatically increments SI to point to the next token in the current sequence. Next, this token is copied from the AX to the BX register, because AX can not be used as an index register. BX now contains a pointer to the code field of the definition. Finally, ES: [BX] JMP, accesses the code field and performs an indirect jump to the runtime code of the current token. Note that the code field is also located in constant data space, while the jump destination always is in code space.

Now it should be easy to understand what the runtime code of colon definitions and of (EXIT) do. Since all colon definitions share the same runtime code, we can simply disassemble an arbitrary colon definition, for example SPACES:

DISASSEMBLE SPACES
0649: BP DEC,
064A: BP DEC,
064B: +00 [BP]+ SI MOV,
064E: BX INC,
064F: BX INC,
0650: SI BX MOV,
0652: ES: LODSW,
0654: BX AX MOV,
0656: ES: [BX] JMP,
 OK
DISASSEMBLE (EXIT)
1586: SI +00 [BP]+ MOV,
1589: BP INC,
158A: BP INC,
158B: ES: LODSW,
158D: BX AX MOV,
158F: ES: [BX] JMP,
 OK

The runtime code of colon definitions first decrements the return stack pointer in order to allocate space for one cell, and then stores the current inner interpreter instruction pointer in this cell. This is actually a push-to-return-stack operation. BX still points to the code field of the colon definition. After incrementing it by the size of one cell, it points to the colon definition's data field. This address becomes the new instruction pointer. Finally, the inner interpreter (NEXT,) is executed.

(EXIT) is even more simple. It's runtime code pops the inner interpreter instruction pointer from the return stack and then executes the inner interpreter.

Addressing modes

So far, you've already seen a small number of addressing modes. For example, AX is the name of an 8086 general-purpose register, but in the context of the StrongForth assembler it denotes an addressing mode. AX is a so-called register direct addressing mode, which simply determines that the operand is in the AX register. Another example is [BX]+. This addressing mode determines that the operand is in the memory cell whose address is calculated by adding a constant displacement to the content of the BX register. The constant displacement is an input operand to [BX]+. Here is a list of all addressing modes explicitly defined in the StrongForth assembler:

Word operands

AX register direct
CX
DX
BX
SP
BP
SI
DI
WORD[] memory direct
[BX] indirect
[BP]
[SI]
[DI]
[BX+SI] base with index
[BX+DI]
[BP+SI]
[BP+DI]
n [BX]+ indirect with displacement
n [BP]+
n [SI]+
n [DI]+
n [BX+SI]+ base with index and displacement
n [BX+DI]+
n [BP+SI]+
n [BP+DI]+
ES segment register direct
CS
SS
DS

Byte operands

AL register direct
CL
DL
BL
AH
CH
DH
BH
BYTE[] memory direct
BYTE[BX] indirect
BYTE[BP]
BYTE[SI]
BYTE[DI]
BYTE[BX+SI] base with index
BYTE[BX+DI]
BYTE[BP+SI]
BYTE[BP+DI]
n BYTE[BX]+ indirect with displacement
n BYTE[BP]+
n BYTE[SI]+
n BYTE[DI]+
n BYTE[BX+SI]+ base with index and displacement
n BYTE[BX+DI]+
n BYTE[BP+SI]+
n BYTE[BP+DI]+

As can be seen, byte and word operands are clearly distinguished. All addressing modes not listed here are implicit to specific assembly instructions. This is also true for immediate addressing, because immediate operands do not require a special word to indicate the addressing mode. For example,

AX 23 MOV,

moves the immediate value 23 into the AX register. MOV, is actually an overloaded word, where one version takes an addressing mode and an immediate operand as input parameters, whereas the second version requires two addressing modes.

[BP] is an addressing mode not defined for the 8086. It is actually emulated by 0 [BP]+. Whenever an addressing mode with displacement (n) is used, the assembler decides whether a 16-bit displacement is required or a signed 8-bit displacement is sufficient.

Note that BL is an addressing mode as well (register direct):

BL ( -- MODE )

This definition makes BL from the Core word set invisible, because both definitions have no input parameters that could give SEARCH a hint which of them to chose. As long as the Search-Order word set is not loaded, renaming the assembler's version of BL is the only secure means to avoid problems. Renaming BL from the Core word set is obviously not a good idea.

Instruction words

The assembler provides only those instructions that are included in the 8086 instruction set. The additional 80186 and 80286 instructions are not implemented, but they can easily be added if desired.

Most assembly instructions expect one or two operands on the stack, which can be immediate values, branch destination addresses, registers or memory locations. Registers and memory addresses are always specified by addressing modes as explained in the previous section.

Note that in all instructions requiring two operands, like ADD, and MOV,, the destination operand comes before the source operand. A list of all StrongForth assembly instructions is given below.

Instruction list

    AAA, ASCII Adjust for Addition
    AAD, ASCII Adjust for Division
    AAM, ASCII Adjust for Multiply
    AAS, ASCII Adjust for Subtraction
ex n ADC, ADd with Carry immediate source to destination byte or word
eb rb ADC, ADd with Carry source byte register to destination byte
ew rw ADC, ADd with Carry source word register to destination word
rb eb ADC, ADd with Carry source byte to destination byte register
rw ew ADC, ADd with Carry source word to destination word register
ex n ADD, ADD immediate source to destination byte or word
eb rb ADD, ADD source byte register to destination byte
ew rw ADD, ADD source word register to destination word
rb eb ADD, ADD source byte to destination byte register
rw ew ADD, ADD source word to destination word register
ex n AND, AND immediate source to destination byte or word
eb rb AND, AND source byte register to destination byte
ew rw AND, AND source word register to destination word
rb eb AND, AND source byte to destination byte register
rw ew AND, AND source word to destination word register
  c CALL, CALL subroutine at absolute address c
  ew CALL, CALL subroutine at address stored in source word
u c CALLF, CALL Far subroutine at absolute address c in memory segment u
  mw CALLF, CALL Far subroutine at address/segment stored in source double word
    CBW, Convert Byte to Word
    CLC, CLear Carry flag
    CLD, CLear Direction flag
    CLI, CLear Interrupt-enable flag
    CMC, CoMplement Carry flag
ex n CMP, CoMPare immediate source to destination byte or word
eb rb CMP, CoMPare source byte register to destination byte
ew rw CMP, CoMPare source word register to destination word
rb eb CMP, CoMPare source byte to destination byte register
rw ew CMP, CoMPare source word to destination word register
    CMPSB, CoMPare String of Bytes
    CMPSW, CoMPare String of Words
    CS: Code Segment prefix
    CWD, Convert Word to Double word
    DAA, Decimal Adjust for Addition
    DAS, Decimal Adjust for Subtraction
  n DB, Define immediate Byte
  d DD, Define immediate Double word
ex   DEC, DECrement destination byte or word by 1
  ex DIV, DIVide AX by source byte or AX/DX by source word (unsigned)
    DS: Data Segment prefix
  n DW, Define immediate Word
    ES: Extra Segment prefix
    HLT, enter HaLT state
  ex IDIV, Integer DIVide AX by source byte or AX/DX by source word (signed)
  ex IMUL, Integer MULtiply AL by source byte or AX by source word (signed)
ar pb IN, INput byte or word from immediate source port into AL or AX
ar DX IN, INput byte or word from source port in DX into AL or AX
ex   INC, INCrement destination byte or word by 1
  ub INT, INTerrupt vector number ub
    INTO, INTerrupt on Overflow vector number 4
    IRET, Interrupt RETurn
  cb JA, Jump on Above to absolute address cb
  cb JAE, Jump on Above or Equal to absolute address cb
  cb JB, Jump on Below to absolute address cb
  cb JBE, Jump on Below or Equal to absolute address cb
  cb JC, Jump on Carry to absolute address cb
  cb JCXZ, Jump if CX Zero to absolute address cb
  cb JE, Jump on Equal to absolute address cb
  cb JG, Jump on Greater than to absolute address cb
  cb JGE, Jump on Greater than or Equal to absolute address cb
  cb JL, Jump on Less than to absolute address cb
  cb JLE, Jump on Less than or Equal to absolute address cb
  c JMP, JuMP unconditionally to absolute address c
  ew JMP, JuMP unconditionally to absolute address stored in source word
u c JMPF, JuMP unconditionally to absolute address c in memory segment u
  mw JMPF, JuMP unconditionally to address/segment stored in source double word
  cb JNA, Jump on Not Above to absolute address cb
  cb JNAE, Jump on Not Above or Equal to absolute address cb
  cb JNB, Jump on Not Below to absolute address cb
  cb JNBE, Jump on Not Below or Equal to absolute address cb
  cb JNC, Jump on Not Carry to absolute address cb
  cb JNE, Jump on Not Equal to absolute address cb
  cb JNG, Jump on Not Greater than to absolute address cb
  cb JNGE, Jump on Not Greater than or Equal to absolute address cb
  cb JNL, Jump on Not Less than to absolute address cb
  cb JNLE, Jump on Not Less than or Equal to absolute address cb
  cb JNO, Jump on Not Overflow to absolute address cb
  cb JNP, Jump on Not Parity to absolute address cb
  cb JNS, Jump on Not Sign to absolute address cb
  cb JNZ, Jump on Not Zero to absolute address cb
  cb JO, Jump on Overflow to absolute address cb
  cb JP, Jump on Parity to absolute address cb
  cb JPE, Jump on Parity Equal to absolute address cb
  cb JPO, Jump on Parity Odd to absolute address cb
  cb JS, Jump on Sign to absolute address cb
  cb JZ, Jump on Zero to absolute address cb
    LAHF, Load register AH from Flags
rw ew LDS, Load pointer using DS and destination register from source double word
rw ew LEA, Load Effective Address of source into destination register
rw ew LES, Load pointer using ES and destination register from source double word
    LOCK LOCK the bus prefix
    LODSB, LOaD String of Bytes
    LODSW, LOaD String of Words
  cb LOOP, LOOP to absolute address cb
  cb LOOPE, LOOP while Equal to absolute address cb
  cb LOOPNE, LOOP while Not Equal to absolute address cb
  cb LOOPNZ, LOOP while Not Zero to absolute address cb
  cb LOOPZ, LOOP while Zero to absolute address cb
ex n MOV, MOVe immediate source to destination byte or word
eb rb MOV, MOVe source byte register to destination byte
ew rw MOV, MOVe source word register to destination word
rb eb MOV, MOVe source byte to destination byte register
rw ew MOV, MOVe source word to destination word register
ew sr MOV, MOVe source segment register to destination word
sx ew MOV, MOVe source word to destination segment register
    MOVSB, MOVe String of Bytes
    MOVSW, MOVe String of Words
  ex MUL, MULtiply AL by source byte or AX by source word (unsigned)
ex   NEG, NEGate destination byte or word
    NOP, No OPeration
ex   NOT, logical NOT destination byte or word
ex n OR, OR immediate source to destination byte or word
eb rb OR, OR source byte register to destination byte
ew rw OR, OR source word register to destination word
rb eb OR, OR source byte to destination byte register
rw ew OR, OR source word to destination word register
pb ar OUT, OUTput byte or word from AL or AX to immediate destination port
DX ar OUT, OUTput byte or word from AL or AX to destination port in DX
sx   POP, POP word from stack into destination segment register
ew   POP, POP word from stack into destination
    POPF, POP flags from stack
  sr PUSH, PUSH source segment register to stack
  ew PUSH, PUSH source word to stack
    PUSHF, PUSH flags to stack
ex 1 RCL, Rotate destination byte or word through Carry Left by 1 bit
ex CL RCL, Rotate destination byte or word through Carry Left by CL bits
ex 1 RCR, Rotate destination byte or word through Carry Right by 1 bit
ex CL RCR, Rotate destination byte or word through Carry Right by CL bits
    REP REPeat prefix
    REPE REPeat while Equal prefix
    REPNE REPeat while Not Equal prefix
    REPNZ REPeat while Not Zero prefix
    REPZ REPeat while Zero prefix
    RET, RETurn from subroutine
  u RET, RETurn from subroutine and add u to SP
    RETF, RETurn from Far subroutine
  u RETF, RETurn from Far subroutine and add u to SP
ex 1 ROL, Rotate destination byte or word Left by 1 bit
ex CL ROL, Rotate destination byte or word Left by CL bits
ex 1 ROR, Rotate destination byte or word Right by 1 bit
ex CL ROR, Rotate destination byte or word Right by CL bits
    SAHF, Store register AH into Flags
ex 1 SAL, Shift destination byte or word Arithmetic Left by 1 bit
ex CL SAL, Shift destination byte or word Arithmetic Left by CL bits
ex 1 SAR, Shift destination byte or word Arithmetic Right by 1 bit
ex CL SAR, Shift destination byte or word Arithmetic Right by CL bits
ex n SBB, SuBtract with Borrow immediate source from destination byte or word
eb rb SBB, SuBtract with Borrow source byte register from destination byte
ew rw SBB, SuBtract with Borrow source word register from destination word
rb eb SBB, SuBtract with Borrow source byte from destination byte register
rw ew SBB, SuBtract with Borrow source word from destination word register
    SCASB, SCAn String of Bytes
    SCASW, SCAn String of Words
ex 1 SHL, SHift destination byte or word logical Left by 1 bit
ex CL SHL, SHift destination byte or word logical Left by CL bits
ex 1 SHR, SHift destination byte or word logical Right by 1 bit
ex CL SHR, SHift destination byte or word logical Right by CL bits
    SS: Stack Segment prefix
    STC, SeT Carry flag
    STD, SeT Direction flag
    STI, SeT Interrupt-enable flag
    STOSB, STOre String of Bytes
    STOSW, STOre String of Words
ex n SUB, SUBtract immediate source from destination byte or word
eb rb SUB, SUBtract source byte register from destination byte
ew rw SUB, SUBtract source word register from destination word
rb eb SUB, SUBtract source byte from destination byte register
rw ew SUB, SUBtract source word from destination word register
ex n TEST, TEST immediate source with destination byte or word
eb rb TEST, TEST source byte register with destination byte
ew rw TEST, TEST source word register with destination word
rb eb TEST, TEST source byte with destination byte register
rw ew TEST, TEST source word with destination word register
    WAIT, enter WAIT state
eb rb XCHG, eXCHanGe source byte register with destination byte
ew rw XCHG, eXCHanGe source word register with destination word
rb eb XCHG, eXCHanGe source byte with destination byte register
rw ew XCHG, eXCHanGe source word with destination word register
    XLATB, TransLATe Byte
ex n XOR, eXclusive OR immediate source to destination byte or word
eb rb XOR, eXclusive OR source byte register to destination byte
ew rw XOR, eXclusive OR source word register to destination word
rb eb XOR, eXclusive OR source byte to destination byte register
rw ew XOR, eXclusive OR source word to destination word register

The symbols used in the destination and source operand columns have the following meaning:

a  ::= any ADDRESS
ar ::= AX | AL
c  ::= any CODE
cb ::= any CODE; CODE-HERE - 127 < cb < CODE-HERE + 130
d  ::= any DOUBLE
eb ::= rb | mb
ew ::= rw | mw
ex ::= eb | ew
mb ::= BYTE[BX+SI] | BYTE[BX+DI] | BYTE[BP+SI] | BYTE[BP+DI] |
       BYTE[SI] | BYTE[DI] | BYTE[BP] | BYTE[BX] |
       n BYTE[BX+SI]+ | n BYTE[BX+DI]+ | n BYTE[BP+SI]+ | n BYTE[BP+DI]+ |
       n BYTE[SI]+ | n BYTE[DI]+ | n BYTE[BP]+ | n BYTE[BX]+ |
       a BYTE[]
mw ::= [BX+SI] | [BX+DI] | [BP+SI] | [BP+DI] |
       [SI] | [DI] | [BP] | [BX] |
       n [BX+SI]+ | n [BX+DI]+ | n [BP+SI]+ | n [BP+DI]+ |
       n [SI]+ | n [DI]+ | n [BP]+ | n [BX]+ |
       a WORD[]
n  ::= any SINGLE
pb ::= any PORT; 0 <= pb <= 255
rb ::= AL | CL | DL | BL | AH | CH | DH | BH
rw ::= AX | CX | DX | BX | SP | BP | SI | DI
sr ::= CS | DS | ES | SS
sx ::= DS | ES | SS
u  ::= any UNSIGNED
ub ::= any UNSIGNED; 0 <= ub <= 255

The instruction list includes three instructions for compiling bytes, words and double words into the code space: DB, DW, and DD, respectively. These words are mostely used by the assembler itself. They are defined as follows:

: DB, ( SINGLE -- ) SPACE@ CODE-SPACE SWAP C, SPACE! ;
: DW, ( SINGLE -- ) SPACE@ CODE-SPACE SWAP  , SPACE! ;
: DD, ( DOUBLE -- ) SPACE@ CODE-SPACE SWAP  , SPACE! ;

Instructions whose name does not end with a comma are prefixes, that have to be used in combination with another instruction. One of those, ES:, is actually used within NEXT,. The syntax for prefixes in relation to the assembly instruction they are applied to is rather simple and obvious: Prefixes have to be executed immediately before the assembly instruction, but they may be mixed with addressing modes and immediate values if convenient. For example, these two phrases generate the same code:

ES: AX [DI] MOV,
AX ES: [DI] MOV,

Conditionals

The StrongForth assembler supports structured programming by using the following instructions instead of conditional and unconditional jump instructions and explicit labels:

IFcc,    ( -- ORIGIN-CODE )
ELSE,    ( ORIGIN-CODE -- 1ST )
THEN,    ( ORIGIN-CODE -- )
AHEAD,   ( -- ORIGIN-CODE )
BEGIN,   ( -- DESTINATION-CODE )
UNTILcc, ( DESTINATION-CODE -- )
AGAIN,   ( DESTINATION-CODE -- )
WHILEcc, ( DESTINATION-CODE -- ORIGIN-CODE 1ST )
REPEAT,  ( ORIGIN-CODE DESTINATION-CODE -- )

IFcc, UNTILcc, and WHILEcc, each stand for a whole group of instructions, with cc being a condition derived from the bits of the 8086 flags register:

A above
AE above or equal
B below
BE below or equal
C carry
E equal
G greater than
GE greater than or equal
L less than
LE less than or equal
NA not above
NAE not above or equal
NB not below
NBE not below or equal
NC not carry
NCXZ not CX zero
NE not equal
NG not greater than
NGE not greater than or equal
NL not less than
NLE not less than or equal
NO not overflow
NP not parity
NS not sign
NZ not zero
O overflow
P parity
PE parity even
PO parity odd
S sign
Z zero

The above instructions are the assembler's equivalent to the words IF, ELSE, THEN, AHEAD, BEGIN, UNTIL, WHILE and REPEAT from the Core word set and the word AGAIN from the Core extension word set. The data types ORIGIN-CODE and DESTINATION-CODE are used in a similar way as ORIGIN and DESTINATION. This means that structures like

... IFcc, ... THEN, ...
... IFcc, ... ELSE, ... THEN, ...
... AHEAD, ... THEN, ...
... BEGIN, ... UNTILcc, ...
... BEGIN, ... AGAIN, ...
... BEGIN, ... WHILEcc, ... REPEAT, ...

can be inserted into the assembly code. These structures can also be nested if desired. They will be translated into appropriate conditional and unconditional branch instructions. Let's try an example:

CODE > ( UNSIGNED 1ST -- FLAG )
 OK
BX POP, AX POP, AX BX CMP,
 OK
IFA, AX TRUE MOV,
 OK
ELSE, AX AX XOR,
 OK
THEN, AX PUSH,
 OK
NEXT, END-CODE
 OK
DISASSEMBLE >
1B15: BX POP,
1B16: AX POP,
1B17: AX BX CMP,
1B19: 1B20 JBE,
1B1B: AX FFFF MOV,
1B1E: 1B22 JMP,
1B20: AX AX XOR,
1B22: AX PUSH,
1B23: ES: LODSW,
1B25: BX AX MOV,
1B27: ES: [BX] JMP,
 OK

As expected, IFA, generates a conditional jump for the inverse condition. Above turns to Below or Equal, because the if branch should be skipped whenever the condition is not true. ELSE, resolves the conditional jump generated by IFA,, and compiles an unconditional jump that skips the else branch. Finally, THEN, resolves the unconditional jump compiled by ELSE,.

A second example demonstrates a typical loop structure. BITS takes an item of data type SINGLE from the stack and returns the number of bits required to represent it's value. BITS actually returns the bit number of the highest 1 bit in SINGLE, plus 1.

CODE BITS ( SINGLE -- UNSIGNED )
 OK
AX POP, CX CX XOR, AX AX TEST,
 OK
BEGIN,
 OK
WHILENZ, CX INC, AX 1 SHR,
 OK
REPEAT, CX PUSH,
 OK
NEXT, END-CODE
 OK
DISASSEMBLE BITS
1B62: AX POP,
1B63: CX CX XOR,
1B65: AX AX TEST,
1B67: 1B6E JZ,
1B69: CX INC,
1B6A: AX 1 SHR,
1B6C: 1B67 JMP,
1B6E: CX PUSH,
1B6F: ES: LODSW,
1B71: BX AX MOV,
1B73: ES: [BX] JMP,
 OK

WHILENZ, compiles a conditional branch for the inverse condition, and REPEAT, compiles an unconditional branch and resolves the branch addresses of the complete loop structure. It is not necessary to define any labels.

Labels

The location counter of the assembler is identical to StrongForth's code space pointer as it is delivered by CODE-HERE. This word is part of StrongForth's the Core word set. Since most other assemblers use a special symbol for the location counter, it might be useful to define an alias:

' CODE-HERE ALIAS $ ( -- CODE )
Although the existence of conditional instructions greatly reduces the need to explicitely define labels or to access the location counter in general, it can in certain situations be necessary to define labels. A label can be defined with

CODE-HERE CONSTANT name

where name is the name of the label. However, this phrase looks a little bit awkward when embedded into assembly code. As an alternative, the StrongForth assembler provides the word LABEL, which allows labels to be defined as follows:

LABEL name

This is the definition of LABEL:

: LABEL ( -- )
  CODE-HERE [DT] CODE DTP@ ! CONSTANT ;

The phrase [DT] CODE DTP@ ! is required, because CONSTANT expects the data type of the constant at the first unused address of the data type heap. This data type is automatically available only if CONSTANT is interpreted. The data type is simply left there by the interpreter. But if CONSTANT is executed as part of a compiled word, you have to make sure yourself that it finds the correct data type at the location the data type heap pointer points to.

Disassembler

The usage of the disassembler has already been demonstrated several times in the previous paragraphs. DISASSEMBLE is normally the only word of the disassembler package that will be used. It disassembles machine code beginning at the machine code address of a word until the instruction [BX] JMP, is found. This is the last instruction of the inner interpreter, which typically is the last action of a code definition. If this does not work, because the code definition does not execute the inner interpreter at the end, or it has more than one exit point, or [BX] JMP, is used for other purposes as well, an overloaded version of DISASSEMBLE can be used. This version expects an explicit line count as input parameter:

4 DISASSEMBLE BITS
1B62: AX POP,
1B63: CX CX XOR,
1B65: AX AX TEST,
1B67: 1B6E JZ,
 OK

Floating-Point Instructions

The basic version of the assembler and the disassembler are not able to deal with floating-point instructions. In order to extend the assembler with the instruction set of the 8087 floating-point coprocessor, you have to LOAD the floating-point assembler extensions. The disassembler considers floating-point instructions after the floating-point disassembler extensions have been loaded:

800 814 THRU \ Floating-Point Assembler Extensions
815 823 THRU \ Floating-Point Disassembler Extensions

The floating-point assember adds some new addressing modes in addition to the floating-point instructions. The operands of floating-point instructions are located either in the registers of the floating-point stack or in memory:

ST0 floating-point register direct
ST1
ST2
ST3
ST4
ST5
ST6
ST7
ST synonym for ST0
M16 modifier for 16-bit integer numbers in memory
M32 modifier for 32-bit integer and floating-point numbers in memory
M64 modifier for 64-bit integer and floating-point numbers in memory
M80 modifier for 80-bit floating-point numbers in memory

The same memory addressing modes like for the basic 8086 assembler can be used to access the operands of floating-point instructions in memory. In order to distinguish different operand sizes, which are 16, 32 and 64 bits for integer operands and 32, 64 and 80 bits for floating-point operands, four new address modifiers are provided. For example,

[BX] M32 FILD,

reads a 32-bit integer number from the memory address contained in DS:BX and pushes it as a floating-point number to the floating-point stack. Without the modifier M32, FILD, throws an exception, because it does noth know whether the operand is 16, 32 or 64 bits wide.

10 [BP+DI]+ M64 FSTP,
pops a floating-point number from the floating-point stack and stores its 64-bit memory representation at a memory location whose address is the sum of the contents of registers BP and DI and the constant offset 10.

Now, here's the list of the floating-point instructions:

    F2XM1, Replace ST0 with 2 ** ST0-1.0E0
    FABS, Replace ST0 with its ABSolute value
    FADD, ADD ST0 to ST1 and pop ST0
  ef FADD, ADD source to ST0
ST0 st FADD, ADD source to ST0
st ST0 FADD, ADD ST0 to destination
st ST0 FADDP, ADD ST0 to destination and pop ST0
  mw FBLD, BCD LoaD from source and push to ST0
mw   FBSTP, BCD STore ST0 to destination and pop ST0
    FCHS, Change the Sign of ST0
    FCLEX, CLear EXceptions
    FCOM, COMpare ST0 with ST1
  ef FCOM, COMpare ST0 with source
ST0 st FCOM, COMpare ST0 with source
    FCOMP, COMpare ST0 with ST1 and pop ST0
  ef FCOMP, COMpare ST0 with source and pop ST0
ST0 st FCOMP, COMpare ST0 with source and pop ST0
    FCOMPP, COMpare ST0 with ST1 and pop both ST0 and ST1
    FCOS, Replace ST0 with its COSine
    FDECSTP, DECrement the floating-point stack pointer
    FDISI, DISable Interrupts
    FDIV, DIVide ST1 by ST0 and pop ST0
  ef FDIV, DIVide ST0 by source
ST0 st FDIV, DIVide ST0 by source
st ST0 FDIV, DIVide destination by ST0
st ST0 FDIVP, DIVide destination by ST0 and pop ST0
    FDIVR, DIVide ST0 by ST1 and pop ST0, result in new ST0
  ef FDIVR, DIVide source by ST0, result in ST0
ST0 st FDIVR, DIVide source by ST0, result in ST0
st ST0 FDIVR, DIVide ST0 by destination, result in destination
st ST0 FDIVRP, DIVide ST0 by destination and pop ST0, result in destination
    FENI, ENable Interrupts
st   FFREE, Free destination register
  mi FIADD, Integer ADD source to ST0
  mi FICOM, Integer COMpare ST0 with source
  mi FICOMP, Integer COMpare ST0 with source and pop ST0
  mi FIDIV, Integer DIVide ST0 by source
  mi FIDIVR, Integer DIVide source by ST0, result in ST0
  mj FILD, Integer LoaD from source and push to ST0
  mi FIMUL, Integer MULtiply ST0 with source
    FINCSTP, INCrement the floating-point stack pointer
    FINIT, INITialize floating point unit
mi   FIST, Integer STore ST0 to destination
mj   FISTP, Integer STore ST0 to destination and pop ST0
mi   FISUB, Integer SUBtract source from ST0
mi   FISUBR, Integer SUBtract ST0 from source, result in ST0
  eg FLD, LoaD from source and push to ST0
    FLD1, LoaD 1.0E0 and push to ST0
  mw FLDCW, LoaD Control Word from source
  mw FLDENV, LoaD ENVironment from source
    FLDL2E, LoaD ld(e) and push to ST0
    FLDL2T, LoaD ld(10) and push to ST0
    FLDLG2, LoaD lg(2) and push to ST0
    FLDLN2, LoaD ln(2) and push to ST0
    FLDPI, LoaD π and push to ST0
    FLDZ, LoaD 0.0E0 and push to ST0
    FMUL, MULtiply ST1 with ST0 and pop ST0
  ef FMUL, MULtiply ST0 with source
ST0 st FMUL, MULtiply ST0 with source
st ST0 FMUL, MULtiply destination with ST0
st ST0 FMULP, MULtiply destination with ST0 and pop ST0
    FNOP, No Operation
    FPATAN, Replace ST0 with its Arcus TANgens
    FPREM, Replace ST0 with the Partial REMainder of ST0 divided by ST1
    FPREM1, Replace ST0 with the Partial REMainder of ST0 divided by ST1, IEEE version
    FPTAN, Replace ST0 with its Partial TANgens, then push 1.0E0 to ST0
    FRNDINT, RouND ST0 to an INTeger value
  mw FRSTOR, ReSTORe the state of the floating-point unit from source
mw   FSAVE, SAVE the state of the floating-point unit at destination
    FSCALE, SCALE ST0 by 2 ** ST1
    FSETPM, SET Protected Mode
    FSIN, Replace ST0 with its COSine
    FSINCOS, Replace ST0 with its SINe, then push the COSine to ST0
    FSQRT, Replace ST0 with its SQuare RooT
ef   FST, STore ST0 to destination
mw   FSTCW, STore Control Word to destination
mw   FSTENV, STore ENVironment to destination
eg   FSTP, STore ST0 to destination and pop ST0
AX   FSTSW, STore Status Word to AX
mw   FSTSW, STore Status Word to destination
    FSUB, SUBtract ST0 from ST1 and pop ST0
  ef FSUB, SUBtract source from ST0
ST0 st FSUB, SUBtract source from ST0
st ST0 FSUB, SUBtract ST0 from destination
st ST0 FSUBP, SUBtract ST0 from destination and pop ST0
    FSUBR, SUBtract ST1 from ST0 and pop ST0, result in new ST0
  ef FSUBR, SUBtract ST0 from source, result in ST0
ST0 st FSUBR, SUBtract ST0 from source, result in ST0
st ST0 FSUBR, SUBtract destination from ST0, result in destination
st ST0 FSUBRP, SUBtract destination from ST0 and pop ST0, result in destination
    FTST, TeST ST0 for zero
  ef FUCOM, Unordered COMpare ST0 with source
  ef FUCOMP, Unordered COMpare ST0 with source and pop ST0
    FUCOMPP, Unordered COMpare ST0 with ST1 and pop both ST0 and ST1
    FWAIT, WAIT for floating-point unit ready
    FXAM, EXAMine ST0
    FXCH, EXCHange ST0 and ST1
st   FXCH, EXCHange ST0 and destination
st ST0 FXCH, EXCHange ST0 and destination
ST0 st FXCH, EXCHange source and ST0
    FXTRACT, EXTRACT exponent from ST0, then push significand to ST0
    FYL2X, Compute ST1 * ld(ST0) and pop ST0, result in new ST0
    FYL2XP1, Compute ST1 * ld(ST0+1.0E0) and pop ST0, result in new ST0
ef ::= st | m2 | m4
eg ::= st | m2 | m4 | m5
m1 ::= mw M16
m2 ::= mw M32
m4 ::= mw M64
m5 ::= mw M80
mi ::= m1 | m2
mj ::= m1 | m2 | m4
st ::= ST0 | ST1 | ST2 | ST3 | ST4 | ST5 | ST6 | ST7

Finally, let's view an example of a simple machine code word that contains floating-point instructions:

CODE * ( FLOAT SIGNED-DOUBLE -- 1ST )
 OK
  BX SP MOV, [BX] M32 FIMUL, AX POP, AX POP, NEXT, END-CODE
 OK
DISASSEMBLE *
1EC3: BX SP MOV,
1EC5: [BX] M32 FIMUL,
1EC7: AX POP,
1EC8: AX POP,
1EC9: ES: LODSW,
1ECB: BX AX MOV,
1ECD: ES: [BX] JMP,
 OK

Dr. Stephan Becher - June 12th, 2007