The A68k Assembler

In addition to the GNU Assembler, the TIGCC package also includes the A68k assembler by Charlie Gibbs (slightly modified Amiga version). Although TIGCC also includes the GNU assembler used by GCC, A68k is included here because almost all assembly programs for the TI-89 and TI-92+ are created just with this assembler, so including it in the package allows compiling existing ASM programs as well, and because, while it is inferior to the GNU assembler in some aspects, it also has its advantages, mainly easier syntax, but also support for binary include files for example. As this part of the TIGCC package was developed completely independently of the rest of the TIGCC project (and long before the TIGCC project was even started), it is somewhat inconsistent with the rest of the project, but some effort has been made to integrate it into TIGCC. For example, you can now call a function from a static library using a simple bsr or jsr instruction.

This assembler comes with its own set of header files. All of them are included mainly for compatibility reasons (note that some of them are deprecated, obsolete, inconsistent or even obscure), so they will not be described here. Information about them may be found in various ASM tutorials for TI-89 and TI-92+ (also deprecated, but note that nearly 95% of all ASM programs for TI-89 and TI-92+ are written using a now deprecated way, because a lot of information about the system was not available at the time when these programs were created). We recommend using only OS.h, which contains a list of equates for ROM_CALLs (but the ROM_CALL macros are not very optimized and should thus be avoided), and functions from TIGCCLIB, which need no header file at all (for example, bsr GrayOn is enough to call the GrayOn function). What will be presented here is the original A68k documentation, written by Charlie Gibbs himself. We have however annotated it in some places to reflect the changes made in recent versions, and we have added 2 chapters: the list of supported assembler directives, which was missing, and the history, which was kept in a separate file.


Introduction - About A68k

A68k - a freely distributable assembler for the Amiga
by Charlie Gibbs
with special thanks to
Brian R. Anderson and Jeff Lydiatt
The current versions are now part of the TIGCC project. As such, they will be aimed much more at development for the TI-89 and TI-92+ calculators than at the Amiga. Modifications of this document are marked with (TIGCC)
(Version 2.71.F3w - July 27, 2006)


Note: This program is Freely Distributable, as opposed to Public Domain. Permission is given to freely distribute this program provided no fee is charged, and this documentation file is included with the program.

This assembler is based on Brian R. Anderson's 68000 cross- assembler published in Dr. Dobb's Journal, April through June 1986. I have converted it to produce AmigaDOS-format object modules, and have made many enhancements, such as macros and INCLUDE files.

My first step was to convert the original Modula-2 code into C. I did this for two reasons. First, I had access to a C compiler, but not a Modula-2 compiler. Second, I like C better anyway.

The executable code generator code (GetObjectCode and MergeModes) is essentially the same as in the original article, aside from its translation into C. I have almost completely rewritten the remainder of the code, however, in order to remove restrictions, add enhancements, and adapt it to the AmigaDOS environment. Since the only reference book available to me was the AmigaDOS Developer's Manual (Bantam, February 1986), this document describes the assembler in terms of that book.


Restrictions

Let's get these out of the way first:


Extensions

Now for the good stuff:


A68k Assembler Directives

Note: This section has been written entirely by the TIGCC Team. Part of the information comes from direct.txt by Mathieu Lacage, but the whole text is completely rewritten and contains more details. Also, some of the directives listed here are not documented in direct.txt.

The A68k assembler supports the following assembler directives:

