Path: utzoo!utgpu!news-server.csri.toronto.edu!rpi!usc!apple!stadler From: stadler@Apple.COM (Andy Stadler) Newsgroups: comp.sys.apple2 Subject: Re: ML subroutines (passing parameters in ML) (LONG!) Message-ID: <51984@apple.Apple.COM> Date: 25 Apr 91 01:00:12 GMT References: <3397@kluge.fiu.edu> <13845@ucrmath.ucr.edu> <51983@apple.Apple.COM> Organization: Apple Computer Inc., Cupertino, CA Lines: 460 The following is a little gift for you if you are writing reasonably complex programs in the 16-bit world. See my previous posting for more information about -why- you'd want to use these. The macros, as posted, are written for MPW IIGS. They don't really use any fancy macro features and should be easy to port to APW or other assemblers. In addition, there are a few features which could be easily added depending on your needs. Possibilities include support for C-style calls (don't pop the parameters) and JSR calls (currently wired for JSL and RTL). I hope these are useful to you in your programming. Andy Stadler Apple Computer, Inc. ;--------------------------------------------------------------------- ; ; file stackframe.macros ; ; This file contains stack frame code, originally designed by Darryl ; Lovato and further modified by Andy Stadler. This code creates ; "best optimized" entry and exit code for pascal-style calling ; sequences. ; ; Created: 19-Apr-88 ; Modified: See Mod History Below ; Author: Andy Stadler ; ; Copyright (C) Apple Computer, Inc. 1988-1991 ; All Rights Reserved. ;--------------------------------------------------------------------- ; ; Modification History ; ; 19-Apr-88 ADS Original release ; 20-May-88 ADS Modified, now there are only 2 cases ; 23-May-88 ADS Fixed bug in small frame exit case ; 24-May-90 ADS Added "big locals" support - for strings, etc ;--------------------------------------------------------------------- ; ; Pascal defines a stack frame with the following structure: ; ; | | ; |---------------| ; | | ; | func result | "fsize" ; | | ; |---------------| ; | | ; | parameters | "psize" ; | | ; |---------------| ; | | ; | RTL | 3 bytes ; | | ; |---------------| ; | | ; | saved D reg | 2 bytes ; | | ; |---------------| ; | | ; | local vars | "lsize" ; | | ; |---------------| ; D ->| | ; | big locals | "bsize" ; | | ; |---------------| ; SP ->| | ; | | ; ; ; These macros generate code to save the old D register, create ; local variable space (if requested), and point D and the stack ; pointer to the appropriate stack offsets. In addition, equates ; are automatically generated to all variables within the stack ; frame. ; ; Using the macros to generate stack frames is quite simple. The ; structure of every procedure/function is as follows: ; ; ProcName PROC ; InitFrame ; FrameType flags ; ; biglocal1..n BigLocal types ; local1..n Local types ; param1..n Param types ; result Result type ; ; FrameEntry ; ; { user code } ; ; FrameExit ; ENDPROC ; ; NOTE that the data must be declared in that order, and it is exactly ; from bottom to top. In addition, you must list the parameters in ; exactly backwards order from the pascal source (see example below). ; ; The following data types are defined: ; ; Str255 256 bytes ; Rect 8 ; Handle 4 ; Ptr 4 ; LongInt 4 ; Integer 2 ; Char 2 ; Byte 2 ; Boolean 2 ; ; Any other size variable may be defined simply by giving the number ; of bytes to reserve. Note that Char, Byte, and Boolean reserved two ; bytes apiece; this follows the Pascal calling conventions. For local ; storage, single byte variables may be declared with size 1 (but they may ; only be stored to in 8 bit mode). For code efficiency, however, it's ; usually easier to just store them in two byte spaces. ; ; The ExitCode routine includes the final RTL instruction; none is needed. ; ; Only one FrameType flag is currently defined: SaveB will cause the data ; bank register to be saved and restored. This allows the procedure to access ; data other than the standard globals page. If no flags are set, the ; FrameType instruction is not needed. ; ; BigLocals should only be used when you have more than about 250 bytes of ; stack frame (this means everything from the bottom of the locals up to ; the top of the function result space. This will most often be caused by use ; of a pascal string as a local variable. Because BigLocals are "below" the ; true direct page, you can not access them directly. Instead you can use ; one of two techniques. You can calculate the address and work with a ; pointer, or you can take advantage of the dp,X mode, which WRAPS within bank ; zero, and effectively allows use of negative offsets. The equates generated ; for BigLocals are therefore negative numbers. Here are examples of each ; technique: ; ; Pushing a pointer to a BigLocal Using Negative Indexing ; ------------------------------- ----------------------- ; ; pea 0000 ; push hi word ldx #bigloc ; negative # ; tdc ; calc lo word lda 0,x ; clc lda 2,x ; hardcoded ; adc #bigloc ; negative # lda 10,x ; record ; pha lda 20,x ; offsets! ; ; A nice side effect is that the macros restore the stack pointer, no matter ; what its state when ExitCode is reached. This was designed mainly for the ; pascal compiler so it could compile EXIT(proc) easily; It can be used ; similarly from assembly. You don't need to clean up when done because it ; will be fixed for you! ; ;--------- ; ; The following example demonstrates the use of the macros. ; ; FUNCTION MyFunct(address: Ptr; count: Integer) : Handle; ; VAR ; myFlag: Boolean; ; myWord: Integer; ; myRect: Rect; ; myArray: ARRAY[1..10] OF INTEGER; ; myString: Str255; ; ; BEGIN ; myWord := myRect.top; { a few samples to show data access } ; myWord := address^[count]; ; MyFunct := NIL; ; END; ; ; In assembly: ; ; MYFUNCT FUNC ; FUNC and PROC are synonyms ; InitFrame ; FrameType SaveB ; ; myString BigLocal Str255 ; myArray Local 20 ; 10 integers is 20 bytes ; myRect Local Rect ; myWord Local Integer ; myFlag Local Boolean ; 2 bytes ; count Param Integer ; address Param Ptr ; theResult Result Handle ; ; EntryCode ; ; lda 0 THEN Error: BigLocal_declaration_after_a_Local ENDIF IF qPSize <> 0 THEN Error: BigLocal_declaration_after_a_Param ENDIF IF qFSize <> 0 THEN Error: BigLocal_declaration_after_a_Result ENDIF MEND MACRO &lab Local &val &lab equ qLSize+1 qLSize set qLSize+&val IF qPSize <> 0 THEN Error: Local_declaration_after_a_Param ENDIF IF qFSize <> 0 THEN Error: Local_declaration_after_a_Result ENDIF MEND MACRO &lab Param &val ; Stack based parameter &lab equ qLSize+6+qSaveB+qPSize qPSize set qPSize+&val IF qFSize<>0 THEN Error: Param_declaration_after_a_Result ENDIF MEND MACRO &lab Result &val IF qFSize <> 0 THEN Error: Duplicate_Result ENDIF &lab equ qLSize+6+qSaveB+qPSize qFSize set &val MEND ;--------------------------------------------------------------------- ; ; Entry Code Generation Macro ; ; The EntryCode macro generates code to create the stack frame. This ; code saves the D and (optionally the B) register, creates space for ; the local variables, and sets the D register and the Stack Ptr to ; just below this area (so the first local variable is at address 1). ; MACRO EntryCode longa on longi on IF qSaveB = 1 THEN phb ENDIF phd IF qLSize <= 8 THEN Qtemp SET qLSize ; small # locals, PHY to make space WHILE Qtemp > 0 DO phy Qtemp SET Qtemp - 2 ENDWHILE tsc ELSE tsc ; large # locals, calculate new SP sec sbc #qLSize ENDIF tcd ; set the direct page IF qBSize = 0 THEN IF qLSize > 8 THEN tcs ; no bigs, large locals ENDIF ELSE sec ; split off the stack for bigs sbc #qBSize tcs ENDIF MEND ;--------------------------------------------------------------------- ; ; Exit Code Generation Macro ; ; The following macro generates the appropriate exit code for a ; subprogram. The stack pointer, D reg, and optionally B reg are ; restored, and an RTL is executed. ; MACRO &lab ExitCode &lab longa on longi on IF qPSize <> 0 THEN ; move RTL & B up over parms lda 2 GOTO .LARGEFRAME ; This frame has a small number of locals, zero or two bytes worth. ; We can pull the D reg right off the stack, and then either pull or ; calculate to remove the call parameters. .SMALLFRAME tdc tcs Qtemp SET qLSize ; use ply's to kill locals WHILE Qtemp > 0 DO ply Qtemp SET Qtemp - 2 ENDWHILE pld ; recover direct page register IF qPSize <= 8 THEN Qtemp SET qPSize ; small # parms, use ply's WHILE Qtemp > 0 DO ply Qtemp SET Qtemp - 2 ENDWHILE ELSE ; large # parms, use clc adc clc adc #2+qLSize+qPSize tcs ENDIF GOTO .DONE ; This frame has a large amount of stack data. We use a load to get to the ; saved D register, and dump the stack data by calculating a new stack pointer. .LARGEFRAME ldx <1+qLSize tdc clc adc #2+qLSize+qPSize tcs txa tcd .DONE IF qSaveB = 1 THEN plb ENDIF rtl MEND