Structured Programming in Macro-11: SUMAC Structured User Macros by Bob Schor February, 1987 Updated December, 1988; May, 1989 The "structured programming" revolution has as its most visible manifestation the increasing popularity of simple Algol-like languages, of which the author's favorite is the language Pascal. These languages all embody certain key concepts. At one end is the notion of "top-down design", in which programs are conceived in large blocks (e.g. inputdata; performcomputations; writereport); each block, in turn, is broken up into simpler blocks, which are broken up into still simpler blocks, and so on until the inner most block is reached whose function it is to perform one elementary, easily-understood (and debugged!) operation. At the other end is the notion of program control, or "GOTO considered harmful", which structures code in terms of loops (WHILE, REPEAT, FOR), conditional branches (IF, CASE), procedure and function calls, and sequential operations. A number of attempts have been made to bring some of the benefits of structured programming, particularly the simple control structure and the top-down block concept, to the programmer who must interact intimately with machine hardware, the traditional habitat of the machine language programmer. Examples of such languages are C, Modula, and perhaps Ada. However, these languages are often deeply embedded in specialized programming environments, and are not included in the typical bundled software package from the computer's manufacturer. What typically is provided, however, is an assembler with the ability to construct "macros", or "macro-instructions", new user-defined instructions outside the normal instruction set of the hardware. By defining a suitable subset of these macros, it is possible to thereby define such items as WHILE loops, IF statements, and block structures. One such package, designed for the Digital Equipment Corporation Macro-11 assembler for the PDP-11 series of computers is SUMAC, described herein. Versions of this package have been written for the DEC operating system RT-11 and for the time-sharing system TSX (S&H Computers). The macros should largely be compatible with RSX, with the exception of the terminal output routines. The SUMAC package uses standard PDP-11 mnemonics for much of the sequential coding, so that a program written using these macros retains much of the appearance of a Macro-11 program. It will be assumed that the reader has familiarity with PDP-11 assembly programming, though no familiarity with writing and using macros themselves will be necessary. The macros in this package are used to provide a certain amount of - 1 - Macros for Structured Programming structure to the assembly program, which is then assembled in the normal manner (adding that SUMAC is to be used as a supplemental macro library). Examples will be given below. Structured languages like Pascal achieve block structure by use of PROGRAM, PROCEDURE, and FUNCTION blocks, which have variables local to their blocks, and which can contain their own (local) PROCEDURE and FUNCTION blocks. Such nesting of blocks, and definition of local variables, are not explicitly provided by this macro package. Block types provided are the PROCED and TASK blocks. The PROCED block is functionally equivalent to a well-structured procedure, in that it is entered as a subroutine at the top, and left at the bottom after the final instruction in the block is executed. Pascal procedures contain a mechanism for passing arguments into and out of the routine -- functions, in fact, can be simply considered procedures which return a value to an external argument. Two mechanisms exist in SUMAC for passing arguments. One method uses the method employed by RT-11 Fortran, in which the argument addresses follow the subroutine call, and the called routine can look backward to find the arguments. Advantage of this method is that it is Fortran-compatible, meaning that you can call Fortran subroutines using this type of call, as well as write routines which are called by Fortran. The other method is to follow the Pascal route of putting the arguments on the stack. The advantage of this method is that procedures can now call themselves (or be recursive), since the arguments do not live in fixed memory locations; the trade-off is that stack clean-up procedures are needed. The other type of block is the TASK block, which is reserved for interrupt service routines. Such a routine can be considered a special type of procedure -- it is also entered at the top and exited at the bottom, like a normal routine, but doesn't usually have "arguments" in the usual sense. Rather it usually manipulates some global values, such as filling a buffer, setting and clearing software flags, etc. Another difference between normal and interrupt routines is that interrupts occur asynchronously, that is, they can occur at any time in the program, so they aren't called from any fixed place. Thus a mechanism must be found to return to precisely the spot in the program at which the interrupt occurred. The exit from the TASK block automatically takes care of this. Within blocks, or within sections of the main program, macros exists for providing control of program control. Many of these macros will look familiar to the Pascal programmer, such as WHILE, REPEAT, IF, ELSE, and CASE. The similarities are deliberate, but some differences exist in their application. These program-control macros define a section of code which is to be executed under a specific set of conditions: WHILE a condition is true, IF a condition is true, or REPEATed UNTIL a condition is true. These macros define the beginning of the code to be executed; another macro, usually some form of END (such as ENDIF, ENDCASE, etc.), is required to signal the end of the block of code. For example, the IF macro has the form - 2 - Macros for Structured Programming IF arg1, cond, arg2 ; e.g. IF r0, lt, r1 stmt1 stmt2 ... stmtn ; code to be executed if true ENDIF ; macro to end block of code The section of code, which can be ordinary Macro-11 code or further SUMAC macros, will be executed (if the test specified in the statement is true) until the ENDIF statement is reached, signalling the end of the block. If the test in the IF statement is false, none of the statements between IF and ENDIF are executed. For correct operation, a macro which starts a block (such as IF, CASE, etc.) must have a corresponding form of END statement. Although the present version of the macro package doesn't contain code to specifically ensure that an ENDIF matches an IF, an UNTIL a REPEAT, some error checking is built-in. Blocks declared as PROCED or TASK blocks, which correspond to procedures (subroutines) and interrupt service routines, do contain code to check that within the block, there are neither too many nor too few END macros. In a well-written structured program, most code will be bracketed by PROCED ... ENDPROC or TASK ... ENDTASK macros, so most mismatch errors should be caught. Two further checks are made. If the number of END macros ever exceeds all the previous block-originating macros, an error is generated. Finally, an ENDEND macro is provided, which belongs at the end of the program, to ensure that no ENDs are missing. The PDP-11 instruction set contains both word-oriented and byte-oriented instructions, such as "TST" and "TSTB". Similarly, both word and byte versions of several of the macros have been provided (e.g. IF and IFB). The byte versions share the same name as the word version, with a "B" tacked on the end; the operation of the two forms of the macro is the same, except the byte form expects its operands to be bytes, and will use PDP-11 byte instructions in dealing with these operands. Control blocks which test conditions (such as IF, WHILE, REPEAT/UNTIL) can have the initial test extended by AND or OR (and byte version) tests. A number of macros have been provided as utility macros. These include the macros which facilitate Fortran-style subroutine calls and argument retrieval, along with macros to convert Fortran array indices (from 1 .. N bytes, integers, or reals) into an appropriate offset for indexing into the actual array. A second set of macros facilitate using the stack by providing convenient mechanisms for pushing and popping an arbitrary number of arguments. Macros are also provided for debugging and program checking. This includes the ENDEND macro mentioned above, which makes sure enough END macros have been specified. To permit typeouts of messages, a NOTE and PNOTE macro are provided. CHECK will output the value of any variable, both in octal and as a signed integer. The ASSERT macro can be used to make sure that at run time, certain conditions are true -- if the assertions are ever violated, the false assertion, and the values of the - 3 - Macros for Structured Programming variables which caused it to fail, will be output. Before getting into a detailed description of the macros and their usage, a few notes on implementation and addressing mode restrictions. Most of these macros allow any sensible addressing mode to be used, but caution should be exercised when using stack addressing (remember some macros, such as PUSH and POP, change the stack). Also note that if auto-increment or decrement modes are used, the registers involved will be modified when the macro is executed. To include the SUMAC package in a Macro-11 program, the macro names have to be defined using the ".mcall" statement. This is accomplished by using the SUMAC macro, which defines the entire package, and initializes certain internal variables. The form of this macro, which must be the first SUMAC macro used in the program, is .mcall SUMAC SUMAC A recent change (creating version 9.05) was to attempt to ensure that code generated by SUMAC is "pure", or suitable to be placed in an I-Space "instruction" program section. Three of the macros build data tables -- CASE builds a jump table, which is now placed in a data .psect named $CASE$; NOTE and PNOTE place strings in $TEXT$; FCALL (the FORTRAN call macro) builds its argument list in $DATA (where F4 normally placed them). Another minor change in this version was to remove an "illogical" (i.e. inconsistent) form of the PNOTE macro. SUMAC is constantly being maintained and upgraded by Bob Schor. A symbol, ".suver", has been added which encodes the current "version" in decimal (presently, .suver = 905., meaning version 9.05). RT-11 notes The macro package can be examined and modified by simply editing SUMAC.MAC. Note, however, that any changes to the package must be made with care to avoid inadvertantly destroying other sections. Furthermore, support cannot be offered for changes made without the author's knowledge and authorization. The source, when provided, is mainly for the edification of the advanced system programmer, who may need to know the internal workings of the package in order to untangle a complex coding error. A few changes were made to Macro-11 when Version 5 of RT-11 was announced; the two that matter are that lower case characters are now acceptable in macro definitions, and that the default extension for macro libraries is ".MLB". If you choose to use SUMAC with earlier versions of RT-11, you should take these changes into account. The macro library is constructed from the source version by using the librarian. To build a stand-alone macro library (the recommended procedure), - 4 - Macros for Structured Programming LIBRARY/MACRO SUMAC SUMAC will construct a macro library, SUMAC.MLB from the source SUMAC.MAC (for RT-11 Version 5 and later). To now assemble a program, say the demonstration program SUTEST.MAC using the macro library, MACRO SUTEST+SUMAC/LIB With the RT-11 version 5 indirect command file, one could define an indirect command which accepts an argument (SUTEST) and executes the above string. Another possibility is to incorporate the structured macros into the system macro library, SYSMAC.SML. This procedure is not recommended, as the contents of this DEC macro library are subject to change, and monkeying with it is probably not advisable. RT-11 version 5 also provides the .library command, which causes external libraries to automatically be searched. Thus if SUMAC.MLB is on device SY:, the Macro directive .library /SY:SUMAC/ will cause SUMAC to automatically be searched for the required macros. This means that the simple form of the MACRO (or COMPILE )statement will work, i.e. you can say simply MACRO SUTEST or COMPILE SUTEST. The message output macros (NOTE, CHECK) make use of the standard RT-11 system macros .TTYOUT and .PRINT. However, the macros have been coded so that the user does not have to worry about register saving and restoring (it is all done internally). TSX notes The macro package for TSX is identical to that for RT-11, and is used in exactly the same way. A useful command file for SY: is SUMAC.COM, which consists of the following -- COMPILE ^1+SY:SUMAC/LIB which is just the command required to implement a command suggested above for automatically invoking the macro package. Thus you would say SUMAC SUTEST to compile the demonstration program under TSX. TSX's real-time features make certain restrictions on interrupt service routines, namely that they must dispatch through the monitor via an "RTS PC" rather than directly via an "RTI". To force this change, the symbol "$TSX" must be defined as a non-zero quantity. - 5 - Macros for Structured Programming SUMAC macros, and calling sequences 1. Enabling macro SUMAC When invoked, this macro will ".mcall" all the rest of the macros in the package, and will perform necessary initialization. This macro should be the first structured macro used in the program. .mcall SUMAC ;include SUMAC as external library SUMAC ;initialize macro package Note that as of RT-11 Version 5, macro libraries such as SUMAC can be automatically searched by means of the .LIBRARY command. Thus if SUMAC.MLB was located on device SY:, .library /SY:SUMAC/ would cause SY:SUMAC.MLB to be searched for undefined (SUMAC) macro names. 2. Loop macros WHILE, WHILEB, ENDWHILE The WHILE macro takes up to three arguments, which are used to form a test which must be true in order to execute the code contained in the block (i.e. all code between the WHILE and its terminating ENDWHILE macro). Each time the block of code is executed, the program will loop back to the beginning and again test the condition specified by the WHILE macro, exiting only when the condition is false. Note that if the condition is false on entry, the code within the block is never executed. The form of the test for the WHILE macro is similar for several of the other macros, and will be described in detail here. The most general form of the macro is illustrated by the three-argument form, WHILE r1, lt, r2 which executes the loop while the relationship between the two arguments is true, in this case while the contents of r1 are less than those of r2. The two-argument form, illustrated by WHILE (sp)+, ne has an implicit second argument of zero, so the above will execute while the top value on the stack is non-zero. Note that this particular example has a side-effect (dangerous, in general) of changing the value of the stack pointer, similar to a POP. Finally, the one-argument form, WHILE cs - 6 - Macros for Structured Programming can be used to test a condition code, resulting from some previous operation. Any of the standard PDP-11 branch mnemonics (without the "b") can be used as the "test" -- "eq", "ne", "mi", "pl", "cs", "vs", "cc", "vc", "lt", "ge", "le", "gt", "hi", "los", "his", "lo". FOR, ENDFOR This macro defines a counting loop, similar to the Pascal FOR or Fortran DO statement. The macro takes three or four arguments, and has the following form -- FOR r0, #1, (r2) The effect of this loop is to assign to the first argument (which must be an assignable variable) the value specified in the second argument. Thus, in the example above, the number 1 is placed in r0. Then a (signed) comparison is made with the third argument (in the example, the value in r1, the number 1, is compared with the value contained in the address held in r2). If the variable is less than or equal to this final value, the code within the block (i.e. up to the matching ENDFOR macro) is executed, and the variable (r0) is incremented by 1, tested again, and so on until the variable exceeds the final value. Note that if the initial value exceeds the final value, the loop is never executed. Also note that within the loop, the value of the controlled variable (the first argument) is defined by the loop, and should not be changed within the loop. At the end of the loop, however, it should be assumed to be undefined, and its value unspecified (conforming to often-ignored Pascal and Fortran rules). The three-argument form of this macro, illustrated above, uses a default loop increment of 1. An optional fourth argument can be used to specify a different increment, which can be positive or negative. If a negative argument is used, the sense of the tests are reversed, so that the loop executes as long as the controlled loop index (the first argument) is greater than or equal to the final loop value (argument 3). Thus to execute the loop illustrated above, but with the values running backwards, FOR r0, (r2), #1, #-1 is appropriate. REPEAT, UNTIL, UNTILB A REPEAT loop is very similar to a WHILE loop, except that the test is made at the end of the loop, guaranteeing that the code within the loop will be executed at least once. Also, the WHILE loop executes as long as some condition is true, so that at the end of the loop, you know the condition is false; at the end of a REPEAT loop, however, the guarding - 7 - Macros for Structured Programming condition in the UNTIL clause is true. The REPEAT macro takes no arguments; the UNTIL macro takes one to three arguments, analogous to the WHILE macro. Thus a typical loop might look like REPEAT mov (r1)+, (r2)+ ;move a word UNTIL (r1), eq ;until 0 encountered An additional form of the UNTIL loop is also provided -- if no arguments are present, the (missing) test will never succeed and the REPEAT/UNTIL structure will become an endless loop. 3. Conditional macros IF, IFB, ENDIF The IF macro takes one to three arguments, like the WHILE macro. If the test is true, the code block within the macro is executed once; if the test is false, the code is skipped and not executed. This macro is terminated with either the ENDIF, ELSE, ELSIF, or ELSIFB macro (see below). ELSE Using the ELSE macro to terminate an IF block starts another block, terminated by an ENDIF macro, which contains the code executed if the test specified by the IF macro is false. Note that the ELSE macro should be used when two different actions are required, one if a condition is true, and another if the condition is false. If only a single action is required, a simple IF .. ENDIF block should suffice. For example, suppose you wanted to push the larger of two arguments onto the stack. Then you might say IF r1, ge, r2 PUSH r1 ;r1 >= r2, push larger ELSE PUSH r2 ;r2 larger ENDIF Note that the IF macro code is terminated by ELSE, which starts its own block that is, in turn, terminated by ENDIF. ELSIF, ELSIFB If the first IF fails, code in the ELSE .. ENDIF block will be executed. The ELSIF (or ELSIFB) macro combine the ELSE and IF (or IFB) macros, allowing a test to be included in the ELSE block without another IF .. ENDIF pair. The two sections of code below have the same effect. - 8 - Macros for Structured Programming IF r1, ge, r2 IF r1, ge, r2 PUSH r1 PUSH r1 ELSE IF r1, ge ELSIF r1, ge PUSH r2 PUSH r2 ELSE ELSE PUSH #0 PUSH #0 ENDIF ENDIF ENDIF ELSIF blocks, like IF blocks, can be terminated by ELSE, ENDIF, or another ELSIF. IFSTR Another specialized version of IF is used for comparing two strings of characters (bytes). A string is defined as a sequence of bytes, each of which is assumed to hold an ascii character. Strings either have a specific defined length, or else can be defined as having an unspecified length, but being terminated with a null (0) byte. If the strings have a defined length, then the form of the macro is IFSTR #stra, eq, #strb, #length code-if-strings-match ELSE code-if-strings-do-not-match ENDIF In this example, the strings which start at addresses "stra" and "strb" are compared byte by byte, for "length" bytes, and if each byte matches, the first bit of code is executed, otherwise the second is performed. Note that ELSE code can also be included. The other form of the macro is IFSTR first, le, second code ENDIF which works by assuming that the strings are terminated by nulls. This particular example looks at two strings whose first word addresses are stored in "first" and "second" (note absence of "#") and compares byte by byte until it reaches the end of the first string. Note that the end of the second string itself is not noticed. The above example can be used to see if the string pointed to by "first" precedes alphabetically that pointed to by "second". If a negative length is given, or null strings presented, the comparison will yield equality by default. - 9 - Macros for Structured Programming AND, OR, ANDB, ORB These macros can be used to extend the IF, WHILE, and UNTIL tests by adding further conditions. These tests are processed in the order they are encountered. For example, the sequence IF test1 AND test2 OR test3 AND test4 is equivalent to the boolean test ((test1 AND test2) OR test3) AND test4 CASE, CASEB, ENDEL, ENDCASE The IF macro makes a single test, and can have at most two outcomes. Occasionally a multi-way branch, similar to the Pascal CASE statement or the Fortran computed GOTO, is needed. The CASE macro provides such a mechanism, more similar to Fortran than Pascal. The form of the statement is CASE choice, opt1 : code ;executed if choice is 1 ENDEL opt2 : code ;executed if choice is 2 ENDEL ... optn : code ;executed if choice is n ENDEL ENDCASE ;marks end of entire macro In this macro, the first argument is the case selector -- it should evaluate to number between 1 and the total number of case options (note that there is nothing illegal with using immediate mode addressing, e.g. CASE #2, < ... >, except that such a call will always choose the same option, in this case, the second). The second argument consists of a list of addresses, enclosed in angle brackets (to make the macro consider it as a single argument). Each address starts a case element which contains appropriate code for that particular value of the case selector. Each case element ends with an ENDEL statement (it is optional, but recommended, for the final element), and an ENDCASE is used to end the entire CASE macro. If a null case option is present, for example CASE select, it will be interpreted to mean "do nothing" if selected (in the example above, if select evaluates to 3, no code will be executed, and the whole CASE statement up to the ENDCASE will be skipped). In addition, should the case selector evaluate to 0, a negative number, or to a number larger than - 10 - Macros for Structured Programming the highest option, no option will be selected and the CASE skipped. A note of caution -- this macro requires specifying addresses to define the options. If you want to use local symbols such as 1$, 2$, etc. for the option labels, then you must enable a local symbol block around the CASE statement, as the statement itself generates its own symbols. For example, .enabl lsb ;extend scope of local symbols CASE choice, <1$, 2$, ...> 1$ : code ENDEL 2$ : ... ENDEL ENDCASE ;end of CASE macro .dsabl lsb ;restrict local symbol scope 4. Block definition macros PROCED, QUIT, ENDPROC The PROCED macro defines a procedure, a set of code which can be considered as a subroutine, which is called through one of several means at various points in the program, and which returns to the statement following the calling statement at its termination. Methods exists to pass arguments back and forth across procedure boundaries, but they are not specified by the PROCED macro; it is the programmer's responsibility to know where and how arguments are passed, and to program accordingly (see below, the Fortran-compatible macros; also see the examples in the demonstration routine). The macro has an argument which will be used as a label to define the entrance to the procedure. Additional code in the macro causes a box to be generated in any listing file with the name of the procedure inside it. An additional second argument, a string enclosed in angle braces, may also be specified. If so, it causes a subtitle to be generated using the procedure name and the specified string. Thus PROCED sub, code ENDPROC ;will expand as RTS PC will cause the label "sub" to be generated, will make a listing file look like - 11 - Macros for Structured Programming PROCED sub, ; ; ************************* ; * * ; * sub * ; * * ; ************************* ; sub: code ENDPROC ;will expand as RTS PC and will create a subtitle of sub Demonstration subroutine The block defined by the PROCED macro must be terminated by an ENDPROC macro; when the ENDPROC is encountered, it causes a return to the calling routine. In addition, extra code is generated by the PROCED and ENDPROC macros to make sure that within their confines, there are an equal number of "starting" (e.g. IF, WHILE, etc.) and "ending" (ENDIF, ENDWHILE) macros. Sometimes it is necessary to leave a procedure prematurely, before reaching the ENDPROC macro. QUIT forces an immediate exit. It has no arguments. INVOKE The INVOKE macro is used to call a procedure defined by PROCED. Thus to use the procedure "sub" defined above, you would INVOKE sub ;next instruction executed on return from "sub" which would cause you to begin execution of the "sub" procedure. TASK, RESUME, ENDTASK The other type of code block, the interrupt service routine, is specified by the TASK macro. The format for this macro is identical to the PROCED macro, except that the code within the block is assumed to be an interrupt service routine, so that the terminal ENDTASK macro generates an appropriate "RTI" instruction. TASK clock, code ENDTASK ;will expand as RTI causes the label "clock" to be defined, sets up an appropriate subtitle, and causes the listing file to contain - 12 - Macros for Structured Programming TASK clock, ; ; ************************* ; * * ; * clock * ; * * ; ************************* ; ;>>>>>> Interrupt !! <<<<<< ; clock: code ENDTASK ;will expand as RTI RESUME is used to force a premature exit from an interrupt service routine, or TASK block. It is thus analogous to QUIT, above. LABEL This macro is used to generate significant address labels. It is called like the PROCED macro, with an address and an optional comment in angle brackets. An example of its use would be LABEL start which would generate an appropriate subtitle, and the following code -- ; ; ************************* ; * * ; * name * ; * * ; ************************* ; name: HEAD This macro generates no code, but makes a nice box heading, with optional subtitle, similar to the PROCED and TASK macros. It is useful to visually mark off sections of programs. For example, you might want to have a box labelled "var" to mark the location of variables in the code. HEAD var, will do just that. Note that the first argument, "var", is only used to generate text in the listing file -- no symbol of that name is defined by the HEAD macro. - 13 - Macros for Structured Programming 5. Fortran-compatible subroutine macros FCALL This macro is used to call routines, passing arguments in a manner compatible with DEC RT-11 and RSX-11 Fortran programs. The macro should be used instead of INVOKE to (a) actually call a Fortran sub-program, whether or not arguments are passed, or (b) to call a Macro sub-program which must adhere to the Fortran calling conventions (e.g. the sub-program gets called from Fortran). Its use is similar to the Fortran CALL statement -- FCALL name, where "name" is the name of the called routine (either an external global name of a Fortran (or Fortran-compatible) subroutine/function or the name of a Macro routine, as specified with the PROCED macro. The arguments, to be Fortran compatible, must be addresses of arguments (since Fortran only uses "call-by-reference" for passing arguments). Thus if you wanted a routine called "add" which adds its arguments together, putting the sum into the last argument, you would set up the call as follows -- FCALL add, ;next instruction goes here ... HEAD var, first: 1 second: 2 third: 3 The effect would be to add 1+2+3 (assuming the routine "add" is coded properly; see below), and putting the result into the variable "third", replacing its value of 3. Note that if we want to use an array as an argument, then we pass, as the argument, the address of the first word of the array. A note on using these routines -- the current Fortran implementations use general register 5 (r5) as a linkage pointer, i.e. a pointer to the argument list. Your routine should not modify this register; it is needed by the other argument macros to access the arguments. Also note that using the FCALL macro will overwrite whatever happened to be in r5 when the macro is invoked. A recent upgrade to FCALL allows a more flexible form of argument passing. Suppose your Macro routine is both called by, and calls, a Fortran routine. Furthermore, suppose the calling Fortran routine passes in a parameter which, in turn, needs to be passed to the called routine. You could use GETARG (see below) to get the appropriate argument (or its address), store it temporarily in the Macro routine, and then build the appropriate FCALL to the subroutine. However, since both the passed in - 14 - Macros for Structured Programming argument and the argument to the called routine are both addresses, there is no real need to require intermediate argument storage in the Macro routine. As an example, you write a macro routine MSUB, which is passed two arguments. All you want to do with each argument is pass them on, one at a time, to a fortran subroutine, FSUB. The old way to do this would be as follows: LABEL msub, GETARG 1, arg1 ;get arguments (see GETARG, below) GETARG 2, arg2 ;note we store address of arguments FCALL fsub, arg1 ;call with first argument FCALL fsub, arg2 ENDPROC arg1: .word 0 ;address of arguments in calling arg2: .word 0 ;routines Now, however, you could simply say LABEL msub, PUSH r5 ;save, will be modified by FCALL FCALL fsub, <2(r5)> ;call with first passed argument POP r5 ;restore r5 FCALL fsub, <4(r5)> ;call with second argument ENDPROC This second method is faster, shorter, and requires no intermediate storage of the arguments in transition. FEXIT This macro provides for a Fortran-compatible return from a subroutine, and hence is equivalent to the Fortran RETURN statement. It exists in three forms -- FEXIT gives the usual Fortran RETURN; in this form, it is identical to the QUIT macro (which forces early exit from a PROCED block), and thus also equivalent to the ENDPROC macro which terminates the PROCED block. If a well-structured routine is being written, one with a single entry at its beginning and a single exit at its end, this macro is unnecessary (see example below). FEXIT n provides the equivalent to the (non-standard) RETURN N statement -- it will return to an address specified by the n-th argument (note -- it is the user's responsibility to ensure that the n-th argument is a legitimate address, including that N arguments are actually present). - 15 - Macros for Structured Programming FEXIT @n gives the equivalent of the RETURN @N statement, which returns to the address specified indirectly by the n-th argument, i.e. the argument gives the address of the return address. Note that these latter two forms are highly non-standard, not supported by DEC Fortran, and should rarely (if ever) be used. GETARG, PUTARG The macro GETARG is used to obtain the values or addresses of arguments specified in the FCALL macro. Its use will be illustrated in an example program below. It can be used to obtain the value of an argument (appropriate if the argument represents a variable) or the address of an argument (since if the argument represents an array, returning the address gives you the first word address of the array). To get the value of the n-th argument, GETARG @n, dest which gets the value of argument n and stores it in the variable "dest". Note that "n" is an actual number, i.e. 1, 2, 3, etc., not a variable name. This is functionally equivalent to mov , dest so that the destination may be specified by any appropriate PDP-11 addressing mode. To get the address of an argument (i.e. an array first word address), GETARG n, dest To illustrate the macro, suppose you want to copy an array from one argument to another; you would use GETARG to get both array first word addresses, would save them in index registers, and then copy using those registers. Such a routine could be coded as follows: PROCED copy, ;copies N words of array A into array B PUSH ;save registers GETARG 1, r0 ;r0 -- pointer to A GETARG 2, r1 ;r1 -- pointer to B GETARG @3, r2 ;r2 -- number of elements FOR r3, #1, r2 ;for each of N words, mov (r0)+, (r1)+ ;copy a word from A to B ENDFOR POP ;restore registers ENDPROC ;return (FEXIT not needed) - 16 - Macros for Structured Programming PUTARG The macro PUTARG is the inverse of GETARG, and serves to return results into the arguments specified in the FCALL macro. As with the GETARG macro, both a "value" and "address" form are available, but in practice, only the "value" form is likely to be employed. To save a value in the n-th argument (assuming that the argument represents a simple variable), PUTARG src, @n where, again, "src" can be any appropriate PDP-11 addressing mode. PUTARG src, n puts the value in "src" into the address specified by the n-th argument. NUMARG This macro determines the number of arguments in the calling routine. This facilitates writing subroutines which have a variable number of arguments. Its use is NUMARG n which puts in the variable "n" the number of arguments in the corresponding FCALL macro. As an example, to code the subroutine "add" which sums all its arguments, returning the sum in the last argument, PROCED add, PUSH ;save registers NUMARG r0 ;get number of arguments clr r1 ;clear sum GETARG 1, r2 ;get address of first arg FOR r3, 1, r0 ;for each argument add @(r2)+, r1 ;add argument value to sum ENDFOR mov r1, @-(r2) ;result in last argument ENDPROC ;and return Note the use of GETARG to get the address (not the value) of the first argument, then just accessing the arguments sequentially (remembering that r2 points to their addresses, so we need to use the "@(r2)+" addressing mode). Finally, the result is stored back through the last address accessed, using the "@-(r2)" mode. - 17 - Macros for Structured Programming INDEXB, INDEXW, INDEX4 If you need to do a lot of computation with Fortran arrays, the INDEX macro can help provide proper conversion of array indices (which start with 1) into address offsets. Note that if you are processing an entire array (as in the example above), an auto-increment address mode is probably more appropriate. Three forms of the macro are provided, one for byte arrays (LOGICAL*1 or character strings), one for word arrays (INTEGER*2), one for real arrays (REAL*4) -- these take into account the variable amount of storage each data type requires. The examples below illustrate the INDEXW form. INDEXW op1, op2 changes the Fortran array index in "op1" into a word offset and stores it in "op2"; INDEXW op1 does the same calculation on "op1", but then replaces this variable with the address offset. The following code will clear the seventh element of the array "buffer" -- INDEXW #7, r0 ;r0 will point to 7th word clr buffer(r0) ;clear 7th word of array "buffer" 6. Utility macros PUSH, PUSHB This macro causes its arguments, however many, to be pushed onto the stack (pointed to by the stack pointer, SP = r6). PUSH pushes arguments 1, 2, ... n onto the stack, in that order, while PUSH pushes and clears the top of the stack (equivalent to PUSH #0). The use of PUSH to save general registers has been illustrated in some of the code segments above. After execution of a PUSH (or POP) macro, the carry flag remains unchanged, allowing its use as an error indicator (e.g. after an RT-11/TSX EMT). - 18 - Macros for Structured Programming POP, POPB This macro is the inverse of PUSH, restoring variables from the stack. Note that if variables are pushed onto the stack in a certain order, they must be POPped in the inverse order. POP takes the top "n" arguments and puts them into arguments n, n-1, ... 1 thus restoring the values saved by a PUSH . POP simply pops the stack, discarding its top element. SWAP, SWAPB These two macros, which take two arguments, interchange the values specified by their arguments, either words (SWAP) or bytes (SWAPB). Thus IF r0, lt, r1 ;if r0 smaller, SWAP r0, r1 ;interchange ENDIF will ensure that the contents of r0 are greater than, or equal to, those in r1. 7. Debugging and output macros NOTE This macro is used to output an arbitrary string on the system console. It can be used to prompt the user for input, provide information, or output debugging messages (such as "You have just entered subroutine Add"). This macro involves operating system-specific calls to handle terminal output. This macro mimics the Pascal procedure "writeln". NOTE causes the message "Hello, world" to be output, while NOTE without a specific string outputs a blank line. - 19 - Macros for Structured Programming PNOTE To write a "partial note", i.e. to output a string without the final carriage return and line feed, use the PNOTE macro. This is equivalent to the Pascal procedure "write". PNOTE NOTE will also output the message "Hello, world". The macro without arguments, PNOTE formally put out a single carriage return; however, for logical consistency, this form of the macro now puts out a "null" partial note, thereby doing nothing. CHECK To check on numeric values, use the CHECK macro, which will name its argument, give its values both in octal and as a signed decimal value, and output an optional string on the terminal. For example, suppose r1 contained the address 1000, and at 1000 was stored the number -5. Then CHECK r1, will cause the following to be displayed on the console -- r1 = 1000 (512.) Contents of register 1 while CHECK (r1), will produce (r1) = 177773 (-5.) Contents of contents This macro has been coded so that it will allow any type of argument involving the stack or general registers. Thus to examine the state of the stack, you could say CHECK sp, CHECK (sp), CHECK 2(sp), CHECK (sp)+, - 20 - Macros for Structured Programming ASSERT In developing complex procedures, it is sometimes difficult to figure out what is happening when things go awry. One method is to use ODT to step through the procedure, checking critical variables to make sure they stay within legal bounds. Another method is to embed such testing code within the procedure, which can test during run time to make sure variables obey specified conditions. The ASSERT macro provides a simple method for implementing this latter method. An example of the use of this macro is ASSERT r0, lt, r1 which will include code to test that r0 is less than r1 whenever the macro is invoked. If the assertion is not met, a message of the form PROOF = 4. r0, lt, r1 -- assertion is false r0 = 10 (8.) r1 = 6 (6.) The first line gives the value of "proof", a (unique) number assigned to each assertion (see below). The assertion is presented, along with the values which caused it to fail. If the assertion is met, no message is output. The test in the ASSERT macro can be any of the forms used above for IF, WHILE, and UNTIL, such as ASSERT (sp), ne ;assert that top of stack not zero or ASSERT cc ;assert that the carry is not set These macros cause the insertion of extra code to handle the assertion tests and inform the user of possible violations. Once the code has been tested demonstrated correct, the extra code can be conveniently eliminated without major editing changes. The actual generation of the assertion code is governed by a user-defined constant named "proof" -- this constant must be defined, and set to a value > 0 in order to cause code generation. Thus just by changing the line PROOF = 1 to PROOF = 0 the code-generation will be turned off. Each time an ASSERT macro is assembled and "proof" is positive, "proof" is incremented, providing a unique identifier for each assertion to aid in debugging. These identifiers appear in the listing next to the ASSERT macro in the form - 21 - Macros for Structured Programming ; ;>>>>>> n <<<<<< This is assertion # n ; ASSERB To make an assertion about a byte, use ASSERB instead of ASSERT. Otherwise, the macros are identical. "Proof" will also be incremented (if positive) when ASSERB is assembled. ENDEND Each time an "END" macro is assembled, a test is made to ensure that it has an appropriate macro (such as IF, WHILE, PROCED, etc.) in front of it. The ENDEND macro provides the opposite function -- it ensures that enough END macros have been included to match all preceding macros. It is typically placed as the last macro in the program, and will cause the generation of an error message if a missing END is detected. 8. "Invisible" macros for numeric output These macros are all "invisible", that is, the user does not need to directly specify them. They provide certain bookkeeping functions for the output routines described in the previous section. The first two macros, TYP.8 and TYP.10, which output numeric values, might be useful in a user program. TYP.8, TYP.10 These macros type a number in octal or signed decimal on the console. They can be called in two fashions -- TYP.8 x types the number in x on the console, while TYP.8 outputs the contents of r0. The contents of the registers are not altered by either of these macros. In particular, they allow "sp" and "(sp)" as arguments. - 22 - Macros for Structured Programming .TYPER This macro does the actual type out of octal or decimal values. When first invoked, it builds an internal subroutine ".tysub" which handles the number-to-character conversion. A.LABL This macro is a part of the assertion macros. Its function is to provide an identifying label in listings showing the number of each assertion. An example listing could include ASSERT r0, lt, r1 ; ;>>>>>> 12 <<<<<< This is assertion # 12 ; 9. "Invisible" macros for program control This group of macros is invoked by many of the block-structuring macros (IF, WHILE, END, etc.) to handle control block and looping structure. You should never need to explicitly invoke them; they are described here for completeness. Internal symbols The macros work by maintaining some internal symbols, which serve as stacks, addresses for loop beginning and end, type of comparison, etc. The symbols all use the "reserved" character "." as the first character, so they should not interfere with user-defined symbols. .1, .2, .3 ... Labels used to construct control blocks, e.g. forms addresses at beginning and end of WHILE-controlled code. .head Unique (pair) of numbers used to generate labels for control blocks. .tail Symbol often used at end of control blocks to construct final branches. .temp Temporary "scratch" symbol. .stk Depth of internal symbol stack. .stk1, .stk2, ... Internal symbol stack, holds various values for blocks, used to build nested block structure. - 23 - Macros for Structured Programming .br Holds branch instruction (e.g. bne, bgt) needed for other macros. .nchrs Used to count characters in output strings. Internal symbol stack macros .PUSH, .POP Pushes or pops argument using the internal stack. The argument can be any value or symbol. ..PUSH, ..POP These macro does the actual stack manipulation for .PUSH and .POP. .TOP This macro gets the top element from the stack, without doing a .POP. The effect is equivalent to a .POP followed by a .PUSH. Label macros Certain macros (such as IF, WHILE, CASE) generate labels, which are a unique pair of symbols of the form ".n" where n is a number. Labels are generated sequentially; .1 and .2 are the first pair of labels. .LABEL This macro generates a label of an appropriate number (its argument) corresponding to the current program counter. .ADDR This macro generates an address corresponding to a label number. In contrast to the .LABEL macro, which defines a symbol equal to the current value of the PC, the .ADDR macro generates a word (actual code) whose value corresponds to the address of the corresponding label number. .GOTO This cause a jump to a label. The argument of the macro gives the label number. - 24 - Macros for Structured Programming .GOBAK This is an optimized backwards-branching form of .GOTO, which will use a branch instruction where possible. Branching macros Several macros require tests, such as "GT", "CC", etc. Branch instructions ;corresponding to such tests (e.g. bgt, bcc) will be stored in the symbol ".br", and will be used in the following macros. ..BR Branch to the address represented by the argument. ...BR Branch to the label whose number is given by the argument. .TEST, .TESTB Generate code for various tests. Arguments represent the number of arguments in the invoking macro, the arguments of the invoking macro, and the numeric value of the false fork's label, i.e. where to go if the test fails. - 25 -