arithmetic operators Arithmetic operators can be used exactly like their C counterparts, except that spaces are not allowed in an expression. But ((1+2)>>3)/4 is a perfectly legitimate A68k expression. Of course, you can not use anything other than + or - on labels and you cannot use arithmetic on registers at all - you will have to use the corresponding assembly instructions.
INCLUDE "filename.h" Includes the file given as a parameter at the current source position. The file is read in as assembly source code, as if it was part of the current assembly source.
INCBIN "filename.bin" Includes the binary content of the file given as a parameter as-is at the current location in the object file output. The file will NOT be assembled, but included as binary data.
EVEN Forces alignment on an even address. The next instruction will be guaranteed to be placed at an even address. This is especially important for dc.w or dc.l directives, since accessing a word or longword at an odd address causes an address error.
CNOP displacement,alignment Forces alignment on an address which is the sum of displacement, which has to be even, and a multiple of alignment, which has to be a power of 2.
XDEF label Exports the label given as an argument to other object files or to the linker. This also allows linker directives like xdef _nostub.
XREF label Imports the label given as an argument. This allows to access variables from other object files even if the '-g' switch is not specified. Since TIGCC specifies '-g' by default, there is no real need for this directive.
PUBLIC label Imports or exports the label given as an argument, depending on whether it is part of the current source file or not. We recommend using XDEF or XREF instead when possible, since they clearly specify what is meant. But PUBLIC is useful when used in a common header file for multiple source files.
SECTION "sectname" Sets the section the following code will be written to. This is especially useful to make A68k use the same section as GCC: .data, in order to make sure the linking order of GCC files first is respected. (Note: You can deliberately NOT specify SECTION ".data" if you want the A68k file to be the main file.)
BSS Specifies the section the following code will be written to as a "BSS section", a section which will not be initialized at runtime, so it can contain only storage place which does not need to be initialized. Also, BSS sections are not allowed in _nostub mode.
CSEG Creates a section called "CODE" where the code will be written to. This directive is not really useful in TIGCC, since both code and data are written to the .data section by GCC and GNU as.
DSEG Creates a section called "DATA" where the code will be written to. Do not use this directive in TIGCC. Instead, specify SECTION .data, which is the section name used by GCC and GNU as for data sections.
END Marks the end of the source file. This directive is now optional since v.2.71.F3c.
DC.{B|W|L} data1,data2,... (Define Constant(s)) Will insert all the arguments as immediate data into the object file. For example, DC.L $12345678 will insert the bytes 0x12, 0x34, 0x56 and 0x78 into the resulting object file. DC.B 'Hello, World!',0 will insert the null-terminated string "Hello, World!" into the resulting object file.
DS.{B|W|L} n (Define Storage) Will insert n bytes, words or longwords (depending on the size specified) of zeros (0, NOT '0') into the resulting object file.
DCB.{B|W|L} n,data (Define Constant Block) Will act as n times the DC.B data instruction. It will insert the immediated data specified by data n times into the resulting object file.
macroName MACRO
 instructions
 ENDM
This declares a macro so that macroName can be used as if it was an instruction, with the difference that unlike opcodes, macro names are case-sensitive. You can also pass arguments to a macro. Those are referenced by a macro as \n, where n is the number of the argument, starting from 1. \0 indicates the instruction size. \@ indicates the number of the current macro, which is useful to create unique labels of the form \@label or \\@localLbl. Therefore:

PUSH MACRO
     MOVE.\0 \1,-(a7)
     ENDM

(defined in "OS.h") allows you to use: PUSH.L #1 when in fact you mean: MOVE.L #1,-(a7).

