Avoiding traps
Volume Number: 2
Issue Number: 10
Column Tag: Advanced Macing
Reduce Your Time in the Traps!
By Mike Morton, Senior Software Engineer, Lotus Development Corp.,
Cambridge, MA
Life in the fast lane
The Macintosh ROM subroutines are called with “trap” instructions, intercepted
by dispatching software which interprets the trap and calls the routine. This method is
very general, providing compatibility with future ROMs and allowing buggy routines to
be replaced.
It's also slow, taking about 45 microseconds for the dispatch process. This
article tells you a way to avoid the dispatcher without losing its generality. Since the
timing differences are measured in microseconds, there's also a discussion of
techniques for measuring the time consumed by a piece of code. Also, a program is
included to show the alternate way to call the ROM and how to measure the times used
by different methods.
Avoiding traps
When a program executes a trap instruction, the 68000 detects the “error” and
transfers control to the trap dispatcher pointed to by the longword at $0028. The
dispatching software must, among other things:
• preserve some registers on the stack
• fetch the trap instruction from the code
• decide if the trap is a Toolbox or OS call
• look up the trap number to find whether the routine is in RAM or ROM, and what
its address is
• handle the “auto-pop” and “pass A0” bits
• call the routine
• restore registers from the stack
Most of this work can be avoided if you know the routine's address and call it
directly, but this is a bad idea for two reasons. First, the address may change in future
ROMs. Second, Apple distributes “patches” to ROM routines by changing the dispatch
table to call new versions in RAM -- if your program “knows” the address, it'll call
the old, buggy ROM routines, ignoring the new RAM-based ones.
There is a balance between hardwiring the address and using the trap dispatcher
for every call. The Toolbox “GetTrapAddress” function decodes a trap instruction for
you and returns the address of the routine, just as the dispatcher does. You can do this
decoding just once in your program, save the address, and repeatedly call it later.
The main reason not to bypass the dispatcher is that it saves a few registers
across each call. If you're working in assembler, this is no problem -- just save
registers yourself, as needed. In most high-level languages, it also won't be a problem,
since the registers lost are typically scratch registers: D1, D2, and A2.
Fig. 1 Our TrapTime Utility shows the difference!
A high-level example
First, let's look at the normal way of calling a Toolbox routine: the simple
“SetPt” procedure, which sets the coordinates of a Quickdraw “point”. The following
example and the timing program are in TML Pascal; they should be easy to convert to
other languages.
Most programs include the Quickdraw unit, which declares “setPt” with
procedure SetPt(VAR pt: point; h, v: integer); INLINE $A880;
When you call the routine with the statement
setPt (myPt, x, y); { set the point }
it pushes the parameters on the stack and executes the instruction $A880 to trap to
the dispatcher, which calls the routine. If you want to skip the cost of repeatedly
decoding the trap, you can do it once like this:
var setPtAddr:longint; { addr of setPt }

setPtAddr := getTrapAddress ($A880);
To call this address, declare a new routine like SetPt, but which produces
different in-line 68000 code:
procedure mySetPt
(VAR pt: point; h, v: integer;
addr: longint);
INLINE $205F, $4E90;
Note the extra parameter to this routine: the address of the routine to be called.
The instructions given in hex after the “INLINE” do a JSR to that address. The result is
nearly the same as executing a trap, but faster.
Calling with this interface is almost like a normal call; pass the address as a
parameter:
mySetPt (myPt, x, y, setPtAddr);
This can be used for most Toolbox calls - just declare your own routine (choose
any name) with the same parameters plus the address parameter, and include the exact
same “INLINE” code after it. Don't forget to initialize the address with GetTrapAddress
before calling, or awful things will happen.
Other high-level languages
You should be able to use this method with almost any language which allows you
to insert assembler code in your high-level program. Some languages may have
trouble calling the ROM directly -- for instance, many C compilers pass parameters
differently than ROM routines do. Some C compilers allow you to choose the method of
parameter passing; this will allow you to dispense with assembler altogether and just
call the routine through a pointer (ask your nearest C guru how to do this).
More straightforward approaches
This approach assumes that “SetPt” is too slow. If you actually need Toolbox
operations to be faster, consider writing the code yourself. You can write a procedure