Segments
Volume Number: 4
Issue Number: 11
Column Tag: Forth Forum
Code Segments & Linker
By Jörg Langowski, MacTutor Editorial Board
Code segments and a Mach 2 linker
This month I’d like to report on some recent Mach2 improvements: the new 2.14
version and a linker for kernel-independent applications that has recently appeared on
the GEnie Mach2 roundtable. Since many of you don’t have access to that BBS, I’ll
document the linker here, with some review of code segment structure, and put the
code on the source code disk.
Single-segment linker
We have seen several examples - DAs, XCMDs, MDEFs - of Mach2 code that runs
independent of the Forth multitasking kernel. Writing such code requires that the
programmer write the setup code that is usually provided by the Mach2 system. For
the examples that I gave in my column, the standard glue code for making a routine
callable from outside Mach2 looked similar to:
CODE prelude
LINK A6,#-Nstack
\ Nstack bytes of local Forth stack
MOVEM.L A0-A5/D0-D7,-(A7) \ save registers
MOVE.L A6,A3 \ setup local loop return stack
SUBA.L #Nlocal,A3 \ in the low Nlocal stack bytes
MOVE.L 8(A6),D0 \ pointer to parameter block
MOVE.L D0,-(A6)
RTS \ just to indicate the MACHro stops here
END-CODE MACH
CODE epilogue
MOVEM.L (A7)+,A0-A5/D0-D7 \ restore registers
UNLK A6
MOVE.L (A7)+,A0 \ return address
ADD.W #4,A7 \ pop off 4 bytes of parameters
JMP (A0)
RTS
END-CODE MACH
: my-forth-code
( code to be called externally )
;
: ext.routine
prelude my-forth-code epilogue
;
After these definitions, the routine ext.routine may be called from the outside as
if defined in Pascal as:
procedure ext.routine (parameter:longint);
begin
( code to be called externally )
end;
All we need in this case before calling our Forth code is to set up a local Forth
stack maintained by the A6 register, save all the registers for safety, and create a loop
return stack maintained by A3.
In principle, a complete application can be created this way; however, some more
setup is required. A simple one-segment application consists of two CODE resources:
the jump table in CODE 0 and the actual code in CODE 1. The structure of the jump
table (JT) as given in IM II-60 looks like the following (in the case that the first
entry in the JT corresponds to a routine in segment 1):
0: longint Above A5 size ( 32 + length of JT )
4: longint Below A5 size (appl. and QD globals)
8: longint Length of jump table in bytes
12: longint Offset from A5 to jump table (32 )
16: Jump table:
------ Jump table entry #1 ------
word offset of routine #1 from beginning of
segment
longint MOVE.W #1,-(A7)
( push segment # of routine on stack)
word _LoadSeg
------ following jump table entries ----
------ for routine #2...n, if necessary ------
When an application is launched, the CODE 0 resource will be loaded and the first
JT entry executed. This will load the appropriate segment into memory and jump to the
routine to which the first entry is pointing. Thus, a simple one-segment application
would consist of a CODE 0 resource like above, with one single JT entry:
$nnnn ( entry address in CODE 1 segment )
( attention: segment starts at )
( beginning of resource + 4)
$3F3C0001 ( MOVE.W #1,-(A7) )
$A9F0 ( _LoadSeg )
CODE 1 would contain the actual code, written in Mach2.
What are the advantages of creating single-segment Mach2 programs? First of
all, we can create very small applications. The smallest conceivable application, which
does absolutely nothing but return, would comprise only 30 bytes:
CODE 0:
$00000028 ( always $20 + length(JT) )
$00000200 ( arbitrary )
$00000008 ( length of JT; one entry )
$00000020 ( always )
$0000 ( entry address in CODE 1 segment )
$3F3C0001 ( MOVE.W #1,-(A7) )
$A9F0 ( _LoadSeg )
CODE 1:
$0000 ( first routine is at beginning of JT )
$0001 ( one entry in this segment )
$4E75 ( RTS )
This program is enclosed on the source code disk as a curiosity. The file actually
is 364 bytes long (Resource map etc.), which is still pretty small.
A second advantage is that we have complete control over the way the application
sets itself up. In particular, we could pass a routine pointer to _InitDialogs to activate
the Resume button of the system bomb box, or we might want to control the amount of
calls to _MoreMasters.
The disadvantage of compiling applications under Mach2: Obviously we have to
care about all the things that the kernel normally does for us, like basic event
handling, menu and menu bar setup, screen input/output, etc. Particularly, there are
quite a few Forth words that may not be used anymore; regular readers of this column
should be familiar with the rules for creating ‘kernel-independent code’ that I’ve laid
out a few times already.
The Forth words that can be used include:
! “ + +! ^ +> - -> 0< 0= 0> 1+ 1- 2* 2+ 2- 2/ 2DROP
2DUP 2OVER < <> = > >BODY >R ?DUP @ ABS AND ASCII C! C@
DROP DUP EXIT I I’ J LEAVE L_EXT NEGATE NOT OR OVER PAD
PICK R> R@ SWAP U< W! W@ XOR { (it’s OK to use local
variables)
The following control and branching structures may also be used:
IF ELSE THEN BEGIN WHILE REPEAT UNTIL AGAIN CASE ENDCASE OF
ENDOF DO LOOP +LOOP
Assembler, of course, may be used freely.
Waymen Askey, of Palo Alto Shipping, has created a ‘linker’ utility that compiles
single-segment application using the strategy given above. We reprint his program in
listing 1 with his permission. This linker operates on a program which has the
following structure:
PROGRAM programname;
( definitions not to be included in the final application
such as constants, compiling words, etc. )
VAR
( global variable declarations which will be offset from A5 )
PROCEDURES
( Forth words called by the top level word )
MAIN
( top level word which is called on startup. )
( This word should call the setup procedures )
( MachSetUp and MacintoshSetUp )
( which are provided with the Linker utility. )
END
The linker computes the ‘below A5’ size from the variables defined after the VAR
statement, adding space for the Forth stacks, Quickdraw globals and various other
things. The offset of the MAIN entry point into the code segment is calculated and the
jump table set up. MakeJumpTable and MakeMain are the words that create the jump
table and code segment 1.
MachSetUp initializes the registers for Mach2 usage. Floating point (D7),
parameter (A6) and return (A3) stacks are created above the current stack base in
the application globals area. The A7 stack, starting at CurStackBase, remains
unaffected. The application globals area is then cleared.
MacintoshSetUp does the standard initialization calls to _MoreMasters,
_InitGraf, _InitFonts, _InitWindows, _InitMenus, _TEInit, _InitDialogs,
_FlushEvents and _InitCursor.
After these initialization calls, the main program may be entered. An example of
a short program which creates a window and beeps is given in the listing. This
program, too, is only 858 bytes long (!!!).
Mach 2.14 upgrade
For those of you who haven’t yet upgraded to Mach2.14, I’ll briefly review the
latest changes.
1. CASE optimization: redundant instruction sequences of the type
MOVE.L D0,-(A6)
MOVE.L (A6)+,D0
are no longer generated.
2. Local variable handling: the new release offers access to the local variable
compiler with the words LALLOT and LP@. For example, a word might define a
local 16-byte buffer in the following way:
: EXAMPLE { | [ 12 LALLOT ] myBuffer -- }
CR .” Please enter your name “
^ myBuffer 16 EXPECT
CR .” Hello “ ^ myBuffer SPAN @ TYPE ;
The local variable compiler can be further enhanced through ‘local variable
compiling words’; examples on how to do this are given on the 2.14 release disk.
3. Disassembler: References to USER and global variables are now given with their
Forth names. Disassembly speed has been greatly improved, which is
particularly evident when executing IL on a Mac Plus or SE. 68881 opcodes are
now supported, however, 68020-specific instructions not yet.
4. New words: ASCII now takes up to four characters, for easy definition of resource
types. 4+, 4-, 4*, 4/ have been added. a n SHIFT will shift a 32-bit word a by n
bits.
5. The trap list has been updated.
Feedback dept.
“Dear Jörg,
I saw a discussion of accented character problems in the July MacTutor and
thought I would throw in a few digressions on that matter.
First, you and your readers might be interested to know that Apple has removed
the scaron and zcaron characters from the new NTX PROMs against the recommendation
of Adobe. These characters were not accessible from the keyboard, because they are
uncoded, meaning that no ASCII value is assigned. The only way to access them is via
Postscript character names. The good news is that several new characters were added
making the NTX almost compliant with the ISO 8859 character set that Adobe routinely
supplies with all new fonts. (Apple removed the ´y and ´Y, too).
For those of you who want to see the unencoded characters, you can get at them
with the following Postscript code and a download utility, if you have one of the new
unprotected Adobe fonts or a late model Laserwriter Plus with v.3 PROMs:
/Garamond-Light findfont dup length dict
/newdict exch def
{1 index /FID
ne{ newdict 3 1 roll put }{ pop pop }ifelse
} forall
/Encoding 256 array def
Encoding 0 /Garamond-Light findfont
/Encoding get 0 256 getinterval putinterval
Encoding 127 /DEL put
Encoding 129 /lslash put
Encoding 130 /Lslash put
Encoding 131 /eth put
Encoding 132 /Eth put
Encoding 133 /thorn put
Encoding 134 /Thorn put
Encoding 135 /onehalf put
Encoding 136 /onequarter put
Encoding 137 /threequarters put