<expression> Makes sure expression will be treated as a single macro parameter, even if it contains separators like commas, semicolons or whitespace.
constant EQU value
OR: constant = value
Defines a symbolic constant. Wherever constant is encountered in the code, it will be replaced by value. Note that value can also be an expression, not only a single number.
regAlias EQUR register Defines an alias for a register. For example, if you are using a5 to hold the address of the jump table in your whole program, you can write jumpTbl EQUR a5, and then use jumpTbl wherever you would use a5.
regListAlias EQUR regList Defines an alias for a list of registers, for use in the MOVEM instruction. The syntax of regList is the same as in the MOVEM instruction. For example, if you are frequently using MOVEM.L d0-d7/a0-a6,-(a7) and MOVEM.L (a7)+,d0-d7/a0-a6, you might define: all REG d0-d7/a0-a6, and then use all wherever you would use d0-d7/a0-a6.
variable SET value Defines a symbolic variable. Wherever variable is encountered in the code, it will be replaced by value. But contrary to an EQU constant, a SET variable can be modified. It can therefore be used as a counter for example, as in: counter SET counter+1.
IFEQ value
instructions
ENDC
The instructions in the block will be parsed and assembled if and only if value equals 0. You can also write ENDIF as a synonym for ENDC.
IFNE value
instructions
ENDC
The instructions in the block will be parsed and assembled if and only if value does not equal 0. You can also write ENDIF as a synonym for ENDC.
IFGE value
instructions
ENDC
The instructions in the block will be parsed and assembled if and only if value is greater than or equal to 0. You can also write ENDIF as a synonym for ENDC.
IFGT value
instructions
ENDC
The instructions in the block will be parsed and assembled if and only if value is greater than 0. You can also write ENDIF as a synonym for ENDC.
IFLE value
instructions
ENDC
The instructions in the block will be parsed and assembled if and only if value is less than or equal to 0. You can also write ENDIF as a synonym for ENDC.
IFLT value
instructions
ENDC
The instructions in the block will be parsed and assembled if and only if value is less than 0. You can also write ENDIF as a synonym for ENDC.
IFC string1,string2
instructions
ENDC
The instructions in the block will be parsed and assembled if and only if string1 and string2 are identical (after expansion of EQU constants). You can also write ENDIF as a synonym for ENDC.
IFNC string1,string2
instructions
ENDC
The instructions in the block will be parsed and assembled if and only if string1 and string2 are different (after expansion of EQU constants). You can also write ENDIF as a synonym for ENDC.
IFD value
instructions
ENDC
The instructions in the block will be parsed and assembled if and only if value is a defined symbol. You can also write ENDIF as a synonym for ENDC.
IFND value
instructions
ENDC
The instructions in the block will be parsed and assembled if and only if value is not a defined symbol. You can also write ENDIF as a synonym for ENDC.
NEAR [register] Enables the small code and data model.
FAR Disables the small code and data model after a NEAR directive.
ORG expression Places the code following the directive at the address specified by expression, which must be relocatable (that is, of the form label+number, where label is a backward reference, not a forward reference). If that address is located before the current position, the code previously generated there will be overwritten. If that adress is located after the current position, the space will be filled with zeros (0, NOT '0').
RORG offset Places the code following the directive at the offset offset, which must be a number, from the beginning of the current object file section. If that address is located before the current position, the code previously generated there will be overwritten. If that adress is located after the current position, the space will be filled with zeros (0, NOT '0'). However, if RORG is specified before the first executable instruction and in the default section (before any SECTION, BSS, CSEG or DSEG directive), no spacing will be generated, so it has the effect of adding the number address to all absolute references, thereby pre-relocating the code to that fixed address. But such an use of this directive is not useful in TIGCC, as assembly programs on the TI-89/92+ can be at any place in memory, and the linker always generates a relocation table which allows AMS to take care of the relocation automatically.
NOLIST The code following this directive will not appear in the listing file created when the '-l' or '-x' switch is specified.
LIST Reenables the listing of code after a NOLIST directive.
PAGE Inserts a form feed into the listing file created when the '-l' or '-x' switch is specified.
SPC [n] Skips n lines, or 1 line if n is omitted, in the listing file created when the '-l' or '-x' switch is specified.
TTL string
OR: TITLE string
Sets the title on the pages of the listing file created when the '-l' or '-x' switch is specified to string. Also begins a new page when specified on the middle of a page.
IDNT "name" Sets the program unit name in the S-record generated when the '-s' switch is specified, to name. The quotes can be omitted. This directive is not useful in TIGCC, since TIGCC does not use S-records.
\localLbl Defines a local label, a label which can be accessed only in the space between the 2 global labels surrounding it. This mainly serves to avoid name conflicts: 2 or more local labels can have the same name as long as they are separated by a global label.
\localLbl@labelNum Defines a local enumeration label, invented mainly for use in macros. The idea is that labelNum can be a variable name which can be expanded while keeping localLbl unchanged. But it is usually a better idea to use \@label or \\@localLbl instead, which will automatically generate unique labels.


