FORTRAN Glue
Volume Number: 6
Issue Number: 6
Column Tag: Assembly Lab
Some Glue for FORTRAN 
By Frederick V. Hebard, Meadowview, VA
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
[Fred Hebard is superintendent of the American Chestnut Foundation’s Re search
Farm in Meadowview, VA. He has a PhD in Plant Pathology. Donations for the
Foundation may be directed to West Virginia University in Morgantown.]
Some Glue for FORTRAN
The last time I sat down to finish up an article for MacTutor it was raining. I had
just started a one or two month hiatus between jobs, and that article was to be my first
project. It rained that night too, so it was not until the next morning that I unpacked
all my stuff from work. That evening I put it all back in the truck, and we evacuated
the house in the face of the Kentucky River Flood of 1989. So much for that vacation!,
not to mention the article. Well, it is December now, and I have completed another
project that may be of interest.
When I first needed to migrate from mainframes to micros, I chose the MC68000
as my CPU because it had a linear 24-bit address space --no 32 or 64 k segments.
The Mac had the most bang for the buck amongst 68000 machines, and AbSoft’s
MacFortran offered full access to the 68000 --no 32 k segments. The Mac also had
spectacular graphics, and a bit-mapped display. It was the slickest computer I had
ever seen. Fortran has been derided quite a bit --no Fortran compiler has ever even
been mini-reviewed in MacUser,-- but the main things MacFortran lacks in
comparison to Pascal or C are complex data types and inline code. Besides, I had a fair
amount of code built up from my mainframe and mini days.
I eventually acquired the MDS Assembler and set out to learn 68000 Assembly,
mostly because all the wizards seem to know it. When HyperCard came along, I put the
MDS package to work since MacFortran could not generate XFCNs, and I am too cheap
(or is it broke?) to buy another language.
My cheapness (stubborness?) has led me to write an interface between
Hypercard and MacFortran. Yeah, I know I can buy one for $70, but for that amount of
money I could almost get Think C or Pascal[Not any more-ed]. Besides, you are
definitely closer to the machine in an assembler --it is more fun. Or at least there
are fewer pitfalls in Assembly than in higher level languages, even if the bugs are ten
times harder to spot in that blizzard of code.
I thought this project would be of interest to XFCN affectionados because it
illustrates error trapping in an XFCN. I have not seen that covered here previously.
Neophyte assemblers might enjoy it since I am close to that level. The wizards can look
at my code and smile. Finally, it is an example of a glue routine. I have not seen any of
those in MacTutor.
Error Trapping in XFCNs
An error handle. An XFCN returns its result to HyperCard in a relocatable block
pointed to by a handle. The block contains a string of ascii characters terminated by a
null (#0). Since XFCNs can have no arguments, we first need to create a result for
HyperCard before we check the arguments HyperCard has passed to us. Then, if the
arguments are inappropriate for our XFCN, we can return to HyperCard without
crashing it. HyperCard will crash if an XFCN returns with no result.
Thus, after setting up a stack frame for local variables, saving all the registers,
and doing another manipulation I will discuss below, we allocate a handle for a
HyperCard result. We make the handle 2 bytes long, stuff a null in its first byte, and
put it in the returnValue slot of the XCmdBlock. It would be nice of Apple to have
HyperCard stuff a valid handle in the returnResult of the XCmdBlock before calling an
XFCN.
Note that we do not attempt to find out whether allocation of the handle was
successful. There is nothing we could do about it if it were unsuccessful since we do
not have a result to pass back to HyperCard. If we do get an error here, it is likely that
we will not be able to perform any of the other memory allocations which occur in this
XFCN. Those errors will be trapped, and we will attempt to inform the user of them.
The other alternative is to reboot the machine, and that is very rude to your users!
So, gritting our teeth just a bit because we are no longer bulletproof, we are
ready to read our args from the XCmdBlock.
Error handling. Our XFCN expects at least three arguments. If there are fewer
than three, we exit by branching to the error-handling routine “badNumArgs.” That
routine simply loads an error message for the user and branches to the routine “Err,”
which is called by all the error-handling routines. “Err” prepares a HyperCard call
back to put up the “Answer” dialog box. The box will contain our error message.
“Err” calls the subroutine “callHyper,” which actually jumps to HyperCard.
HyperCard returns below our BSR in “Err.” “Err” then branches to the main return
point at “dondon” where we restore the registers, unlink, and return to HyperCard. I
cribbed most of this code from Andy Hertzfeld’s source code for his importPict XCMD.
Somehow, Andy’s code seems to be just a _bit_ more elegant than mine!
I would like to mention the use of the MDS Assembler’s _StringFormat 2
directive in the set of error strings. Our strings need to be Pascal String255s, with a
leading length byte (as well as HyperCard strings with a trailing null). It sure is
easier to let the assembler count the number of characters in your string than to do it
by hand. We also use the .Align 2 directive to ensure that all strings start on a word
boundary. Note that we have to restore _String Format to 0 when we are done because
AbSoft’s code expects that.
Fortran Glue
Lock the arguments. Assuming we have not taken the branch to “badNumArgs,”
we can replace the error handle in the returnResult of the XCmdBlock with one of the
arguments passed from HyperCard, since we know the argument exists. MacFortran
passes arguments between program units by pushing pointers to the arguments on the
stack, whereas HyperCard passes handles to its arguments. Since MacFortran can
make Memory Manager calls, we need to lock the handles before dereferencing them
and passing them to MacFortran. Note we call MoveHHi before locking the handles. We
do not lock the first three handles since they will not be passed along to MacFortran.
Runtime libraries. MacFortran implements almost all of its functions and
utilities via calls to a series of routines contained in a runtime library. Floating point
operations are not done via SANE calls; rather, MacFortran has its own collection of
floating point routines, which are contained in the runtime library. The entire
library is needed to implement any MacFotran routine, no matter how simple. Thus
the first thing a MacFortran program does is to allocate space on the heap for the
runtime library. It then jumps into the runtime library itself where most of the
initialization code resides. There actually are two (three?) runtime libraries,
depending on which compiler one has purchased. One library, “f77.rl,” implements
all floating point operations in software, whereas another, “m81.rl,” can use the
68881 and 68882 math coprocessors.
Load the arguments. The first argument passed to Letsgo should be integer 1, 2
or 3, indicating which of three MacFortran runtime libraries the user possesses. If
the integer is negative, we disable MacFortran’s default console window. If the first
argument is not 1, 2 or 3, we branch to an error-handling routine, “badRuntimeArg.”
This routine is similar to the “badNumArgs” routine except that it calls a subroutine
to unlock the argument handles passed from HyperCard before loading its string and
branching to “Err.” Later, when we allocate a handle for the real function result, we
will have to unlock it also before returning to HyperCard. Note that we did not try to
unlock the argument handles if we encountered an error while MoveHHi-ing them and
locking them. An error there is an indication of serious problems in the system which
are beyond our control, so we tell the user to reboot!
The second argument indicates how much heap space to allocate for the function
result, and the third argument is the name of the MacFortran function subprogram the
user wishes to call. Any additional arguments will be passed to the Fortran function
subprogram.
Let’s not init the Window Manager in the middle of HyperCard! In the previous
section, we discussed disabling Fortran’s default console window and selecting the
appropriate runtime library. AbSoft very kindly provides a file, Init.asm, which is to
be included in assembled Fortran main programs. That file contains comments
indicating how to disable the console window, select the runtime library and disable
initialization of the Mac Toolbox managers, such as the Window and Menu Managers.
AbSoft requests that Init.asm not be modified in code distributed to others, but we have
to change the first instruction in Init.asm to disable initialization of the Mac Toolbox
Managers. That is the reason for the fourth and fifth instructions in this XFCN: we
modify Init.asm in RAM after it has been loaded. Note that on CPUs with instruction
caches, you have to flush the cache if you try to modify a nearby instruction. Thus we
modify from a distance.
Four things. After we allocate a handle to hold the result of this XFCN, there are
four things left to do before we can call our Fortran psuedo-main program, .WILLGO,
at its entry point .START. Init.asm is the first part of .WILLGO. Init.asm will allocate
a non-relocatable block to hold the runtime library, load the library from disk, then
jump into its initialization routine (AbSoft still refers to the runtime library’s
non-relocatable block as a “heap” in their documentation, although it no longer is one
with version 2.4). We have to deallocate the storage for the Fortran “heap” before
returning to HyperCard, since MacFortran does not. After initialization, the runtime
library returns to .WILLGO with the Fortran “heap” pointer in register A4. We store
the pointer on the stack frame in .WILLGO so we can deallocate the heap when Fortran
returns to the main section of our XFCN, Letsgo. In Letsgo, we clear the stack frame
storage for the “heap” pointer before the JSR to .START so we will know whether we
ever got to .WILLGO.
We will fail to get to .WILLGO if Init.asm can not allocate the Fortran “heap” or
can not find the runtime library on disk. In those cases, Init.asm will return directly
to us. If Init.asm can allocate the “heap”, it will put a pointer to the end of the “heap”
in register D4. Register D4 will not be altered further by Init.asm. Thus Letsgo will
be able to distinguish the two error conditions, no heap and no runtime, depending on
the contents of register D4 and FortranHeapPtr(A6).
We still have not examined the name of the Fortran function the user wants to
call. That is best done in .WILLGO . Thus we need some indication that the user has
passed us a valid subroutine name. That indication will be on the stack frame in
“errorFlags.” We will fill “errorFlags” in .WILLGO if the function name is invalid,
so we clear it here.
Finally, MacFortran mucks with A6, so we have to push its contents onto the
stack. Then we will be able to access the stack frame of our main program in .WILLGO.
We also will be able to “unlink” easily on return to our main program.
.WILLGO. .Willgo itself does three things: it changes the name of the function the
user wants to call into Fortran readable form; it pushes the function result and
arguments on the stack; and it calls the function.
MacFortran expects its 6-byte function names to be packed by the radix 50
method into a 4-byte integer. The function names can have no leading blanks and have
to be six characters long. Trailing blanks can contribute to that six character length.
So we convert the name the user has passed us to the six-byte format and call the
Rad50 function Absoft supplies with their compiler. The error-handling routine in
Rad50 is modified to set the errorFlag on Letsgo’s stackframe. Then we can use our
error-handling code to notify the user if he passes us bad characters in the function
name.
MacFortran expects a pointer to the function result on top of the stack, followed
by the last argument in the call list, down to the first. Then the two-byte length of the
first argument should have been pushed, followed by the length of the second argument,
on down to the length of the last argument. Finally, the length of the function result
should have been the first thing pushed on the stack. We do that, load data registers
with the Rad50 name of the function and the number of arguments and off we go!
A heap of trouble. Well, I got to this point and thought I was done, but nooo...
I was playing around in HyperCard and I noticed that the paint routines were
unavailable occasionally. Uhoh. Letsgo was the culprit. So I systematically dumped
the heap after successive calls to Letsgo to see what the problem was. It was gook!
Well, more precisely, Fortran was allocating non-relocatable blocks on the heap and
not trashing them. That knowledge, by the way, enabled me to shorten the heap dumps
to only non-relocatable blocks. Now, how do I figure out what Fortran is doing without
violating my license agreement not to disassemble MacFortran’s runtime library?
Before we get to that, I would like to make an editorial comment. Both Apple and
AbSoft have licensing agreements saying you are not to disassemble their private code.
I object strongly to that. I can see vaguely how they can prohibit reverse engineering,
to clone the Mac ROMs for instance, but to look at their code? That is equivalent to
selling somebody a book and telling them they can not read it. Frankly, I think this
whole idea the I do not own the copies of software I have purchased is pure baloney!
Sure it is copyrighted, but I still own the book. And that includes my private property
right to read my book. Is not this part of the idea of personal computers? They are
yours. Do not you have the right (obligation?) to figure out how they work?
So anyway, I started halting execution of the Fortran function .WILLGO called, and
looking at the heap. My Fortran function would be in a non-relocatable block on the
heap, and various other blocks would be added depending on what the function did.
Furthermore, the block containing my Fortran function had some extra data in the
first 14 bytes before the code. And that data changed when other blocks were allocated.
In fact, the first long word in the block holding my function pointed to the first
additional block which was allocated and was zero otherwise. The second long word
pointed back into the runtime library. The location in the runtime library pointed to
my function. Likewise, the first long word in any additional blocks pointed to the next
block in the chain or was zero if there was none, and the second long word pointed back
to the previous block. Aha, a linked list! And that location in the runtime library was
the start of the list. It was 8 bytes above the location pointed to by register A0, which
Absoft refers to as the communications block pointer. So degooking the heap is a
simple matter of traversing the linked list, calling DisposPtr as we go.
After we do this, we merely have to check that .WILLGO liked the function name
the user gave us and unlock HyperCard’s handles before returning to HyperCard.
What’s missing? This glue routine lacks three features. First, it does not
support callbacks to HyperCard from Fortran. That could be added relatively easily.
Second, MacFortran initializes the cursor on startup. I thought it best if the user
handled restoring the cursor. We could patch the runtime library, but that would
necessitate violating the license agreement. Or we could patch _InitCursor so it does
nothing, then unpatch it after startup; that is a pretty heavy-handed approach for a
minor problem! The third feature lacking in our glue routine is that it does not
support static linking of the runtime library or the user’s function subprogram.