Interfacing Assembly Language with Modula-2



There are times when it would be convenient to use Assembly Language within a
Modula-2 program. Assembly Language can be used to speed up critical sections
of a program or to implement operating system calls. Modula-2 provides a number
of low-level facilities that make it easy to do just that.

Every Modula-2 environment includes the pseudo module SYSTEM which exports
machine specific procedures and data types. These facilities provide the
necessary tools to interface Assembly Language routines with Modula-2. The
important point to remember is that anything exported by SYSTEM may have a
different meaning to another Modula-2 compiler. For example, the type BYTE is
usually an eight bit memory storage unit, but on some systems it is allocated
as sixteen bits. Differences such as this can apply to anything you import from
SYSTEM so it is wise to limit such imports if you want to create portable
programs.

The following steps demonstrate how to insert Assembly Language code into a
Modula-2 program using the facilities of module SYSTEM. This example implements
the AMOS SLEEP monitor call.

STEP ONE: Create the Assembly Language source file and assemble it.

For this example we have created an Assembly Language source file called
SNOOZE.M68 and assembled it with the AMOS M68 assembler. The text of the source
file is:

    ; SNOOZE.M68

            SEARCH  SYS                     ; Search AMOS System definitions

            SAVE    A4,A5,A6                ; Save Modula-2 registers
            MOV     #1,D0                   ; Set result register
            SLEEP   D6                      ; Call SLEEP with arg in D6
            BNE     WOKEUP                  ; Branch if awakened by another job
            CLR     D0                      ; Clear result if not awakened
    WOKEUP: REST    A4,A5,A6                ; Restore Modula-2 registers

            END

The most important thing to note is that you must save the A4, A5 and A6
processor registers if they might get changed by your code. The Modula-2
program will give unpredictable results otherwise. Almost all AMOS Monitor
Calls change A6, and if you are in doubt about A4 and A5 save them also.  Most
monitor calls set or clear the processor Z Flag to indicate the result of the
call. The SLEEP call does this to indicate whether the job was awakened by
another job before the sleep period expired. This example uses register D0 to
communicate this result back to the Modula-2 program.


STEP TWO: Convert the assembled file to Modula-2 INLINE statements.

The next step is to convert the assembled file to Modula-2 statements. For this
example we used the program CVTHEX which is available on the AMUS Net in the
Modula-2 SIG Directory. The output file from CVTHEX for our example looks like
this:

    INLINE(048E7H,0000EH,07001H,0A046H,06602H);
    INLINE(04280H,04CDFH,07000H);

The CVTHEX program converts the assembled (.LIT) file to Modula-2 INLINE
statements containing hexadecimal constants representing the machine code.
These INLINE statements can be inserted into our Modula-2 source file.


STEP THREE: Insert the INLINE statements into the program.

Now it is time to construct the final Snooze procedure using regular Modula-2
statements and the Assembly Language code. The source to the Snooze procedure
is:

    PROCEDURE Snooze(tenths: CARDINAL; VAR awakened: BOOLEAN);
    CONST
      D0 = 0;
      D6 = 6;
    BEGIN
      SETREG(D6,VAL(LONGCARD,tenths*1000);          (* put argument in reg D6 *)
      INLINE(048E7H,0000EH,07001H,0A046H,06602H);   (* inline code for SLEEP  *)
      INLINE(04280H,04CDFH,07000H);                 (* monitro call.          *)
      awakened := REG(D0) = 1D;                     (* set boolean result     *)
    END Snooze;

That's all there is to it. You will note that we have used the SETREG and REG
procedures from SYSTEM to set and read processor registers.  Each of these
procedures accepts a register number from 0 to 15 as the first argument.
Registers D0 to D7 are numbered from 0 to 7 and registers A0 to A7 are numbered
from 8 to 15 in decimal. The Snooze procedure allows the caller to specify the
sleep duration in tenths of seconds. Since the SLEEP monitor call expects its
argument to be in microseconds we multiply tenths by 1000 to keep everyone
happy. The boolean variable awakened is set according to the value in D0 to
signal whether the sleep was terminated by another job.

A few more notes are in order. It is usually a good idea to isolate inline code
from other parts of your program by placing it in a seperate procedure.  This
will assure that the only processor registers in use will be the ones discussed
above. If you must use inline code in the middle of a section of other Modula-2
code it is probably a good idea to save and restore all registers except A7.
The WITH statement for example, uses one of the address registers to accomplish
its purpose, if you were to overwrite this register your program would get
"lost". You may also use the ADR function to put the address of a variable into
a register. The following example puts the address of a variable called ddb
into the A1 register:

    SETREG(A1,ADR(ddb));

Since the SETREG and REG procedures require that their second argument always
be of longword size you can use the VAL function to transfer to the required
data type. For example:

    SETREG(D0,VAL(LONGCARD,AnyTypeOfVar):

    AnyTypeOfVar := VAL(DestType,REG(D0));

I hope you have found this brief guide to be useful. Please post any questions
or comments to the bulletin board or send email.

David Tingler (TSI/AM)