The Small Code/Data model

Version 2.4 implements a rudimentary small code/data model. It consists of converting any data reference to one of the following three addressing modes:

These conversions do not take place unless a NEAR directive is encountered. The NEAR directive can take one operand, which must be either an address register or a symbol which has been equated (using EQUR) to an address register. Register A7 (SP) may not be used. If no register is given, A4 is assumed.

Conversion is done for all operands until a FAR directive is encountered. NEAR and FAR directives can occur any number of times, enabling conversion to be turned on and off at will.

Backward references which cannot be converted (e.g. external labels declared as XREF) will remain as absolute long addressing. All forward references are assumed to be convertible, since during pass 1 A68k has no way of telling whether conversion is possible. If conversion turns out to be impossible, invalid object code will be generated - an error message ("Invalid forward reference") will indicate when this occurs.

Although the small code/data model can greatly reduce the size of assembled programs, several restrictions apply:

I'll be the first to admit that this is a very crude and ugly implementation. I hope to improve it in future versions.


Files

A68k uses the following files:


File Names

The names of the above files can be explicitly specified. However, A68k will generate default file names in the following cases:

A default name is generated by deriving a stem name from the source code file name, and appending '.o' for an object code file name ('.s' if the '-s' switch is specified to produce Motorola S-records), '.equ' for an equate file name, or '.lst' for a listing file name. The stem name consists of all characters of the source file name up to the last period (or the entire source file name if it contains no period). Here are some examples of default names:

Source fileObject fileEquate fileListing file
myprog.asmmyprog.omyprog.equmyprog.lst
myprogmyprog.omyprog.equmyprog.lst
new.prog.asmnew.prog.onew.prog.equnew.prog.lst


A68k Command-Line Parameters (How to use A68k)

The command-line syntax to run the assembler is as follows:

   a68k <source file name>
        [<object file name>]
        [<listing file name>]
(TIGCC) [-a]
        [-d[[!]<prefix>]]
        [-e[<equate file name>]]
        [-f]
        [-g]
        [-h<header file name>]
        [-i<include directory list>]
        [-k]
        [-l[<listing file name>]]
        [-m<small data offset>]
        [-n]
        [-o<object file name>]
        [-p<page depth>]
        [-q[<quiet interval>]]
(TIGCC) [-r[a][l][m]]
        [-s]
        [-t]
