; +-------------------+ ; | | ; | 68K-Macros | ; | | ; +-------------------+ ; ; 68K-Macros - macros to improve on the 68000's instruction mnemonics. ; ; %W%\t%G% - SCCS Info ; ; These macros are mostly intended to augment the 68000 instruction set. ; Some, like "compare", simply reverse the arguments to make it easier to ; understand. Others facilitate creation of structured code. ; Macros to make the "cmp" instruction logical by un-reversing its arguments. ; macro compare arg1,arg2 = cmp.w {arg2},{arg1} | macro compare_l arg1,arg2 = cmp.l {arg2},{arg1} | macro compare_b arg1,arg2 = cmp.b {arg2},{arg1} | ; Lsl and Lsr by 16 bits, a common operation. ; macro lsl_16 arg1 = swap {arg1} clr {arg1} | macro lsr_16 arg1 = clr {arg1} swap {arg1} | ; Save/restore all registers, another common operation ; macro save_all = movem.l d0-d7/a0-a4,-(sp) | macro restore_all = movem.l (sp)+,d0-d7/a0-a4 | ; Useful constants true equ $ffff false equ 0 ; Set and test variables declared by dc.w directives. ; macro set_var var,value = lea {var},a0 move {value},(a0) | macro cmp_var var,value = move {var},d0 cmp {value},d0 | ; Macro for debugging; inserts an instruction into the code which stores a unique ID ; at memory location zero. If you put a debug macro at the beginning of each subroutine, ; it makes it easy to tell what subroutine the program was in when a bomb occurs, and also ; makes it easy to find the start of each subroutine (if for some reason the labels are not ; available in the debugger.) ; The program must declare a constant "debug_flag"; if it is zero, the debugging ; instructions will not be assembled into the program. ; macro debug number = IF debug_flag<>0 move #{number},$0 bug_number set {number} ENDIF | ; Macro for calling a subroutine. This macro restores the current debugging number in ; memory location zero after the JSR, because the subroutine (probably) stored its own ; debugging number in $0. ; Like the debug macro, this macro does not generate the debugging instruction if ; debug_flag is zero. ; macro call routine_name = jsr {routine_name} IF debug_flag<>0 move #bug_number,$0 ENDIF | ; Macros to make loop structures easier to implement, by simulating the "LOOP..WHILE" and ; "LOOP..UNTIL" constructs common in high-level languages. ; ; loop: Put addr of next instruction on stack macro loop = jsr *+4 | ; while: Loop back on condition, throw away the address on stack otherwise macro while cond = b{cond} (sp) lea 4(sp),sp | ; until: Throw away address on condition, loop back otherwise macro until cond = b{cond}.w *+8 bra.w (sp) lea 4(sp),sp | ; =========================== Stack-based subroutine macros ============================== ; ; These macros make it easier to implement stack-based subroutines and functions, using ; Apple's standard stack-based calling sequence. They allow the programmer to declare ; parameters and local variables (which are allocated on the stack) and to refer to them ; by name rather than by -14(A6) or whatever. They also allow the programmer to ; refer to the return value (if any) with a macro. These macros use A6 as the parameter ; frame pointer, and restore its value when the subroutine ends. ; ; First, use the stack_start macro as the first instruction in the subroutine. When the ; subroutine is called, the stack will be like this: ; ; SP ; | ; ----+-----------+-----+-----------+-----------+--- ;higher| return |word | long | return | Stack grows ;memory| value |param| param | address | this way --> ; ----+-----------+-----+-----------+-----------+--- ; ; The stack_start macro saves A6 on the stack, then makes A6 point to the bottom of the ; parameter space. After stack_start, the stack looks like this: ; ; A6 SP ; | | ; ----+-----------+-----+-----------+-----------+-----------+--- ;higher| return |word | long | return | old | Stack grows ;memory| value |param| param | address | A6 | this way --> ; ----+-----------+-----+-----------+-----------+-----------+--- ; ; Note that this is *not* equivalent to the LINK instruction, which is normally used to ; start a stack-based subroutine. The reason LINK is not used here is that it requires ; knowing the size of the local space. This would require putting a macro to initialize ; the assembler variable used to keep track of this size and the declarations of the ; local variables before the stack_start macro. Since that is more complicated for the ; programmer, I decided to do things this way. ; macro stack_start = move.l a6,-(sp) lea 8(sp),a6 stack_size set 0 last_local set -8 | ; After the stack_start macro, use the macros byte_param, word_param, and long_param to ; declare the parameters. These macros don't generate any code; they just set some ; assembler variables so that the other macros will know what to do. ; macro long_param name = stack_size set stack_size + 4 parm_{name} set stack_size | macro word_param name = stack_size set stack_size + 2 parm_{name} set stack_size | macro byte_param name = word_param {name} | ; Next, use the macros byte_local, word_local, long_local, and array_local to declare the ; local variables. These macros generate code to move the stack pointer down by the ; appropriate amount for each local variable. After the locals are declared, the stack ; will look something like this: ; A6 SP ; | | ; ----+-----------+-----+-----------+-----------+-----------+-----+-----+--- ;higher| return |word | long | return | old |byte |word | Stack grows ;memory| value |param| param | address | A6 |local|local| this way --> ; ----+-----------+-----+-----------+-----------+-----------+-----+-----+--- ; macro long_local name = last_local set last_local - 4 locl_{name} set last_local subq.l #4,sp | macro word_local name = last_local set last_local - 2 locl_{name} set last_local subq.l #2,sp | macro byte_local name = word_local {name} | ; NOTE: When declaring an array local, the number of bytes specified MUST be an even ; number. If it isn't, the Mac will reboot (really!) as soon as the subroutine tries to ; use the stack or call another subroutine. (Trying to push or pop something on the ; stack when the SP is odd causes a "double bus fault", which causes the system to ; reboot.) ; macro array_local name,size = last_local set last_local - {size} locl_{name} set last_local lea -{size}(sp),sp | ; After the parameters and locals are declared, start the actual code of the subroutine. ; Use these macros to refer to parameters, local variables, and the return value. They ; may be used anywhere the disp(An) addressing mode is allowed. ; macro param name = stack_size-parm_{name}(a6) | macro local name = locl_{name}(a6) | macro return_v = stack_size(a6) | ; At the end of the subroutine, use the stack_rts macro in place of an RTS instruction. ; Note that the subroutine might have left some extra garbage on the stack. Therefore, ; the stack will look something like this: ; ; stack_size(A6) A6 -8(A6) SP ; | | | | ; ----+-----------+-----+-----------+-----------+-----------+-----------+-----------+--- ;higher| return |word | long | return | old |byte |word | extra | ;memory| value |param| param | address | A6 |local|local| garbage | ; ----+-----------+-----+-----------+-----------+-----------+-----------+-----------+--- ; ; stack_rts works in five steps: First, it discards the garbage and the local variables ; by setting the SP to the location of the old A6. Second, it pops the old A6 off the ; stack to restore A6. Third, it copies the return address onto the top four bytes of the ; parameter space. Then, it points the SP at this address. Finally, it does an RTS, ; leaving the stack empty except for the return value. (The reason RTS is used instead of ; JMP (A0) or something like that is to preserve A0.) ; macro stack_rts = lea -8(a6),sp move.l (sp)+,a6 move.l (sp),stack_size(sp) lea stack_size(sp),sp rts | ; end of 68K-Macros