(TIGCC) [-u]
(TIGCC) [-v<name>[,<value>]
        [-w[<hash table size>][,<secondary heap size>]]
        [-x[<listing file name>]]
        [-y]
        [-z[<debug start line>][,<debug end line>]]

These options can be given in any order. Any parameter which is not a switch (denoted by a leading hyphen) is assumed to be a file name; up to three file names (assumed to be source, object, and listing file names respectively) can be given. A source file name is always required. If a switch is being given a value, that value must immediately follow the switch letter with no intervening spaces. For instance, to specify a page depth of 40 lines, the specification '-p40' should be used; '-p 40' will be rejected.

Switches perform the following actions:

-a

Causes all relocs to be emitted, even PC-relative relocs within a section. It also emits address differences in a special TIGCC-specific format. This will allow more aggressive linker-side optimization.

-d

Causes symbol table entries (hunk_symbol) to be written to the object module for the use of symbolic debuggers. If the switch is followed by a string of characters, only those symbols beginning with that prefix string will be written. This can be used to suppress internal symbols generated by compilers. If the first character is an exclamation mark ('!'), only symbols which do not begin with the following characters are written out. Here are some examples:

-dwrites all symbols
-dabc   writes only symbols beginning with "abc"
-d!xwrites symbols which do not begin with "x"

-e

Causes an equate file (see above) to be produced. A file name can be specified; otherwise a default name will be used.

-f

Causes any branches (Bcc, BRA, BSR) that could be converted to short form to be flagged. A68k will convert as many branches as possible to short form (unless the '-n' switch is is specified), but certain combinations of instructions may set up a ripple effect where shortening one branch brings another one into range. This switch will cause A68k to flag any branches that it may have missed; during pass 2 it is possible to tell this, although during pass 1 it might not be. If the '-n' switch (see below) is specified along with this switch (suppressing all optimization), no branches will be shortened, but all branches which could be shortened will be flagged.

-g

Causes any undefined symbols to be treated as if they were externally defined (XREF), rather than being flagged as errors.

-h

Causes a header file to be read prior to the source code file. A file name must be given. The action is the same as if the first statement of the source file were an INCLUDE statement naming the header file. To find the header file, the same directories will be searched as for INCLUDE files (see the '-i' switch below).

-i

Specifies directories to be searched for INCLUDE files in addition to the current directory. Several names, separated by commas, may be specified. No embedded blanks are allowed. For example, the specification

-imylib,df1:another.lib

will cause INCLUDE files to be searched for first in the current directory, then in "mylib", then in "df1:another.lib".

-k

Causes the object file to be kept even if any errors were found. Otherwise, it will be scratched if any errors occur.

-l

Causes a listing file to be produced. If you want the listing file to include a symbol table dump and cross-reference, use the '-x' switch instead (see below).

-m

Changes the assumed offset from the start of the DATA/BSS section to the base register used when the small code/data option is activated by the NEAR directive. If this parameter is not specified, the offset defaults to 32768.

-n

Causes all object code optimization (see above) to be disabled.

-o

Allows the default name for the object code file (see above) to be overridden.

-p

Causes the page depth to be set to the specified value. This takes the place of the PLEN directive in the Metacomco assembler. Page depth defaults to 60 lines ('-p60').

-q

Changes the interval at which A68k displays the line number it has reached in its progress through the assembly. The default is to display every 100 lines ('-q100'). Specifying larger values reduces console I/O, making assemblies run slightly faster.

If you specify a negative number (e.g. '-q-10'), line numbers will be displayed at an interval equal to the absolute value of the specified number, but will be given as positions within the current module (source, macro, or INCLUDE) rather than as a total statement count - the module name will also be displayed.

A special case is the value zero ('-q0' or just '-q') - this will cause all console output, except for error messages, to be suppressed.

(TIGCC) -r

Allows to disable specific optimizations:
-rm Disable the MOVEM -> MOVE optimization
-ra Disable the ADD(A)/SUB(A) -> LEA optimization
-rl Disable the LEA -> ADDQ/SUBQ optimization
You might use more than one -r switch (as in "-rm -ra") or combine them into a single switch (as in "-rma").

-s

Causes the object file to be written in Motorola S-record format, rather than AmigaDOS format. The default name for an S-record file ends with '.s' rather than '.o; this can still be overridden with the '-o' switch, though.

-t

Allows tabs in the source file to be passed through to the listing file, rather than being expanded. In addition, tabs will be generated in the listing file to skip from the object code to the source statement, etc. This can greatly reduce the size of the listing file, as well as making it quicker to produce. Do not use this option if you will be displaying or listing the list file on a device which does not assume a tab stop at every 8th position.

(TIGCC) -u

Disables automatic alignment of DC.W, DC.L, DCB.W, DCB.L, DS.W, DS.L and code.

(TIGCC) -v

Allows to set a variable in the command line (like -d with GCC). The variable will be a SET, not EQU variable. Syntax: "-v<name>[,<value>]" (without spaces, and without the quotes). Note that <value> can only be a NUMBER at the moment. (We might support symbols as values in a future version.) The default value of <value> is 1.

-w

Specifies the sizes of fixed memory areas that A68k allocates for its own use. You should normally never have to specify this switch, but it may be useful for tuning.

The first parameter gives the number of entries that the hash table (used for searching the symbol table) will contain. The default value of 2047 should be enough for all but the very largest programs. The assembly will not fail if this value is too small, but may slow down if too many long hash chains must be searched. The hashing statistics displayed by the '-y' switch (see below) can be used to tune this parameter. I've heard that you should really specify a prime number for this parameter, but I haven't gone into hashing theory enough to know whether it's actually necessary.

The second parameter of the '-w' switch specifies the size (in bytes) of the secondary heap, which is used to store nested macro and INCLUDE file information (see below). It defaults to 1024, which should be enough unless you use very deeply nested macros and/or INCLUDE files with long path names.

(TIGCC) The default sizes are 4095,2048 in the Win32 (since v.2.71.F3a) and GNU/Linux (since v.2.71.F3c) versions.


You can specify either or both parameters. For example:

-w4093secondary heap size remains at 1024 bytes
-w,2000hash table size remains at 2047 entries
-w4093,2000   increases the size of both areas

If you're really tight for memory, and are assembling small modules, you can use this switch to shrink these areas below their default sizes. At the end of an assembly, a message will be displayed giving the sizes actually used, in the form of the '-w' command you would have to enter to allocate that much space. This is primarily useful to see how much secondary heap space was used.

Note: All other memory used by A68k (e.g. the actual symbol table) is allocated as required (currently in 8K chunks).

-x

Works the same as '-l' (see above), except that a symbol table dump, including cross-reference information, will be added to the end of the listing file.

-y

Causes hashing statistics to be displayed at the end of the assembly. First the number of symbols in the table is given, followed by a summary of hash chains by length. Chains with length zero denote unused hash table entries. Ideally (i.e. if there were no collisions) there should be as many chains with length 1 as there are symbols, and there should be no chains of length 2 or greater. I added this option to help me tune my hashing algorithm, but you can also use it to see whether you should allocate a larger hash table (using the first parameter of the '-w' switch, see above).

-z

This switch was provided to help debug A68k itself. It causes A68k to list a range of source lines, complete with line number and current location counter value, during both passes. Lines are listed immediately after they have been read from the source file, before any processing occurs. Here are some examples of the '-z' switch:

-zlists all source lines
-z100,200   lists lines 100 through 200
-z100lists all lines starting at 100
-z,100lists the first 100 lines


Technical Information

The actual symbol table entries (pointed to by the hash table, colliding entries are linked together) are stored in 8K chunks which are allocated as required. The first entry of each chunk is reserved as a link to the next chunk (or NULL in the last chunk) - this makes it easy to find all the chunks to free them when we're finished. All symbol table entries are stored in pass 1. During pass 2, cross- reference table entries are built in the same group of chunks, immediately following the last symbol table entry. Additional chunks will continue to be linked in if necessary.

Symbol names and macro text are stored in another series of linked chunks. These chunks consist of a link pointer followed by strings (terminated by nulls) laid end to end. Symbols are independent entries, linked from the corresponding symbol table entry. Macros are stored as consecutive strings, one per line - the end of the macro is indicated by an ENDM statement. If a macro spans two chunks, the last line in the original chunk is followed by a newline character to indicate that the macro is continued in the next chunk.

Relocation information is built during pass 2 in yet another series of linked chunks. If more than one chunk is needed to hold one section's relocation information, all additional chunks are released at the end of the section.

The secondary heap is built from both ends, and it grows and shrinks according to how many macros and INCLUDE files are currently open. At all times there will be at least one entry on the heap, for the original source code file. The expression parser also uses the secondary heap to store its working stacks - this space is freed as soon as an expression has been evaluated.

The bottom of the heap holds the names of the source code file and any macro or INCLUDE files that are currently open. The full path is given. A null string is stored for user macros. Macro arguments are stored by additional strings, one for each argument in the macro call line. All strings are stored in minimum space, similar to the labels and user macro text on the primary heap. File names are pointed to by the fixed table entries (see below) - macro arguments are accessed by stepping past the macro name to the desired argument, unless NARG would be exceeded.

The fixed portion of the heap is built down from the top. Each entry occupies 16 bytes. Enough information is stored to return to the proper position in the outer file once the current macro or INCLUDE file has been completely processed.

The diagram below illustrates the layout of the secondary heap.

Heap2 + maxheap2 ----------->  ___________________________
                              |                           |
                              |   Input file table        |
struct InFCtl *InF ---------> |___________________________|
                              |                           |
                              |   Parser operator stack   |
struct OpStack *Ops --------> |___________________________|
                              |                           |
                              |   (unused space)          |
struct TermStack *Term -----> |___________________________|
                              |                           |
                              |   Parser term stack       |
char *NextFNS --------------> |___________________________|
                              |                           |
                              |   Input file name stack   |
char *Heap2 ----------------> |___________________________|

The "high-water mark" for NextFNS is stored in char *High2, and the "low-water mark" (to stretch a metaphor) for InF is stored in struct InFCtl *LowInF. These figures are used only to determine the maximum heap usage.


A68k Contact Information and Bug Reports

And Finally...

Please send me any bug reports, flames, etc. I can be reached on Mind Link (604/533-2312), at any meeting of the Commodore Computer Club / Panorama (PAcific NORthwest AMiga Association), or via Jeff Lydiatt or Larry Phillips. I don't have the time or money to live on Compuserve or BIX, but my Usenet address is Charlie_Gibbs@mindlink.UUCP (...uunet!van-bc!rsoft!mindlink!a218).

Charlie Gibbs
2121 Rindall Avenue
Port Coquitlam, B.C.
Canada
V3C 1T9


(TIGCC) Since, as far as we can tell, Charlie Gibbs is no longer maintaining A68k since 1991, you are better off filing a bug report to Kevin Kofler of the TIGCC team: Kevin@tigcc.ticalc.org (or kevin.kofler@chello.at)


History of A68k

Note: This section has been added to the documentation by the TIGCC Team. It was in history.txt before.

Version 2.71.F3w (Kevin Kofler, July 27, 2006)

Version 2.71.F3v (Kevin Kofler, July 31, 2005)

Version 2.71.F3u (Kevin Kofler, February 2, 2005)

Version 2.71.F3t (Kevin Kofler, January 31, 2005)

Version 2.71.F3s (Kevin Kofler, September 21, 2004)

Version 2.71.F3r (Kevin Kofler, July 19, 2004)

Version 2.71.F3q (Kevin Kofler, December 30, 2003)

Version 2.71.F3p (Kevin Kofler, December 1, 2003)

Version 2.71.F3o (Kevin Kofler, October 5, 2003)

Version 2.71.F3n (Kevin Kofler, September 28, 2003)

Version 2.71.F3m (Kevin Kofler, September 2, 2003)

Version 2.71.F3l (Kevin Kofler, July 15, 2003)

Version 2.71.F3k (Kevin Kofler, May 17, 2003)

Version 2.71.F3i (Kevin Kofler, February 1, 2002)

Version 2.71.F3h (Kevin Kofler, January 23, 2002)

Version 2.71.F3g (Kevin Kofler, January 22, 2002)

Version 2.71.F3f (Kevin Kofler, January 8, 2002)

Version 2.71.F3e (Kevin Kofler, August 3, 2001)

Version 2.71.F3d (Kevin Kofler, July 28, 2001)

Version 2.71.F3c (Kevin Kofler, July 20, 2001)

Version 2.71.F3b (Kevin Kofler, July 12, 2001)

Version 2.71.F3a (Kevin Kofler, July 9, 2001)

Version 2.71.F3 (David Ellsworth, July 11, 2000)

Version 2.71.F2 (David Ellsworth, January 22, 1998)

Version 2.71.F1 (David Ellsworth, January 7, 1998)

Version 2.71 (Charlie Gibbs, April 16, 1991)

The following bugs in version 2.62 have been corrected:

The following enhancements have been added:

Version 2.70 (Charlie Gibbs, February 25, 1991)

The following bugs in version 2.62 have been corrected:

The following enhancements have been added:

Version 2.62 (Charlie Gibbs, March 19, 1990)

The following enhancements have been added:

Version 2.61 (Charlie Gibbs, January 11, 1990)

The following bugs in version 2.6 have been corrected:

The following enhancements have been added:

Version 2.6 (Charlie Gibbs, November 2, 1989)

The following bugs in version 2.5 have been corrected:

The following enhancements have been added:

Version 2.5 (Charlie Gibbs, June 18, 1989)

The following bugs in version 2.42 have been corrected:

The following enhancements have been added:

Version 2.42 (Charlie Gibbs, January 10, 1989)

The following bugs in version 2.41 have been corrected:

Version 2.41 (Charlie Gibbs, January 6, 1989)

The following bugs in version 2.4 have been corrected:

Version 2.4 (Charlie Gibbs, January 4, 1989)

The following bugs in version 2.31 have been corrected:

The following enhancements have been added:

Version 2.31 (Charlie Gibbs, November 30, 1988)

The following bugs in version 2.3 have been corrected:

Version 2.3 (Charlie Gibbs, November 21, 1988)

The following enhancements have been added:

Version 2.2 (Charlie Gibbs, November 4, 1988)

The following bugs in version 2.1 have been corrected:

The following enhancements have been added:

Version 2.1 (Charlie Gibbs, November 1, 1988)

The following bugs in version 2.00 have been corrected:

The following enhancements have been added:

Version 2.00 (Charlie Gibbs, October 26, 1988)

The following bugs in version 1.24 have been corrected:

The following enhancements have been added:

Version 1.24 (Charlie Gibbs, October 11, 1988)

The following bugs in version 1.23 have been corrected:

The following enhancements have been added:

Version 1.23 (Charlie Gibbs, September 20, 1988)

The following bugs in version 1.22 have been corrected:

Version 1.22 (Charlie Gibbs, August 31, 1988)

The following bugs in version 1.21 have been corrected:

The following enhancements have been added:

Version 1.21 (Charlie Gibbs, July 29, 1988)

The following bugs in version 1.2 have been corrected:

Version 1.2 (Charlie Gibbs, July 19, 1988)

The following bugs in version 1.12 have been corrected:

The following enhancements have been added:

Version 1.12 (Charlie Gibbs, May 25, 1988)

The following bugs in version 1.11 have been corrected:

Version 1.11 (Charlie Gibbs, April 6, 1988)

The following bugs in version 1.10 have been corrected:

The following enhancements have been added:

Version 1.10 (Charlie Gibbs, March 20, 1988)

The following bugs in version 1.07 have been corrected:

The following enhancements have been added:

Version 1.07 (Charlie Gibbs, March 11, 1988)

The following bugs in version 1.06 have been corrected:

The following enhancements have been added:

Version 1.06 (Charlie Gibbs, March 6, 1988)

The following bugs in version 1.05 have been corrected:

The following enhancements have been added:

Version 1.05 (Charlie Gibbs, October 30, 1987)

The following bugs in version 1.04 have been corrected:

The following enhancements have been added:

Version 1.04 (Charlie Gibbs, October 21, 1987)

The following bugs in version 1.03 have been corrected:

Version 1.03 (Charlie Gibbs, October 14, 1987)

The following bugs in version 1.02 have been corrected:

The following enhancements have been added:

Version 1.02 (Charlie Gibbs, September 9, 1987)

The following bugs in version 1.01 have been corrected:

The following enhancements have been added:

The following changes have been made to existing logic:

Version 1.01 (Charlie Gibbs, August 20, 1987)

The following bugs in version 1.00 have been corrected:

Version 1.00 (Charlie Gibbs, June 18, 1987) - initial release


Return to the main index