home *** CD-ROM | disk | FTP | other *** search
/ Crawly Crypt Collection 1 / crawlyvol1.bin / program / books / 68k_book / arp_doc / chap_04.doc < prev    next >
Text File  |  1985-11-20  |  116KB  |  2,440 lines

  1.    Atari ST Machine Specific Programming In Assembly
  2.         
  3. Chapter 4: Initialization Algorithms, Part 1
  4.      
  5.      
  6.      The prime directive of a computer system is to function 
  7. as an algorithm executing environment.  We can consider the 
  8. unblemished environment, in which there is no operating 
  9. system, no stored data and no stored applications, to be the 
  10. fundamental environment.  When we add an operating system to 
  11. the fundamental environment, we inject a director of 
  12. operations which dictates to the unblemished environment the 
  13. manner in which it will fulfill its prime directive.
  14.      Initially, the operating system is established as an 
  15. unblemished subenvironment within the fundamental 
  16. environment.  When we cause an algorithm to be executed 
  17. under the direction of this unblemished subenvironment, the 
  18. algorithm is placed within an execution environment that is 
  19. configured according to the operating system's prime 
  20. directives.  The configuration of the execution environment 
  21. as it exists immediately after the algorithm is placed 
  22. therein imposes a set of conditions under which the 
  23. algorithm may attempt to perform its prime directives.
  24.      The set of conditions which restrict the activities of 
  25. the algorithm at the onset of execution are called initial 
  26. conditions, simply because they exist before execution 
  27. commences.  These initial conditions must be distinguished 
  28. from later conditions because the configuration of the 
  29. execution environment is subject to change.  In fact, the 
  30. execution environment may be altered by the algorithm 
  31. itself.
  32.      It is possible, and probable, that the algorithm's 
  33. first prime directive will consist of a set of instructions 
  34. to establish new initial conditions.  These new conditions 
  35. may remove restrictions imposed by the operating system, or 
  36. they may further restrict the activities of the algorithm's 
  37. other prime directives.  When an algorithm establishes new 
  38. initial conditions, we say that it initializes the execution 
  39. environment.  And, as long as neither the initial conditions 
  40. imposed by the operating system, nor those imposed by the 
  41. algorithm prevent such activity, the algorithm is free to 
  42. alter existing conditions at any time during its execution.
  43.      It is via this power to alter execution environments 
  44. that we are able to establish a variety of system 
  45. configurations which we translate into system flexibility.  
  46. A flexible system is able to perform any of hundreds of 
  47. tasks equally well.  As payment for this flexibility, we are 
  48. forced to maintain the execution environment of all but the 
  49. most trivial algorithms.
  50.      In chapter two, I pointed out an Assempro debugger 
  51. deficiency which forced me to develop an initialization 
  52. algorithm that would prevent a program's first line of text 
  53. output from overwriting the debugger screen.  This algorithm 
  54. established initial conditions in the program's execution 
  55. environment, which had been established by AssemPro, to 
  56. correct the deficiency; it was the compromise that I had to 
  57. make with the debugger so that it would execute my program 
  58. in a manner that I considered to be proper.
  59.      While one would be justified in venting a certain 
  60. amount of annoyance because of the bother, perhaps by booing 
  61. and hissing for a few minutes, permitting this trifle to 
  62. fester into something more than a minor irritant would only 
  63. hinder productivity.  This type of bother is not at all 
  64. uncommon when working with computers.  However, I am sure 
  65. that you have already come to know this, even if your 
  66. experience has been limited to executing programs rather 
  67. than writing them.
  68.      I think that one can justifiably view all program 
  69. initialization algorithms as corrections to the execution 
  70. environment.  It seems that one should expect a modern 
  71. operating system to handle such trivial, repetitive 
  72. programming chores.  However, we might wish to consider the 
  73. probable inflexibility of such an operating system.  Well, 
  74. regardless of our wishes or opinions concerning the matter, 
  75. for all but the most trivial, the code proper of each Atari 
  76. ST program must be preceded by a sequence of instructions 
  77. that establishes appropriate initial conditions before 
  78. execution proper begins.  Initial conditions for the more 
  79. simple programs is the subject of this chapter.
  80.      Its lack of rigidity is what I consider to be one of 
  81. the most powerful attributes of the ST's programming 
  82. environment.  Nevertheless, it is the operating systems's 
  83. flexibility which exacerbates the development of a program 
  84. classification scheme that would permit one to assign 
  85. specific initialization algorithms to specific program 
  86. types.  When one decides to explain the configuration of the 
  87. ST operating system, one must discuss the interrelation of 
  88. TOS, GEM, GEMDOS, BIOS, XBIOS, GDOS, VDI, AES, Line-A and 
  89. Line-F routines.  I am not even going to try to best those 
  90. that have already accomplished this task admirably.  Instead 
  91. I refer you to an excellent article, Atari St Software 
  92. Development, by Michael Rothman, Byte, September, 1986.  You 
  93. can obtain a copy from Byte or, perhaps, from a local 
  94. library.
  95.      Alternately, although the discussions are not nearly as 
  96. lucid, you might see what you can get out of pages 2-3 
  97. through 2-5 of the Katherine Peel book and pages 3 to 9 of 
  98. the Compute! AES book, by Leemon.  If you add to that which 
  99. you obtain in knowledge from those two books, by reading 
  100. chapter 1 of the Abacus GEM Programmer's Reference, you may 
  101. be able to get by without the Rothman article.
  102.      Note this, in case you don't see it elsewhere, the 
  103. Line-F Emulator is used by the operating system, it is not 
  104. available to users as is indicated on page 237 of the 
  105. Internals book.  The Internals book hints at this use on 
  106. pages 238-239, but the hint is not sufficient; the book 
  107. should warn readers that interference with this emulator 
  108. will upset the AES portion of the operating system.
  109.      Without trying to force a classification on any type of 
  110. Atari ST program, I find it convenient to discuss them in 
  111. terms of specific attributes which can be assumed from the 
  112. following set of categories:
  113.  
  114.      1. The suffix appended to the program name.
  115.      2. The initialization algorithm used.
  116.      3. The manner in which execution is initiated.
  117.      4. The method by which processor control is                 
  118.         relinquished.
  119.      5. The algorithm used to terminate execution.
  120.   
  121.      To some extent, we can classify a program as a 
  122. particular type by referring to one or more of its list of 
  123. attributes drawn from the above categories.  For example, if 
  124. the suffix of a program is ACC, we call it a desk accessory.  
  125. And the unblemished operating system, as it exists at the 
  126. time I compose this sentence, expects a desk accessory to be 
  127. executed in a particular manner, to reside in a particular 
  128. environment, to contain a particular initialization 
  129. algorithm, to relinquish processor control in a particular 
  130. manner and to remain active until the computer is turned 
  131. off.
  132.      But I have one desk accessory, Multi Desk, which 
  133. creates an environment in which I can ignore most operating 
  134. system expectations for all other desk accessories, 
  135. including additional copies of itself.  Therefore, if I were 
  136. to give you a list of required desk accessory attributes, I 
  137. would have to imagine all sorts of exceptions, some of which 
  138. may not even be known to me at this time.  At best, all I 
  139. can do is to give you examples of programs which will 
  140. function as desk accessories in an unblemished operating 
  141. system environment, while placing little or no restrictions 
  142. on their execution in other environments.
  143.      Nevertheless, in general, suffix differentiation serves 
  144. as an indication of the operating system services required 
  145. by the program and the level of user interaction with the 
  146. program.  The program suffixes recognized by the operating 
  147. system are: PRG, TOS, TTP, and ACC.  You have observed some 
  148. of the differences in services that you can expect for 
  149. programs that have a PRG or TOS suffix.  But, remember, 
  150. these differences were exhibited by an unblemished 
  151. environment.  There is no reason to assume that identical, 
  152. or even opposite, services cannot be forced by some 
  153. environment modifying agent.  Thus, when assigning 
  154. attributes to a program, we should not assume that they are 
  155. tightly bound and not subject to change.  We must remain as 
  156. flexible as the computer system we are using.  The best use 
  157. to which we can apply descriptives is for the convenience of 
  158. the moment.  Then we shall not be surprised by behavior that 
  159. we would otherwise view as deviations from the norm.  With 
  160. this viewpoint, we continue.
  161.      Initialization algorithms tend to define the execution 
  162. environment based on the task to be performed.  For example, 
  163. if a program is to produce graphic output, we can suppose 
  164. that it will contain VDI initialization algorithms.  And if 
  165. the graphic output is directed to a window then we can 
  166. suppose the necessity of AES initialization algorithms.
  167.      Program execution may be initiated at boot time, from 
  168. the desktop, from the menu line or by command of another 
  169. program.  An example of the latter process is the execution 
  170. of a program from within a debugger.  But, as I have 
  171. indicated, with the introduction of utilities such as Multi 
  172. Desk, it is no longer possible to define restrictions 
  173. concerning program execution initiation.  For example, Multi 
  174. Desk permits a desk accessory to be loaded and executed 
  175. after the system has booted.
  176.      Primarily, the method of relinquishing processor 
  177. control is determined by the memory residency status of a 
  178. program.  Desk accessories usually relinquish control with 
  179. AES function $17, aka evnt_mesag.  Load and stay resident 
  180. (LSR) programs usually relinquish control with GEMDOS 
  181. function $31, aka ptermres.  When a program causes another 
  182. to be loaded and executed, it relinquishes processor control 
  183. by executing GEMDOS function $4B.
  184.      The execution of a program may be terminated with 
  185. GEMDOS function $0, which simply passes processor control 
  186. back to the initiating agent, either the desktop or another 
  187. program.  Or it may be terminated with GEMDOS function $4C, 
  188. which will pass a message back to the initiating agent.  If 
  189. the program is a desk accessory, it may not terminate until 
  190. the computer is turned off, unless it has been executed via 
  191. a utility such as Multi Desk.Now, it is with an enlightened 
  192. unbiased frame of reference with which we shall proceed into 
  193. a discussion of initialization algorithms for ST programs.
  194.  
  195. Parameters for GEMDOS Function $4A 
  196.   
  197.      The information contained on pages 137 - 138, GEMDOS 
  198. function $4A, aka (also known as) setblock or m_shrink, and 
  199. pages 145 - 148 of the Abacus Internals book becomes germane 
  200. at this time.  The discussion on page 37 of Compute!'s VDI 
  201. book is also worth reading, but there are two critical 
  202. errors on that page.  SETBLOCK is a GEMDOS function, not an 
  203. XBIOS function; and the second longword on the stack, not 
  204. the second word, points to the start of the Transient 
  205. Program Area.
  206.      I would like you to suppress the portion of these 
  207. discussions pertaining to the necessity of a program 
  208. declared stack.  I want you to realize that a default stack 
  209. does exist, and it is perfectly reliable for programs with 
  210. limited stack growth.  The phrase limited stack growth is 
  211. somewhat vague, but I will give you examples that exhibit 
  212. such growth.
  213.      Either the Compute! or the Abacus discussion (or both) 
  214. should impress you with the following facts: one, a 
  215. program's basepage precedes the program proper; two, at the 
  216. onset of program execution, the first address of the 
  217. basepage is stored at 4(a7); three, GEMDOS function $4A 
  218. requires two parameters: the number of bytes occupied by the 
  219. program and the value that is stored in 4(a7) at the onset 
  220. of execution.
  221.      While it may seem that three parameters are passed when 
  222. function $4A is invoked, according to the examples you will 
  223. see (One is called a meaningless word in the Abacus 
  224. example.), that third parameter is simply one word of the 
  225. function specification for the GEMDOS call.  If you specify 
  226. the function as a longword, it becomes $4A0000, and there is 
  227. no meaningless word to be passed.  This means that you 
  228. specify the function using the instruction: move.l #$4A0000, 
  229. -(sp).      I want to impress you with the fact that the 
  230. number of bytes occupied by a program is precisely the 
  231. difference between the last address occupied by the program 
  232. and that value at 4(a7).  In program 9, a label is placed at 
  233. the end of the program, just before the end statement.  
  234. Then, the number of bytes occupied by the program is 
  235. calculated by subtracting the basepage start address from 
  236. the address of that label.  Program 9 was written to verify 
  237. that the value obtained by this method is equivalent to that 
  238. obtained using the sum of program segments method discussed 
  239. in the references.
  240.      The verification procedure requires the use of a rather 
  241. complex binary to ASCII decimal conversion algorithm.  This 
  242. subroutine is essential to the output of data, not only in 
  243. program 9 but also in the programs to immediately follow.  A 
  244. discussion of program 9's algorithms follows its listing and 
  245. execution results.
  246.  
  247. Program 9. Comparing two methods of computing program 
  248. length.  Execution results follow the listing.
  249.  
  250.  ; Program Name: PRG_3AP.S
  251.  
  252.  ; Assembly Instructions:
  253.  
  254.  ;    Assemble in PC-relative mode and save with a PRG and a TOS extension.
  255.  
  256.  ; Function:
  257.  
  258.  ;    Verifies that two methods of calculating the amount of memory
  259.  ; occupied by a program are equivalent.
  260.  
  261.  ; Execution Instructions:
  262.  
  263.  ;    Double click on PRG_3AP.TOS from the desktop.  A heading and two lines
  264.  ; of data will be printed on the screen.  Press the return key to terminate
  265.  ; execution and return to the desktop.
  266.  
  267.  ; MAJOR NOTE: Because base page information is needed by this program,
  268.  ;             if it is to be executed in the AssemPro debugger, it must
  269.  ;             be loaded with the "Execute program" function.
  270.  
  271.  ; Note - The stack used in the program is small enough to permit easy
  272.  ;        access to its contents via the AssemPro debugger.  The stack
  273.  ;        exhibits limited growth, and program execution is flawless with
  274.  ;        a system assigned, default stack.
  275.  
  276. calculate_size_1:               ; Sums data that is stored in basepage. 
  277.  movea.l    4(a7), a5           ; Copy basepage address in A5.
  278.  move.l     $C(a5), d7          ; Copy program text length in D7.
  279.  add.l      $14(a5), d7         ; Add data length.
  280.  add.l      $1C(a5), d7         ; Add bss length.
  281.  add.l      #$100, d7           ; Add basepage length.
  282.  
  283. calculate_size_2:               ; Subtracts last program address from first.
  284.  lea        program_end, a4     ; Put "end of program" address in A4.
  285.  suba.l     4(a7), a4           ; Subtract basepage address from A4.
  286.  
  287. mainline:
  288.  lea        stack, a7           ; Put address of label "stack" in A7.
  289.  bsr.s      print_newline       ; Prevents damage to AssemPro screen and
  290.                                 ; skips a line for printer output.
  291.  lea        heading, a0         ; Print heading for program's output.  The
  292.  bsr.s      print_string        ; print_string subroutine expects the address
  293.                                 ; of the string to be in A0.
  294. print_size_1:
  295.  lea        message_1, a0       ; Print label to identify output data.
  296.  bsr.s      print_string        ; 
  297.  move.l     d7, d1              ; The bin_to_decimal subroutine expects D1 to
  298.  bsr.s      bin_to_dec          ; contain the binary number to be converted.
  299.  lea        decimal, a0         ; Print program size, an ASCII string that is.  
  300.  bsr.s      print_string        ; stored in the array "decimal".
  301.  lea        end_msg, a0         ; Print "units" label for output data.
  302.  bsr.s      print_string       
  303.  
  304. print_size_2:
  305.  lea        message_2, a0       ; Same remarks as for "print_size_1".
  306.  bsr.s      print_string
  307.  move.l     a4, d1          
  308.  bsr.s      bin_to_dec
  309.  lea        decimal, a0
  310.  bsr.s      print_string
  311.  lea        end_msg, a0
  312.  bsr.s      print_string
  313.  
  314. wait_for_keypress:              ; Halt execution so screen can be read.
  315.  move.w     #8, -(sp)           ; Function = c_necin = GEMDOS $8.
  316.  trap       #1                  ; GEMDOS call.
  317.  addq.l     #2, sp              ; Reposition stack pointer at top of stack.
  318.  
  319. terminate:
  320.  move.w     #0, -(sp)           ; Function = p_term_old = GEMDOS $0.
  321.  trap       #1                  ; GEMDOS call.
  322.  
  323.  ;
  324.  ; SUBROUTINES
  325.  ;
  326.  
  327. print_string:                   ; Expects address of string to be in A0.
  328.  pea        (a0)                ; Push address of string onto stack.
  329.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  330.  trap       #1                  ; GEMDOS call
  331.  addq.l     #6, sp              ; Reposition stack pointer.
  332.  rts
  333.  
  334. print_newline:                  ; Prints a carriage return and linefeed.
  335.  pea        newline             ; Push address of string onto stack.
  336.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  337.  trap       #1                  ; GEMDOS call
  338.  addq.l     #6, sp
  339.  rts
  340.  
  341.  ; This is a binary to ASCII decimal conversion subroutine.  It converts a
  342.  ; 32-bit binary number in register D1 to an ASCII string that is stored in
  343.  ; an array called "decimal".
  344.  
  345.  ; The binary to ASCII decimal conversion subroutine uses an algorithm
  346.  ; based on "repeated subtraction".  The subtrahends are powers of ten
  347.  ; which range from 10^1 to 10^9.  These values were chosen to accommodate
  348.  ; up to ten digit integers.  The binary value passed to the subroutine is
  349.  ; the initial current minuend.  After a decimal digit is extracted from
  350.  ; a current minuend, a new current minuend is produced.
  351.  
  352.  ; During the subtraction process, the minuend is gradually reduced to a
  353.  ; negative number.  The number of times that each power of ten can be
  354.  ; subtracted from a current minuend before the minuend becomes negative
  355.  ; yields a significant digit for that power of ten.
  356.  
  357.  ; As each digit is extracted, it is converted to its ASCII code by adding
  358.  ; the ASCII code for decimal 0 to the accumulated subtractions count.  The
  359.  ; ASCII code for each digit is stored as a character in a string.  The string
  360.  ; is accumulated in the array called "decimal".
  361.  
  362.  ; After the current minuend becomes negative a "new" minuend is formed by
  363.  ; adding the current subtrahend to the negative value.  The process 
  364.  ; continues until the current subtrahend is 0.
  365.  
  366.  ; The algorithm discards leading zeroes using a loop that subtracts
  367.  ; subtrahends from the number in D1, beginning with the largest, until a
  368.  ; subtrahend which does immediately yield a negative difference is detected.
  369.  
  370.  ; NOTE: Register D2 is the subtractions counter.  In it is accumulated the
  371.  ;       number of times that a subtrahend can be subtracted from a minuend
  372.  ;       before the minuend becomes negative.  At the start of the subroutine
  373.  ;       D2 is initialized to zero to guarantee that the first count
  374.  ;       accumulated is correct.
  375.  
  376.  ;       But observe that D2 is initialized to -1 in the section of the
  377.  ;       routine labeled "loop_setup".  The reason: after registers are
  378.  ;       initialized in the loop_setup section, execution branches to the
  379.  ;       section labeled "subtract".  The first instruction in that section
  380.  ;       adds 1 to the value in D2.
  381.  
  382.  ;       I chose to add 1 to the subtractions counter at that particular
  383.  ;       location because it was a convenient point in the algorithm to do
  384.  ;       so upon exit from the "discard leading zeroes" loop.  Upon exit from
  385.  ;       that loop, a valid subtraction has already been performed, therefore,
  386.  ;       it must be accumulated.  Since D2 contains the initialized value 0
  387.  ;       at that point in the execution, adding 1 puts the correct count in
  388.  ;       D2.
  389.  
  390.  ;       Because I want to branch to the same section upon exit from the loop
  391.  ;       setup section, I must pre-initialize D2 to -1.  Then, after 1 is
  392.  ;       added to D2 via the first instruction in the subtract section, the
  393.  ;       subtractions counter is fully initialized to zero, as it should be.
  394.  
  395. bin_to_dec:                  
  396.  lea        decimal, a0         ; Put address of array "decimal" in A0.
  397.  lea        subtrahend, a1      ; Put address of subtrahend table in A1.
  398.  move.l     (a1)+, d0           ; Put first subtrahend in D0.
  399.  moveq      #0, d2              ; Initialize subtractions counter to zero.
  400. get_sign:
  401.  tst.l      d1                  ; Is binary number positive, negative or zero?
  402.  beq.s      zero_passed         ; Branch if number is 0.
  403.  bpl.s      positive            ; Branch if positive.
  404.  move.b     #$2D, (a0)+         ; Store a minus sign in array "decimal".
  405.  neg.l      d1                  ; Change binary number from neg to pos.
  406.  bra.s      discard_leading_zeroes
  407. positive:                       ; Branch to here when number is positive.
  408.  move.b     #$20, (a0)+         ; Store a space in array decimal.
  409. discard_leading_zeroes:         ; Subtract subtrahend from minuend.
  410.  sub.l      d0, d1              ; Loop till difference is positive,
  411.  bpl.s      subtract            ; indicating that digit is not zero.
  412.  add.l      d0, d1              ; Restore minuend.
  413.  move.l     (a1)+, d0           ; Get next subtrahend.
  414.  bra.s      discard_leading_zeroes
  415. subtract:
  416.  addq.b     #1, d2              ; Increment subtractions counter.
  417.  sub.l      d0, d1              ; Subtract subtrahend from D1.
  418.  bpl.s      subtract            ; Loop until D1 becomes negative.
  419. convert_to_ascii:
  420.  addi.b     #$30, d2            ; Converts binary number to ASCII code.
  421.  move.b     d2, (a0)+           ; Store the ASCII digit in array "decimal".
  422. loop_setup:
  423.  add.l      d0, d1              ; Restore the minuend.
  424.  moveq      #-1, d2             ; Pre-initialize subtractions counter to -1.
  425.  move.l     (a1)+, d0           ; Get next subtrahend.
  426.  bne.s      subtract            ; Loop back until subtrahend = 0.
  427.  move.b     #0, (a0)            ; Terminate decimal string with a null.
  428.  rts
  429. zero_passed:
  430.  move.b     #$20, (a0)+         ; Store a space in array "decimal".
  431.  move.b     #$30, (a0)+         ; Store an ASCII zero in array "decimal".
  432.  move.b     #0, (a0)            ; Terminate ASCII decimal string with a null.
  433.  rts
  434.  
  435.  data
  436. subtrahend:  dc.l   $3B9ACA00,$5F5E100,$989680,$F4240,$186A0,$2710,$3E8
  437.              dc.l   $64,$A,$1,$0
  438. heading:     dc.b   $D,$A,'PRG_3AP Execution Results',$D,$A,$D,$A,0
  439. message_1:   dc.b         '   Program Size by Method One: ',0
  440. message_2:   dc.b         '   Program Size by Method Two: ',0
  441. end_msg:     dc.b   ' bytes.',$D,$A,0
  442. newline:     dc.b   $D,$A,0
  443.  bss
  444.  align                  ; Align storage on a word boundary.           
  445. decimal:     ds.l   3   ; Output buffer, NULL terminated.
  446.              ds.l  24   ; Program stack.
  447. stack:       ds.l   1   ; Address of program stack.
  448. program_end: ds.l   0
  449.  ;
  450.  ; NOTE: The declaration following the label "stack", above, can be
  451.  ;       ds.l 0, instead of ds.l 1.  I take advantage of this in later
  452.  ;       programs.  When the declaration is made in that manner, however,
  453.  ;       the label "program_end" does not appear in the debugger's
  454.  ;       output field.  Instead, the label "stack" appears where you
  455.  ;       would expect "program_end", because, although the assembler
  456.  ;       accepts this label without issuing an error report, the label
  457.  ;       "program_end" will not be a part of the program whenever the
  458.  ;       declaration following "stack:" is ds.l 0.
  459.  end
  460.  
  461. PRG_3AP Execution Results
  462.  
  463.    Program Size by Method One:  728 bytes.
  464.    Program Size by Method Two:  728 bytes.
  465.  
  466.  
  467. Discussion of Program 9
  468.  
  469.      Notice that I have declared a stack for the program.  I 
  470. have done so because I want you to be able to view the 
  471. contents of the stack in the AssemPro debugger.  Further, 
  472. notice that it is of a size that is commensurate with 
  473. necessity.  Of course you can declare a stack size that 
  474. represents the wildest possible guess, but there is no 
  475. reason to do so.
  476.      The system functions, GEMDOS $8 and GEMDOS $9 are 
  477. adequately covered in the Abacus Internals book, however, 
  478. there are two things I must say about the code for function 
  479. $9 as it appears in that book.  If a program in which this 
  480. function is invoked is to be assembled in PC-relative mode, 
  481. the address of the string must be pushed onto the stack with 
  482. the pea instruction; the move.l #label, -(sp) instruction 
  483. can be used if the program is to be assembled in Relocatable 
  484. mode.  Also, note that text is an AssemPro reserved word, so 
  485. don't use it as a label.
  486.      There is a word of caution to be observed when using 
  487. GEMDOS function $9 as a subroutine.  To do this, you might 
  488. be tempted to push the address of the string onto the stack 
  489. before each call to the subroutine.  That will not work 
  490. because the stack and its pointer will be altered by the 
  491. call to the subroutine; the return address will be pushed 
  492. onto the stack after your string address.  The way to use 
  493. GEMDOS function $9 as a subroutine is as follows: change the 
  494. first function statement to pea  (An), where An is any 
  495. address register (other than A7, of course); then, just 
  496. before you call the subroutine to print a string, use the 
  497. instruction, lea  string, An.
  498.      My discussion of PRG_3AP will proceed much more 
  499. smoothly if I refer to real addresses and data.  Therefore, 
  500. I will use a partial disassembly of the program's basepage, 
  501. the front end of the program and the program stack, as it 
  502. appeared in the AssemPro debugger when I executed the 
  503. program.  You can perform your own observations after 
  504. assembling PRG_3AP and saving it with a TOS or PRG 
  505. extension.  Load it into the debugger with the Execute 
  506. program function.
  507.      You can obtain your own disassembly listing by clicking 
  508. on the Disassembling option under the Debugger menu.  Figure 
  509. 4.1, a drawing of the Disassembling dialog box, illustrates 
  510. the appearance of the dialog box fields, just before you 
  511. click on the OK button.  If you would rather have a hard 
  512. copy, then click on PRT: instead of File :, but sending a 
  513. disassembly listing directly to a printer is hazardous 
  514. because the listing usually contains ASCII codes that will 
  515. disrupt the printer.  Furthermore, you must be cautious when 
  516. you decide to use the File option also.
  517.      If you have chosen to check the Back-up copy option, as 
  518. illustrated in figure 2.4B of chapter two, then, if you now 
  519. choose a file name for the disassembly listing that is 
  520. already in use on disk, you will be presented with the File 
  521. already exists ! dialog box shown in figure 2.2C of chapter 
  522. two.  But no matter which option you choose the system will 
  523. freeze, and you will have to reboot.  Avoid the use of 
  524. duplicate file names when using AssemPro's Disassembly mode.
  525.      If you choose a file in which AssemPro can save the 
  526. disassembly listing, you can easily remove the ASCII codes 
  527. that disrupt the printer from the file with the Tempus 
  528. editor's Search and replace function, thereby assuring 
  529. smooth printer operation when you print the file.  After 
  530. AssemPro has completed the disassembly, load the file into 
  531. the Tempus editor.
  532.      First, delete the Atari Logo from the first line of the 
  533. listing.  Then, on the Search below string: parameter line 
  534. of the Search and replace dialog box, type ;*.  Type nothing 
  535. on the ...and replace by following parameter line.  Select 
  536. whole text as the search region, Start search at cursor, 
  537. Direction down, Quantity total.  When you click the Start 
  538. button in the dialog box, Tempus will discard the semicolon 
  539. and all characters after it on all lines containing same; 
  540. this action will remove all characters which might cause the 
  541. printer to scream and beg for mercy.
  542.  
  543. Figure 4.1. The Disassembling dialog box; a slightly 
  544. compressed representation.  The from and to address that you 
  545. use must be obtained from your debugger window.  WARNING: Do 
  546. not use a file name that is already in use on disk.
  547.  
  548.  
  549.  
  550.  
  551.  
  552.  
  553.  
  554.  
  555.  
  556.  
  557.  
  558.  
  559.  
  560.  
  561.  
  562.  
  563.  
  564.  
  565.  
  566.  
  567.  
  568.  
  569.      The from address field requires the address from which 
  570. disassembly is to begin; in this case, the basepage start 
  571. address, calculated by subtracting $100 from the program 
  572. start address, as it is shown in the debugger window.  The 
  573. to address field requires the address at which disassembly 
  574. is to cease; neither the to address nor its content will be 
  575. disassembled.
  576.      To determine a suitable to address, you must have some 
  577. knowledge about the last address you want disassembled.  
  578. Fortunately, we have established a program_end address, 
  579. therefore, we simply look for the value being loaded into 
  580. register A4 (after consulting out source listing) as the 
  581. first instruction in the calculate_size_2 algorithm.  On my 
  582. debugger screen, this value was $86080.
  583.      Now, we think a little.  If we want to see the 
  584. program_end address and the longword contents at that 
  585. address in the disassembly listing, then we must use an 
  586. address beyond that one for the to address.  We can simply 
  587. add four to $86080 to obtain $86084 as the to address.  
  588. Alternately, if we decide that we don't need the program_end 
  589. address to show up on the listing, we can simply use it as 
  590. the to address.  Actually, that is the second benefit we 
  591. derive from our use of the program_end label.
  592.      Figure 4.2 is a listing of the first 28 bytes of 
  593. PRG_3AP's basepage.  The first address shown, $085DA8, is 
  594. the basepage start address.  This is the address that is 
  595. copied from 4(A7) to A5 as the first step in the 
  596. calculate_size_1 algorithm.  The basepage start address is 
  597. just $100 bytes before the program start address.  The 
  598. contents of the basepage start address is itself, $00085DA8.  
  599. Within the calculate_size_1 algorithm, the basepage length, 
  600. $100 bytes, is added to data located at specific offsets 
  601. from the basepage address.
  602.      The text length is the longword located at basepage 
  603. start address + $C = $085DB4; it consumes $000000CE bytes.  
  604. The data length is the longword located at basepage start 
  605. address + $14 = $085DBC; it consumes $0000009A bytes.  
  606. Finally, the bss length is the longword located at basepage 
  607. start address + $1C = $085DC4 and consumes $00000070 bytes.  
  608. The total length accumulated by the first algorithm is $100 
  609. + $CE + $9A + 70 = $2D8 = 728 (decimal) bytes.
  610.   
  611. Figure 4.2. Partial disassembly of program 9's basepage.
  612.  
  613. PRG_3AP.TOS disassembly after execution.
  614.  
  615.      085DA8 0008          DC.W 8 
  616.      085DAA 5DA8000F      SUBQ.L #6,$F(A0)
  617.      085DAE 8000          OR.B D0,D0
  618.      085DB0 0008          DC.W 8 
  619.      085DB2 5EA80000      ADDQ.L #7,0(A0)
  620.      085DB6 00CE          DC.W $CE 
  621.      085DB8 0008          DC.W 8 
  622.      085DBA 5F760000      SUBQ.W #7,0(A6,D0.W)
  623.      085DBE 009A00086010  ORI.L #$86010,(A2)+
  624.      085DC4 00000070      ORI.B #$70,D0
  625.  
  626.  
  627.      The values used to calculate the total length by the 
  628. calculate_size_2 algorithm can be seen in figure 4.3.  
  629. Although there are no labels in that figure to guide us, we 
  630. can determine which addresses contain the data we for which 
  631. we are looking by referring to the program source code.
  632.      As shown at memory address $085EBE, the address that is 
  633. loaded into A4, $86080, is the address of the label 
  634. program_end.  The basepage start address is subtracted from 
  635. that address; as shown at memory address $085EC2.  The first 
  636. address shown in figure 4.3, 085EA8, is the program start 
  637. address.  We can deduce the basepage start address from this 
  638. knowledge because it is $100 less, which is $085DA8.
  639.      A programmer's calculator proves to us that the 
  640. program_end address minus the basepage start address = 
  641. $86080 - $85DA8 = $2D8 = 728 (decimal).  This is precisely 
  642. the value obtained by the first algorithm.  We conclude that 
  643. the algorithms produce identical results, therefore we will 
  644. choose to use the second algorithm, whenever possible, 
  645. because it is faster and consumes less memory.
  646.   
  647. Figure 4.3. Disassembled front end of program 9.
  648.   
  649.      085EA8 2A6F0004      MOVEA.L 4(A7),A5
  650.      085EAC 2E2D000C      MOVE.L $C(A5),D7
  651.      085EB0 DEAD0014      ADD.L $14(A5),D7
  652.      085EB4 DEAD001C      ADD.L $1C(A5),D7
  653.      085EB8 DEBC00000100  ADD.L #$100,D7
  654.      085EBE 49FA01C0      LEA $86080(PC),A4
  655.      085EC2 99EF0004      SUBA.L 4(A7),A4
  656.      085EC6 4FFA01B4      LEA $8607C(PC),A7
  657.  
  658.  
  659.      The final item of datum to be extracted from figure 4.3 
  660. is the address of the program stack, shown as the value 
  661. being loaded into register A7 at memory address 085EC6.  
  662. Figure 4.4A is a view of the entire stack.  The first 
  663. address in that figure, 08600E, is not part of the stack; 
  664. the first word of data there, 4 zeroes, was inserted by the 
  665. align statement; at address 08010 is the contents of the 
  666. buffer decimal, in which we stored the results computed by 
  667. the binary to decimal conversion subroutine.  You can see 
  668. the ASCII value for the leading blank space that indicates a 
  669. positive value, followed by the ASCII values for 728.  By 
  670. now, you are aware that the instructions at addresses 08600E 
  671. and 086012, into which those ASCII values just happen to 
  672. translate, are happenstance.
  673.  
  674.  Figure 4.4A. Program 9's stack, disassembled.
  675.  
  676. NOTE: $86010 is the address of the label "decimal".  The 
  677. space reserved for decimal is three longwords.  Therefore, 
  678. the last byte of the stack is at address $8601C.  
  679. Apparently, the last word of the stack that was used is at 
  680. address $86040 (The contents there is $0005.).  If so, the 
  681. rest of the space declared for the stack is wastage.
  682.  
  683.      08600E 00002037      ORI.B #$37,D0
  684.      086012 32380000      MOVE.W 0,D1
  685.      086016 00000000      ORI.B #0,D0
  686.      08601A 00000000      ORI.B #0,D0
  687.      08601E 00000000      ORI.B #0,D0
  688.      086022 00000000      ORI.B #0,D0
  689.      086026 00000000      ORI.B #0,D0
  690.      08602A 00000000      ORI.B #0,D0
  691.      08602E 00000000      ORI.B #0,D0
  692.      086032 00000000      ORI.B #0,D0
  693.      086036 00000000      ORI.B #0,D0
  694.      08603A 00000000      ORI.B #0,D0
  695.      08603E 00000005      ORI.B #5,D0
  696.      086042 F0C2          DC.W $F0C2 
  697.      086044 0310          BTST D1,(A0)
  698.      086046 0008          DC.W 8 
  699.      086048 0005F0C2      ORI.B #-$3E,D5
  700.      08604C 0314          BTST D1,(A4)
  701.      08604E 0008          DC.W 8 
  702.      086050 5F0C          SUBQ.B #7,A4
  703.      086052 00000000      ORI.B #0,D0
  704.      086056 FFFF          DC.W $FFFF 
  705.      086058 FFFF          DC.W $FFFF 
  706.      08605A 00000000      ORI.B #0,D0
  707.      08605E 00000000      ORI.B #0,D0
  708.      086062 00000000      ORI.B #0,D0
  709.      086066 00000000      ORI.B #0,D0
  710.      08606A 000002D8      ORI.B #-$28,D0
  711.      08606E 0008          DC.W 8 
  712.      086070 607A          BRA.S $860EC
  713.      086072 0008          DC.W 8 
  714.      086074 5FA2          SUBQ.L #7,-(A2)
  715.      086076 00000000      ORI.B #0,D0
  716.      08607A 00000000      ORI.B #0,D0
  717.      08607E 00000000      ORI.B #0,D0
  718.                                            
  719. NOTE: Initially, before there is any stack movement, 
  720. $8607C is the top of the stack.  The space that was 
  721. reserved with the ds.l 1 following the label "stack" is not 
  722. used because the stack address is always pre-decremented 
  723. before movement to the stack occurs.  To avoid that waste, 
  724. you can simply declare ds.l 0 after the label "stack".
  725.  
  726.  
  727.      Now, unless we were to view the stack in the AssemPro 
  728. debugger after single stepping through each instruction (By 
  729. the way, something you don't do is single step through 
  730. system trap calls.  Oops. I should have said that I have not 
  731. been successful in doing so.) we can not be certain that the 
  732. entire stack was never utilized by the program.  However, 
  733. looking at the memory locations towards the bottom of the 
  734. stack (those nearest address 08601C), we see that the last 
  735. nine longwords of the stack contain zeroes after program 
  736. execution, and these locations appear not to have been used.
  737.      We could test the theory by declaring the stack to be 
  738. 15 longwords instead of 24, then by assembling and executing 
  739. the program again.  If our data written to the address of 
  740. the label decimal were to be preserved after that execution, 
  741. we could conclude that a stack length of 15 longwords (60 
  742. bytes) is sufficient for this program.  In the references, 
  743. you will often see unreasonably long stacks being declared.  
  744. I simply remind you that a byte wasted is a byte you can't 
  745. use.
  746.      A clearer picture of stack penetration is obtained if 
  747. the variable decimal is loaded with #'s and the stack is 
  748. loaded with !'s before execution.  See figure 4.4B.  There 
  749. you can see that address $86040 was definitely the last 
  750. stack location used during program execution.
  751.  
  752. Figure 4.4B. A clear picture of program 9's stack 
  753. penetration.
  754.  
  755.  
  756.  
  757.  
  758.  
  759.  
  760.  
  761.  
  762.  
  763.  
  764.  
  765.  
  766.   
  767.      After modifying program 9's stack declaration to be 15 
  768. longwords, the after-execution picture shown in figure 4.4C 
  769. develops.  The first longword of decimal contains the ASCII 
  770. code for the new program length; the second contains a null 
  771. character and 3 #'s; the third contains 4 #'s.  In 
  772. preparation for this picture, I also stored ampersands in 
  773. the longword declared following the label stack in order to 
  774. more clearly delineate the 15 longwords of the stack.  
  775. Figure 4.4C clearly shows that this new stack size if 
  776. sufficient.
  777.      Suppose that it were not enough.  How could we tell?  
  778. Depending on what values were overwritten into the area 
  779. reserved for decimal, an insufficient stack size might not 
  780. be apparent.  Therefore, until you become accustomed to the 
  781. stack requirements of the operating system, it is best to 
  782. start with a large stack and reduce its size after you get 
  783. some idea of the penetration.  What is large?  Is 512 
  784. longwords sufficiently large?  That is 2048 bytes, and it is 
  785. large.  When you are truly nervous, use 1024 longwords.       
  786. Perhaps it has occurred to you by now that something has 
  787. been left unsaid.  A proper question at this time would be: 
  788. "Why can't we tell what the stack penetration is from the 
  789. program itself?".  As you shall see when the supervisor mode 
  790. is discussed in chapter 5, a program's stack is used by 
  791. agents outside of the program also.  And the penetration by 
  792. the outside agents is not easily predetermined.
  793.   
  794. Figure 4.4C. A picture of the reduced stack's 
  795. penetration.  Also shown is the longword declared at the 
  796. label stack.  It is filled with ampersands.
  797.  
  798.  
  799.  
  800.  
  801.  
  802.  
  803.  
  804.  
  805.  
  806.  
  807.  
  808.  
  809.  
  810.  
  811.  
  812.  
  813.  
  814.      Using figure 4.4C as a guide, it is easy to see that if 
  815. the stack size were 14 longwords, instead of 15, then the 
  816. data occupying the longword at address $8601C would simply 
  817. be stored at address $86018, and we would notice no 
  818. detrimental effects because the data in decimal does not 
  819. extend far enough to occupy that longword location.  In 
  820. fact, even if the stack were only 13 longwords, the program 
  821. would report the correct value for decimal because, although 
  822. stack data would occupy a key byte in the variable, that 
  823. byte of stack data, a null, is identical to the variable 
  824. datum which it would replace.
  825.      So, it is not until the size of the stack is reduced to 
  826. as few as 12 longwords, at which time the leading blank 
  827. space for the value 692 would become null, that the variable 
  828. decimal actually becomes corrupted enough to give an invalid 
  829. value as output data.  Of course, if we declare the stack 
  830. space in words or bytes, corruption would take place as soon 
  831. as the null at the end of the ASCII string 692 were replaced 
  832. by the 05 in the stack longword 00 05 F0 C2.
  833.      As the declared space for the stack is reduced further, 
  834. the data in the stack overwrites more of the program's 
  835. declared data.  Soon it begins to overwrite the declared 
  836. strings, and the problem becomes readily apparent simply by 
  837. looking at the programs output statements.  Although stack 
  838. penetration for this program could never be deep enough to 
  839. completely disrupt the object code, even if a zero stack 
  840. size were declared, it is possible for such to occur when 
  841. the declared stack size if insufficient.
  842.      When that happens, the program may freeze the system, 
  843. bombs may appear on the screen or, if the program is 
  844. executed from the debugger, you may see a Bus error message.  
  845. Whenever you have declared a stack size that is just 
  846. sufficient for maximum penetration, suspect that stack size 
  847. immediately if you see bombs from the desktop, or if the 
  848. system freezes.  It is easy enough to declare a large stack 
  849. size while you are experimenting with the program, then to 
  850. reduce the size to something more reasonable after the 
  851. program is functional.
  852.  
  853. The Binary to ASCII Decimal Conversion Algorithm
  854.   
  855.      To me, it seems reasonable, even mandatory, for a 
  856. beginning assembly language programmer to question the 
  857. necessity of conversion algorithms that convert a number 
  858. from one base to another, and thence to ASCII character 
  859. codes, before printing its value.  The answer involves the 
  860. functions that are used for printing and the parameters 
  861. which they expect to be passed to them.  The phrases of 
  862. interest can be viewed on pages 107 - 110 of the Internals 
  863. book.
  864.      Within the explanations of the functions discussed on 
  865. those pages, you see phrases such as The ASCII code of the 
  866. pressed key is returned in the low byte of the low word and 
  867. The ASCII value of the character to be printed must be in 
  868. the low byte of the low word.  Now, these discussions tell 
  869. us nothing about converting from one base to another, nor do 
  870. they tell us how to produce the ASCII characters that the 
  871. functions find so desirable.
  872.      The example of function $02 usage, on page 108, simply 
  873. indicates that the decimal number 65, passed as a parameter 
  874. will produce the letter A on the video screen.  But suppose 
  875. we want to print the number 65?  Then we would have to call 
  876. the function twice: once with the ASCII value 54, then 
  877. again, with the ASCII value 53.  Using the GEMDOS $02 
  878. function in this manner, we can directly print any character 
  879. that we wish to the screen.  However, this usage is of 
  880. minimal value to us.
  881.      We need a way to pass parameters to these functions 
  882. without this direct involvement in the passing of ASCII 
  883. coded parameters.  The binary to ASCII decimal conversion 
  884. algorithm relieves us of this burden.  The algorithm does 
  885. more than that.  When we accumulate a number to be printed, 
  886. the result is stored in some register as a binary value.  We 
  887. could choose to print this binary value, if we so desired.  
  888. However, we would still need to convert each binary digit to 
  889. its ASCII equivalent before passing it to the output 
  890. function.
  891.      Doing that, we would end up with a long string of ones 
  892. and zeroes on the screen.  We don't really want that; we 
  893. want to see something much easier to interpret.  That's why 
  894. we want to convert the number from binary to decimal.  Well, 
  895. the conversion algorithm does that also.  In a two-stage 
  896. process, the algorithm first converts the binary number to 
  897. decimal digits, then it converts the decimal digits to ASCII 
  898. characters.
  899.      The conversion algorithm is amply documented within the 
  900. program listing, so I shan't repeat the information here.  I 
  901. will say that this binary to ASCII decimal algorithm is not 
  902. the one you will see most often in the references.  
  903. Therefore, this algorithm will be compared to the one you 
  904. are most likely to see in an upcoming program.
  905.      I find the fact that all of this repetitive conversion 
  906. is still required on modern computers to be disturbing.  Yet 
  907. there is little that we can do about it at this time, except 
  908. to develop conversion algorithms that execute as rapidly as 
  909. possible, while consuming as little memory as possible.  In 
  910. this spirit, I offer a modification to the bin_to_dec 
  911. algorithm that reduces the execution time.
  912.      Change the statement moveq #0,d2 (just before the 
  913. get_sign label) to move.b #$30,d2.  This new statement 
  914. initializes the subtractions counter to the ASCII value for 
  915. decimal 0.  When the counter is incremented for a valid 
  916. subtraction, the ASCII code in D2 will be valid for the 
  917. accumulated count.  If the number passed to the subroutine 
  918. is zero, then the ASCII code for that value will already be 
  919. in D2 when the branch is made to the zero_passed label; 
  920. therefore, you can change the second statement at that label 
  921. from move.b #$30,(a0)+ to move.b d2,(a0)+.
  922.      In the loop_setup section of the algorithm, change the 
  923. moveq #-1, d2 statement to move.b #$2F,d2 to initialize the 
  924. subtractions counter to 1 less than the ASCII code for 
  925. decimal 0.  Delete the label convert_to_ascii: and the 
  926. instruction addi.b #$30,d2 immediately following it.  This 
  927. conversion is no longer necessary because it is the ASCII 
  928. code for the decimal digits that is being accumulated in D2.
  929.  
  930. The Programs That Use GEMDOS Function $4A
  931.   
  932.      At times, and remember that I consider it to be the 
  933. most powerful available for any computer, the behavior of 
  934. the ST's operating system resembles that of a newborn child 
  935. which requires burping after a feeding.  This seems to be 
  936. the role of GEMDOS function $4A (aka m_shrink, setblock and 
  937. etc.).  Any program, except those that are desk accessories 
  938. and those that relinquish processor control with GEMDOS 
  939. function $31, which does not begin with an initialization 
  940. sequence that invokes this function ties up all available 
  941. memory.
  942.      That this is a default situation is appalling.  Could 
  943. we not rely on the loader to calculate the memory occupied 
  944. by a program that it has just loaded and to assign to that 
  945. program the memory required for its occupancy?  The subject 
  946. of ST memory management is adequately covered in the 
  947. Internals book, and I have no reason to repeat its 
  948. information.  Frankly, I would find the task depressing 
  949. because the part of the operating system that performs this 
  950. function is so damn inefficient.
  951.      I have included program 10 as a well documented example 
  952. which features the use of function $4A.  You should read the 
  953. MAJOR NOTE included in the program before attempting to 
  954. assemble and execute the program.  To verify that this 
  955. program functions correctly, do this: load PRG_3BP.TOS into 
  956. the AssemPro debugger using the Execute program function; 
  957. try to select the Save screen function--AssemPro will tell 
  958. you that there is insufficient room in memory; install a 
  959. breakpoint at the lea $C(A7),SP instruction and click on the 
  960. Run program button; when execution stops at the breakpoint, 
  961. you will now be able to select the Save screen function 
  962. because the invocation of GEMDOS function $4A will have 
  963. released all memory not occupied by the program.
  964.  
  965.   
  966. Program 10. A program that initializes with GEMDOS function 
  967. $4A.
  968.  
  969.  ; Program Name: PRG_3BP.S
  970.  
  971.  ; Assembly Instructions:
  972.  
  973.  ;    Assemble in PC-relative mode and save with a TOS extension.
  974.  
  975.  ; Program Function:
  976.  
  977.  ;    Illustrates the use of GEMDOS function $4A to return to GEMDOS all
  978.  ; memory except that required by this program.
  979.  
  980.  ; Execution Instructions:
  981.  
  982.  ;    Double click on PRG_3BP.TOS from the desktop.  After viewing the
  983.  ; program's output, press the Return key to terminate execution.
  984.  
  985.  ; Because base page information is needed by this program, if it is to
  986.  ; be executed in the AssemPro debugger, it must be loaded with the
  987.  ; "Execute program" function.
  988.  
  989.  ; MAJOR NOTE:
  990.  
  991.  ;    Whenever a program which processes base page information is to be
  992.  ; executed in the AssemPro debugger, special attention must be paid to the
  993.  ; process by which the program is loaded into the debugger.
  994.  
  995.  ;    If the program is loaded into the debugger with the "Execute program"
  996.  ; function, the entire program can be executed.
  997.  
  998.  ;    If, however, the program is loaded into the debugger as a result of
  999.  ; just having been assembled, the instructions which process basepage
  1000.  ; information cannot be executed because there is no basepage when a program
  1001.  ; is loaded by that process.
  1002.  
  1003.  ;    For this particular program you would procede as follows: relocate the
  1004.  ; program by clicking on the Relocate button, then execute one, and only
  1005.  ; one, of the initialization instructions.  That is instruction four of the
  1006.  ; program:
  1007.  ;
  1008.  ;             lea  stack, a7
  1009.  
  1010.  ;    To accomplish this, move the PC cursor in the disassembly output field
  1011.  ; to the fourth instruction, and execute the instruction by single stepping.
  1012.  ; Then, move the PC cursor to the line containing the label "mainline".
  1013.  ; Now, execution may proceed via the "Run program" or "Single step" buttons.
  1014.  
  1015.  ;    A program that used a default stack provided by the system would
  1016.  ; require only that the PC cursor be moved beyond the initialization
  1017.  ; sequence, which includes the return_memory algorithm.
  1018.  
  1019. calculate_program_size:
  1020.  lea        program_end, a0     ; Put "end of program" address in A0.
  1021.  movea.l    4(a7), a1           ; Put basepage address in A1.
  1022.  suba.l     a1, a0              ; Subtract basepage address from program's
  1023.                                 ; ending address.
  1024.  lea        stack, a7           ; Point A7 to this program's stack.
  1025.  
  1026. return_memory:                  ; Return unused memory to operating system.
  1027.  move.l     a0, -(sp)           ; Store total program length in stack.
  1028.  move.l     a1, -(sp)           ; Store basepage address in stack.
  1029.  move.l     #$4A0000, -(sp)     ; Function = m_shrink = GEMDOS $4A0000.
  1030.  trap       #1                  ; GEMDOS call.
  1031.  lea        $C(a7), sp          ; Reset stack pointer to top of stack.
  1032.  
  1033.  ; Note: When the stack pointer must be moved 8 bytes or less, use 
  1034.  ;       addq.l #n, sp, where n is the number of bytes.  When it must
  1035.  ;       be moved more than 8 bytes, addq.l can't be used.
  1036.  
  1037.  ;       Although you may be tempted to use adda.l #n, sp in that case, a
  1038.  ;       better choice is lea n(a7), sp because it is twice as fast and
  1039.  ;       requires 2 bytes less of memory.  Program LEA_ADDA.TOS verifies
  1040.  ;       these claims.
  1041.  
  1042. mainline:                       ; Marks the beginning of program proper.
  1043.  lea        newline, a0
  1044.  bsr.s      print_string
  1045.  
  1046.  ; The above instructions prevent damage to the debugger screen and skip a
  1047.  ; line for printer output.  Skipping a line on the printer separates the
  1048.  ; program output from a listing which precedes execution, or it will 
  1049.  ; separate the results of repeated executions.
  1050.  
  1051. print_declared_string:
  1052.  lea        string, a0          ; Put address of the label "string" in A0.
  1053.  bsr.s      print_string
  1054.  
  1055. wait_for_keypress: 
  1056.  move.w     #8, -(sp)           ; Function = c_necin = GEMDOS $8.
  1057.  trap       #1                
  1058.  addq.l     #2, sp            
  1059.  
  1060. terminate:
  1061.  move.w    #0, -(sp)            ; Function = p_term_old = GEMDOS $0.
  1062.  trap      #1                 
  1063.  
  1064.  ; SUBROUTINES
  1065.  
  1066. print_string:                   ; Expects address of string to be in A0.
  1067.  pea        (a0)                ; Push address of string onto stack.
  1068.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  1069.  trap       #1                
  1070.  addq.l     #6, sp
  1071.  rts
  1072.  
  1073.  data
  1074. newline: dc.b $D,$A,0  ; All strings must be NULL terminated because the
  1075.                        ; function we are using to print them requires it.
  1076.  
  1077.                        ; Note that the ASCII code for a NULL character is $0,
  1078.                        ; which is equal to decimal 0.
  1079.  
  1080.                        ; The ASCII code for a carriage return is $D; that for
  1081.                        ; a linefeed is $A.
  1082.  
  1083. string:  dc.b 'This string will not overwrite the AssemPro debugger screen.'
  1084.          dc.b $D,$A,0  ; The string is continued on this line.  Here, we
  1085.                        ; declare a carriage return, linefeed and terminate
  1086.                        ; the entire string with a NULL = $0 = 0.
  1087.  
  1088.  bss
  1089.  align                          ; Align storage on a word boundary.
  1090.                  ds.l     16    ; Stack.
  1091. stack:           ds.l      0    ; Address of stack.
  1092. program_end:     ds.l      0    ; Marks the end of program memory.
  1093.  end               
  1094.  
  1095.  
  1096. Repeated Subtraction Versus Repeated Division
  1097.  
  1098.      The binary to ASCII decimal conversion algorithm that 
  1099. you are most likely to see in references is based on 
  1100. repeated division by decimal 10.  Since I advocate the use 
  1101. of a repeated subtraction algorithm, similar to the one 
  1102. introduced in program 9, it seems reasonable to expect me to 
  1103. present a program that justifies my impudence (that's ud 
  1104. back there, not ot).
  1105.      Program 11 is a two-part program that verifies the 
  1106. identical accuracy of two binary to ASCII decimal conversion 
  1107. algorithms in the first part, then goes on to compare their 
  1108. execution speeds.  The time required to execute each 
  1109. algorithm is too short for accurate measurement, therefore, 
  1110. each algorithm is executed 1000 times.  The times reported 
  1111. by the program are the elapsed times for those 1000 
  1112. executions.
  1113.  
  1114. The Get_time Algorithm
  1115.   
  1116.      I think that you shall find the program sufficiently 
  1117. documented, but the get_time subroutine deserves special 
  1118. attention.  As you read about GEMDOS function $20 on pages 
  1119. 117 - 118 of the Internals book, compare the code there to 
  1120. that in program 11, and you will see that I did not find it 
  1121. necessary to save the original value of the supervisor stack 
  1122. pointer.  This is so because the content of D0 was not 
  1123. altered between the two function $20 calls which are 
  1124. required.
  1125.      As you study other GEMDOS functions, you will see one 
  1126. labeled Get Time (GEMDOS function $2C), and you may wonder 
  1127. why that function was not used in this program.  Then, 
  1128. later, you will probable run across the BIOS function 
  1129. Tickcal and wonder what it has to do with time, if anything.  
  1130. Well, the answers are simple.  GEMDOS function $2C has a 
  1131. resolution of 2 seconds, which is too low for accurate 
  1132. execution speed measurement.  As promising as the Tickcal 
  1133. function might appear to be at first sight, it actually does 
  1134. nothing but return the value 20 milliseconds every time you 
  1135. call it.
  1136.      Fortunately, we are able to access an ST system 
  1137. variable at memory location $4BA.  This variable is called 
  1138. _hz_200, and it is described in the Internals book as being 
  1139. the Counter for 200 Hz system clock; in the Peel book it is 
  1140. described as being the Raw 200Hz timer tick.  We gain access 
  1141. to this system variable by invoking GEMDOS function $20 to 
  1142. place the microprocessor in the supervisor mode.  Refer to 
  1143. the subroutine within program 11 for details of my get_time 
  1144. algorithm's operation.
  1145.  
  1146. Program 11. Comparison of two binary to ASCII decimal 
  1147. conversion algorithms.
  1148.  
  1149.  ; Program Name: PRG_3CP.S
  1150.  
  1151.  ; Assembly Instructions:
  1152.  
  1153.  ;    Assemble in PC-relative mode and save with a TOS extension.
  1154.  
  1155.  ; Execution Instructions:
  1156.  
  1157.  ;    Execute from the desktop.  Terminate execution by pressing the Return
  1158.  ; key.
  1159.  
  1160.  ; Function:
  1161.  
  1162.  ;    This program is divided into two parts.  Part 1 verifies that the
  1163.  ; results from two binary to ASCII decimal conversion algorithms are
  1164.  ; identical.  The first conversion algorithm is called the "repeated
  1165.  ; division" method; the number to be converted is repeatedly divided by 10.
  1166.  ; The second algorithm is called the "repeated subtraction" method; powers
  1167.  ; of ten are repeatedly subtracted from the number to be converted.
  1168.  
  1169.  ;    Part 2 compares the two algorithms to determine which is the faster.
  1170.  
  1171.  ;    Part 1 contains three sections.  Section 1 confirms that both algorithms
  1172.  ; yield the same result for a positive number; section 2 confirms identical
  1173.  ; results for a negative number; section 3 confirms identical results for
  1174.  ; the number zero.
  1175.  
  1176.  ; A FEW NOTES:
  1177.  
  1178.  ; 1 - "clr.w  Dn" is one of the fastest ways to clear only the lower word
  1179.  ;     of a data register.
  1180.  
  1181.  ; 2 - The stack used in the program is small enough to permit easy access
  1182.  ;     to its contents via the AssemPro debugger.
  1183.  
  1184.  ; 3 - In the "repeated division" algorithm, a null character must be stored
  1185.  ;     in the array "reversed" for proper operation of the
  1186.  ;     "reversed_to_decimal" loop when the program is executed from AssemPro.
  1187.  ;     This is true because AssemPro will not necessarily clear the array to
  1188.  ;     zeroes.
  1189.  
  1190.  ;     When the program is executed from the desktop, however, the operating
  1191.  ;     system will clear the array.  Therefore, if the program were intended
  1192.  ;     for use such as it is, the instruction which stores the null at the
  1193.  ;     end of the array "reversed" could be eliminated.
  1194.  
  1195.  ;     This is only one of the many types of adjustments
  1196.  ;            which must be made when executing programs from debuggers.
  1197.  
  1198. calculate_program_size:
  1199.  lea        program_end, a0     ; Put "end of program" address in A0.
  1200.  movea.l    4(a7), a1           ; Put "basepage" address in A1.
  1201.  suba.l     a1, a0              ; Subtract basepage address from A0.
  1202.  lea        stack, a7           ; Point A7 to this program's stack.
  1203.  
  1204. return_memory:                  ; Return unused memory to operating system.
  1205.  pea        (a0)                ; Store total program length in stack.
  1206.  pea        (a1)                ; Store basepage address in stack.
  1207.  move.l     #$4A0000, -(sp)     ; Function = m_shrink = GEMDOS $4A.
  1208.  
  1209.  ; NOTE: The above instruction is a combination of two that are often seen
  1210.  ;       in references:
  1211.  
  1212.  ;       move.w     d0, -(sp)   ; Dummy value, can be anything.
  1213.  ;       move.w     #$4a, -(sp) ; Function = m_shrink = GEMDOS $4A.
  1214.  
  1215.  trap       #1                  ; GEMDOS call.
  1216.  lea        $C(sp), sp          ; Reset stack pointer to top of stack.
  1217.  
  1218. mainline:                       ; Marks the beginning of program proper.
  1219.  lea        heading, a0         ; Print heading for program's output.
  1220.  bsr        print_string
  1221.  
  1222.  ;
  1223.  ; PART 1, SECTION 1: Conversion of a positive binary number to ASCII decimal.
  1224.  ;
  1225.  
  1226.  lea        part_1_head, a0     ; Print PART 1 heading.
  1227.  bsr        print_string
  1228.  lea        sect_1_head, a0     ; Print SECTION 1 heading.
  1229.  bsr        print_string
  1230.                                
  1231. repeated_division_1:
  1232.  lea        division_head, a0   ; Print heading for division results.      
  1233.  bsr        print_string       
  1234.  move.l     #2147483647, d1     ; Number to be converted must be in D1.
  1235.  bsr        bin_to_dec_1        ; ASCII decimal string stored in "decimal".
  1236.  lea        decimal, a0         ; Print ASCII decimal string.     
  1237.  bsr        print_string     
  1238.  
  1239.  ; NOTE: Remember, although the number we store in D1 appears to our eyes
  1240.  ;       to be a very familiar decimal number, the computer does not see
  1241.  ;       it that way.  It is the assembler that lets us see things that
  1242.  ;       are palatable to us while we are programming, and which, during
  1243.  ;       assembly, converts that which we like to something the computer
  1244.  ;       likes.  And the computer likes binary.
  1245.  
  1246. repeated_subtraction_1:
  1247.  lea        subtract_head, a0   ; Print heading for subtraction results.
  1248.  bsr        print_string 
  1249.  move.l     #2147483647, d1     ; Number to be converted must be in D1.
  1250.  bsr        bin_to_dec_2        ; ASCII decimal string stored in "decimal". 
  1251.  lea        decimal, a0         ; Print ASCII decimal string.
  1252.  bsr        print_string 
  1253.  bsr        print_newlines
  1254.  
  1255.  ;
  1256.  ; PART 1, SECTION 2: Conversion of a negative binary number to ASCII decimal.
  1257.  ;
  1258.  
  1259.  lea        sect_2_head, a0     ; Print SECTION 2 heading.
  1260.  bsr        print_string
  1261.  
  1262. repeated_division_2:
  1263.  lea        division_head, a0   ; Print heading for division results.      
  1264.  bsr        print_string 
  1265.  move.l     #-7483647, d1      
  1266.  bsr        bin_to_dec_1       
  1267.  lea        decimal, a0        
  1268.  bsr        print_string       
  1269.  
  1270. repeated_subtraction_2:
  1271.  lea        subtract_head, a0   ; Print heading for subtraction results.
  1272.  bsr        print_string
  1273.  move.l     #-7483647, d1 
  1274.  bsr        bin_to_dec_2  
  1275.  lea        decimal, a0
  1276.  bsr        print_string
  1277.  bsr        print_newlines
  1278.  
  1279.  ;
  1280.  ; PART 1, SECTION 3: Conversion of binary number zero to ASCII decimal.
  1281.  ;
  1282.  
  1283.  lea        sect_3_head, a0     ; Print SECTION 3 heading.
  1284.  bsr        print_string
  1285.  
  1286. repeated_division_3:
  1287.  lea        division_head, a0   ; Print heading for division results.      
  1288.  bsr        print_string 
  1289.  move.l     #0, d1         
  1290.  bsr        bin_to_dec_1   
  1291.  lea        decimal, a0    
  1292.  bsr        print_string   
  1293.  
  1294. repeated_subtraction_3:
  1295.  lea        subtract_head, a0   ; Print heading for subtraction results.
  1296.  bsr        print_string
  1297.  move.l     #0, d1           
  1298.  bsr        bin_to_dec_2     
  1299.  lea        decimal, a0      
  1300.  bsr        print_string     
  1301.  bsr        print_newlines
  1302.  
  1303.  ;
  1304.  ; PART 2: Repeated division algorithm versus repeated subtraction algorithm
  1305.  ;         execution speed comparision.  Each algorithm is executed 1000 times.
  1306.  ;
  1307.  
  1308.  lea        part_2_head, a0     ; Print PART 2 heading.
  1309.  bsr        print_string
  1310.  
  1311. repeated_division_method:
  1312.  lea        div_time_head, a0
  1313.  bsr        print_string
  1314.  move.l     #9999, d7           ; D7 is counter for the push loop.
  1315.  bsr        get_time            ; Value of system clock returned in D5.
  1316.  move.l     d5, d6              ; Save time in scratch register.
  1317. division_loop:
  1318.  move.l     #1928374650, d1     ; Number to be converted to ASCII decimal.
  1319.  bsr        bin_to_dec_1 
  1320.  dbra       d7, division_loop   ; Loop 1000 times.
  1321.  bsr        get_time            ; Get current value of system clock.
  1322.  sub.l      d6, d5              ; Subtract beginning value from ending value.
  1323.  mulu       #5, d5              ; Convert to milliseconds.
  1324.  move.l     d5, d1              ; Transfer value for conversion to ASCII decimal.
  1325.  bsr       bin_to_dec_2        ; Convert.
  1326.  lea        decimal, a0         ; Print repeated division algorithm time.
  1327.  bsr.s      print_string
  1328.  lea        units_label, a0     ; Print time label.
  1329.  bsr.s      print_string
  1330.  
  1331. repeated_subtraction_method:
  1332.  lea        sub_time_head, a0
  1333.  bsr.s      print_string
  1334.  move.l     #9999, d7           ; D7 is counter for the push loop.
  1335.  bsr        get_time            ; Value of system clock returned in D5.
  1336.  move.l     d5, d6              ; Save time in scratch register.
  1337. subtraction_loop:
  1338.  move.l     #1928374650, d1     ; Number to be converted to ASCII decimal.
  1339.  bsr        bin_to_dec_2 
  1340.  dbra       d7, subtraction_loop; Loop 1000 times.
  1341.  bsr        get_time            ; Get current value of system clock.
  1342.  sub.l      d6, d5              ; Subtract beginning value from ending value.
  1343.  mulu       #5, d5              ; Convert to milliseconds.
  1344.  move.l     d5, d1              ; Transfer value for conversion to ASCII decimal.
  1345.  bsr        bin_to_dec_2        ; Convert.
  1346.  lea        decimal, a0         ; Print repeated division algorithm time.
  1347.  bsr.s      print_string
  1348.  lea        units_label, a0     ; Print time label.
  1349.  bsr.s      print_string
  1350.  
  1351. wait_for_keypress:
  1352.  move.w     #8, -(sp)           ; Function = c_necin = GEMDOS $8.
  1353.  trap       #1                  ; GEMDOS call.
  1354.  addq.l     #2, sp              ; Reposition stack pointer at top of stack.
  1355.  
  1356. terminate:
  1357.  move.w     #0, -(sp)           ; Function = p_term_old = GEMDOS $0.
  1358.  trap       #1                  ; GEMDOS call.
  1359.  
  1360.  ;
  1361.  ; SUBROUTINES
  1362.  ;
  1363.  
  1364. print_string:                   ; Expects address of string to be in A0.
  1365.  pea        (a0)                ; Push address of string onto stack.
  1366.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  1367.  trap       #1                  ; GEMDOS call
  1368.  addq.l     #6, sp              ; Reposition stack pointer.
  1369.  rts
  1370.  
  1371. print_newlines:                 ; Prints 2 carriage returns and linefeeds.
  1372.  pea        newlines            ; Push address of string onto stack.
  1373.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  1374.  trap       #1                  ; GEMDOS call
  1375.  addq.l     #6, sp
  1376.  rts
  1377.  
  1378.  ; Get_time subroutine.  Returns current value of system clock in D5.
  1379.  
  1380.  ; The get_time subroutine obtains the current value of the system variable
  1381.  ; _hz_200 (memory location $4BA).  This variable is incremented every 200
  1382.  ; hertz, which means that the period between increments is 5 milliseconds
  1383.  ; (1/200 = .005).  In turn, this means that the variable measures time with 
  1384.  ; a resolution of 5 milliseconds.
  1385.  
  1386.  ; Use this subroutine to measure the elapsed time of an event as follows:
  1387.  
  1388.  ; 1. Read and store the content of $4BA at the beginning of the event.
  1389.  ; 2. At the conclusion of the event, read the content of $4BA again.
  1390.  ; 3. Subtract the first value from the second.  This yields the number of
  1391.  ;    5 millisecond intervals which occurred during the event.
  1392.  ; 4. Multiply the difference by 5 to convert the elapsed time to milliseconds.
  1393.  
  1394. get_time:                       ; Get number of 5 msec periods.
  1395.  move.l     #0, -(sp)           ; The zero turns on supervisor mode.
  1396.  move.w     #$20, -(sp)         ; Function = super = GEMDOS $20.
  1397.  trap       #1                  ; Go to supervisor mode.
  1398.  addq.l     #6, sp              ; Supervisor stack pointer returned in D0.
  1399.  move.l     $4BA, d5            ; Copy system time into register D5
  1400.  move.l     d0, -(sp)           ; Restore supervisor stack pointer.
  1401.  move.w     #$20, -(sp)         ; Function = super = GEMDOS $20.
  1402.  trap       #1                  ; Go to user mode.
  1403.  addq.l     #6, sp              ; Reset stack pointer to top of stack.
  1404.  rts
  1405.  
  1406.  ; The binary to ASCII decimal conversion subroutine uses an algorithm
  1407.  ; based on the "repeated division" algorithm discussed in chapter 9 of the
  1408.  ; Ford & Topp book; however, the algorithm used here is not limited to a
  1409.  ; 16-bit binary number.  There is a similar algorithm in the Atari section
  1410.  ; of appendix B in the Skinner book. The divisor is decimal 10.
  1411.  
  1412. bin_to_dec_1:                   ; Converts 32-bit binary number in D1 to
  1413.                                 ; ASCII decimal.
  1414.  lea        decimal, a0         ; Point to beginning of array "decimal".
  1415.  lea        reversed + 10, a1   ; Point to end of array "reversed".
  1416.  move.b     #0, (a1)            ; Put a null at the end of the array.
  1417. _get_sign:
  1418.  tst.l      d1                  ; Is binary number positive, negative or zero?
  1419.  beq.s      _zero_passed        ; Branch if number is 0.
  1420.  bpl.s      _positive           ; Branch if positive.
  1421.  move.b     #$2D, (a0)+         ; Store a minus sign in array decimal.
  1422.  neg.l      d1                  ; Change number from negative to positive.
  1423.  bra.s      _division_loop
  1424. _positive:                      ; Branch to here when number is positive.
  1425.  move.b     #$20, (a0)+         ; Store a space in array decimal.
  1426. _division_loop:
  1427.  move.w     d1, d2              ; Store lower word in temp register D2.
  1428.  clr.w      d1                  ; Clear lower word.  
  1429.  swap       d1                  ; Move higher word to lower word.
  1430.  divu       #10, d1             ; Divide full 32 bits by ten.
  1431.  move.w     d1, d3              ; Store quotient in temp register D3.
  1432.  move.w     d2, d1              ; Combine lower word with remainder.
  1433.  divu       #10, d1             ; Divide full 32 bits by ten.
  1434.  swap       d1                  ; Swap quotient and remainder words.
  1435. _convert_to_ascii:              ; Convert digit to ASCII and store it.
  1436.  addi.b     #$30, d1            ; Convert digit to ASCII.
  1437.  move.b     d1, -(a1)           ; Store the digit in array "reversed".
  1438.  move.w     d3, d1              ; Bring in higher word quotient. 
  1439.  swap       d1                  ; Swap high and low word quotients.
  1440.  tst.l      d1                  ; Is content of D1 zero yet?
  1441.  bne.s      _division_loop      ; Continue until content of D1 is zero.
  1442. reversed_to_decimal:            ; Transfer contents of "reversed" to "decimal".
  1443.  move.b     (a1)+, (a0)+        ; Loop until the null is transfered.
  1444.  bne.s      reversed_to_decimal
  1445.  rts
  1446. _zero_passed:
  1447.  move.b     #$20, (a0)+         ; Store a space in array "decimal".
  1448.  move.b     #$30, (a0)+         ; Store the zero in array "decimal".
  1449.  move.b     #0, (a0)            ; Terminate the decimal string with a null.
  1450.  rts
  1451.  
  1452.  ; Conversion from binary to ASCII decimal using repeated subtraction.
  1453.  ; See documentation in program PRG_3AP.S.
  1454.  
  1455. bin_to_dec_2:                  
  1456.  lea        decimal, a0         ; Put address of array "decimal" in A0.
  1457.  lea        subtrahend, a1      ; Put address of subtrahend table in A1.
  1458.  move.l     (a1)+, d0           ; Put first subtrahend in D0.
  1459.  move.b     #$30, d2            ; Initialize subtractions counter to ASCII zero.
  1460. get_sign:
  1461.  tst.l      d1                  ; Is binary number positive, negative or zero?
  1462.  beq.s      zero_passed         ; Branch if number is 0.
  1463.  bpl.s      positive            ; Branch if positive.
  1464.  move.b     #$2D, (a0)+         ; Store a minus sign in array "decimal".
  1465.  neg.l      d1                  ; Change binary number from neg to pos.
  1466.  bra.s      discard_leading_zeroes
  1467. positive:                       ; Branch to here when number is positive.
  1468.  move.b     #$20, (a0)+         ; Store a space in array decimal.
  1469. discard_leading_zeroes:         ; Subtract subtrahend from minuend.
  1470.  sub.l      d0, d1              ; Loop till difference is positive,
  1471.  bpl.s      subtract            ; indicating that digit is not zero.
  1472.  add.l      d0, d1              ; Restore minuend.
  1473.  move.l     (a1)+, d0           ; Get next subtrahend.
  1474.  bra.s      discard_leading_zeroes
  1475. subtract:
  1476.  addq.b     #1, d2              ; Increment subtractions counter.
  1477.  sub.l      d0, d1              ; Subtract subtrahend from D1.
  1478.  bpl.s      subtract            ; Loop until D1 becomes negative.
  1479.  move.b     d2, (a0)+           ; Store the ASCII digit in array "decimal".
  1480. loop_setup:
  1481.  add.l      d0, d1              ; Restore the minuend.
  1482.  move.b     #$2F, d2            ; Pre-initialize subtractions counter to $30-1.
  1483.  move.l     (a1)+, d0           ; Get next subtrahend.
  1484.  bne.s      subtract            ; Loop back until subtrahend = 0.
  1485.  move.b     #0, (a0)            ; Terminate decimal string with a null.
  1486.  rts
  1487. zero_passed:
  1488.  move.b     #$20, (a0)+         ; Store a space in array "decimal".
  1489.  move.b     d2, (a0)+           ; Store an ASCII zero in array "decimal".
  1490.  move.b     #0, (a0)            ; Terminate ASCII decimal string with a null.
  1491.  rts
  1492.  
  1493.  data
  1494. subtrahend:    dc.l $3B9ACA00,$5F5E100,$989680,$F4240,$186A0,$2710,$3E8
  1495.                dc.l $64,$A,$1,$0
  1496. heading:       dc.b 'PRG_3CP Execution Results',$D,$A,$D,$A,0
  1497. part_1_head:   dc.b '  Part 1: Conversion Verification',$D,$A,$D,$A,0
  1498. sect_1_head:   dc.b '    Section 1: Positive Number Conversion',$D,$A,$D,$A,0
  1499. sect_2_head:   dc.b '    Section 2: Negative Number Conversion',$D,$A,$D,$A,0
  1500. sect_3_head:   dc.b '    Section 3: Converson for Zero',$D,$A,$D,$A,0
  1501. division_head: dc.b '      Decimal value by repeated division method:    ',0
  1502. subtract_head: dc.b $D,$A
  1503.                dc.b '      Decimal value by repeated subtraction method: ',0
  1504. part_2_head:   dc.b '  Part 2: Execution Speed Results',$D,$A,$D,$A,0
  1505. time_head:     dc.b '    Times for 1000 executions of each conversion method.',0
  1506. div_time_head: dc.b '      Elapsed time for repeated division method:    ',0
  1507. sub_time_head: dc.b '      Elapsed time for repeated subtraction method: ',0
  1508. units_label:   dc.b ' milliseconds',$D,$A,0
  1509. newlines:      dc.b $D,$A,$D,$A,0
  1510.  bss
  1511.  align                  ; Align storage on a word boundary.
  1512. reversed:    ds.l    3  ; Temp buffer for the repeated division method.
  1513. decimal:     ds.l    3  ; Output buffer, must be NULL terminated.
  1514.              ds.l   24  ; Program stack, short enough for examination.
  1515. stack:       ds.l    0  ; Address of program stack.
  1516. program_end: ds.l    0  ; Marks the end of program memory.
  1517.  end
  1518.  
  1519.  
  1520. PRG_3CP Execution Results
  1521.  
  1522.   Part 1: Conversion Verification
  1523.  
  1524.     Section 1: Positive Number Conversion
  1525.  
  1526.       Decimal value by repeated division method:     2147483647
  1527.       Decimal value by repeated subtraction method:  2147483647
  1528.  
  1529.     Section 2: Negative Number Conversion
  1530.  
  1531.       Decimal value by repeated division method:    -7483647
  1532.       Decimal value by repeated subtraction method: -7483647
  1533.  
  1534.     Section 3: Converson for Zero
  1535.  
  1536.       Decimal value by repeated division method:     0
  1537.       Decimal value by repeated subtraction method:  0
  1538.  
  1539.   Part 2: Execution Speed Results
  1540.  
  1541.       Elapsed time for repeated division method:     4760 milliseconds
  1542.       Elapsed time for repeated subtraction method:  2440 milliseconds
  1543.  
  1544.  
  1545.      The repeated division algorithm is much slower than the 
  1546. repeated subtraction algorithm just because it uses repeated 
  1547. division.  Each division consumes 140 clock periods, but 
  1548. each subtraction consumes only 8 clock periods.  I should 
  1549. mention that the elapsed time for the repeated division 
  1550. method can be reduced to 4695 milliseconds with the 
  1551. following modifications to the algorithm: insert move.w 
  1552. #10,d4 just before the _get_sign label, then change the two 
  1553. divu #10,d1 instructions to divu d4,d1.
  1554.      With these alterations, each division is 4 clock 
  1555. periods faster.  The speed of the repeated division 
  1556. algorithm can be increased a little more by making the same 
  1557. changes for the ASCII code conversion as was suggested for 
  1558. the repeated subtraction algorithm (and implemented in 
  1559. program 11).  I did not make the alterations discussed in 
  1560. this paragraph because I wanted you to see the repeated 
  1561. division algorithm as it most often appears in references.
  1562.  
  1563. Load and Stay Resident Programs
  1564.   
  1565.      If only because of precedence, we have come to regard a 
  1566. particular type of program execution cycle as normal.  To 
  1567. initiate this cycle, a user performs some action that causes 
  1568. the operating system to load a program into memory and give 
  1569. processor control to the program.  For some finite length of 
  1570. time, the program is in control of some subset of the 
  1571. computer's total capacity.  Eventually, the program 
  1572. accomplishes is designed task and returns processor control 
  1573. to the operating system.  The operating system then removes 
  1574. the program from memory either by actually clearing the part 
  1575. of memory occupied by the program or simply by "forgetting" 
  1576. that it is there.
  1577.      But there is a type of program which does not follow 
  1578. the normal execution cycle.  The execution of programs in 
  1579. this class may be initiated by the operating system, a user 
  1580. or another program.  One characteristic of these programs 
  1581. that differentiates them from normal programs is their 
  1582. length of residency in memory; once loaded, they are not 
  1583. usually removed for the life of the power-up cycle. In 
  1584. addition, their execution need not proceed linearly; that 
  1585. is, once loaded into memory, it may be that only a portion 
  1586. of the program's instructions will be executed immediately 
  1587. and the balance may be executed at a later time, or, as is 
  1588. sometimes the case, a portion of the program may never be 
  1589. executed.
  1590.      Some references refer to this class of programs, in the 
  1591. most general sense, as terminate and stay resident (TSR) 
  1592. programs.  I prefer the more accurate  descriptive load and 
  1593. stay resident (LSR).  The word terminate most naturally 
  1594. connotes end or finish; but, as you shall see, programs of 
  1595. this class need not terminate during the life of the power-
  1596. up cycle.
  1597.  
  1598. GEMDOS Function $31
  1599.   
  1600.      The easiest way to establish a program as type LSR is 
  1601. to exit the program with GEMDOS function $31 instead of 
  1602. function $0.  For a complete discussion of function $31, 
  1603. please refer to pages 121-122 of the Internals book.  In 
  1604. short, this function is used to relinquish processor 
  1605. control, but, simultaneously, it tells the operating system 
  1606. to keep the program in memory in such a manner that the 
  1607. program may seize, or be given, control of the processor at 
  1608. some later time.
  1609.      At the time that function $31 is executed, it is 
  1610. possible, and probable, that the program has not completed, 
  1611. or even initiated its intended function or functions.  Most 
  1612. likely, at some time during the power-up cycle, the program 
  1613. will be given control of the processor so that it can 
  1614. perform the function for which it was designed.  In the same 
  1615. manner that we do not think of the operating system as 
  1616. terminating when it relinquishes control to a program, we 
  1617. need not consider a program to have terminated when it 
  1618. executes function $31.
  1619.      Program 12 illustrates the use of GEMDOS function $31 
  1620. to establish permanent memory residency.  When executed from 
  1621. the desktop, this program will be loaded into memory, where 
  1622. it will remain until the computer is powered down.  At the 
  1623. onset of execution, the program prints its starting address 
  1624. and ending address on the video screen, then waits for a 
  1625. keypress so that you can write them down.
  1626.      After you record the addresses, press the Return key.  
  1627. When the desktop appears, execute ASSEMPRO.PRG and go to the 
  1628. debugger.  Click on the from address button and type the 
  1629. first address (program_start) on the from address parameter 
  1630. line.  The program's first instruction will appear as the 
  1631. first line in the output field.  There you will see the 
  1632. second address (program_end) being loaded into register A3.
  1633.      Use this method of obtaining the location of an LSR 
  1634. program in memory whenever you wish to examine it via the 
  1635. debugger or whenever you want to obtain a disassembly 
  1636. listing of this type of program, especially after certain 
  1637. portions, or the entire program has been executed.  After 
  1638. you gain sufficient knowledge, you will be able to attach 
  1639. appropriate instructions to LSR programs written by others 
  1640. so that you can locate them in memory with the debugger.
  1641.   
  1642. Program 12. Establishing a LSR program with GEMDOS 
  1643. function $31.  Note the warning given.
  1644.  
  1645.  ; Program Name: PRG_4AP.S
  1646.  
  1647.  ; Assembly Instructions:
  1648.  
  1649.  ;    Assemble in PC-relative mode and save with a TOS suffix.
  1650.  
  1651.  ; Program Function:
  1652.  
  1653.  ;    This program simply establishes itself in memory as a Load and Stay
  1654.  ; Resident (LSR) program and prints its location to the video screen.  The
  1655.  ; addresses printed are the program start address (not the basepage address)
  1656.  ; and the program end address.
  1657.  
  1658.  ; Program Purpose:
  1659.  
  1660.  ;    To illustrate the role of GEMDOS function $31 in establishing a
  1661.  ; program as LSR.
  1662.  
  1663.  ; Execution Instructions:
  1664.  
  1665.  ;    Execute the program from the desktop.  When the addresses appear on
  1666.  ; the screen, write them down.  Terminate execution by pressing the Return
  1667.  ; key.  In the AssemPro debugger, go to the first address using the
  1668.  ; "from address" function.  On the first line in the output field, you
  1669.  ; should see the second address (program_end) being loaded into register A3.
  1670.  
  1671.  ;    Once this program has been executed, it will remain in ram memory until
  1672.  ; the computer is rebooted.
  1673.  
  1674.  ; WARNING: If this program or any other LSR program is executed from within
  1675.  ;          the AssemPro debugger, upon exit from AssemPro, the operating
  1676.  ;          system will not be able to clear the program from memory.
  1677.  
  1678.  ;          Because the program will be residing in an area of memory that
  1679.  ;          was controlled by AssemPro, the environment will be corrupted.
  1680.  ;          You can confirm this by trying to reexecute AssemPro after you
  1681.  ;          exit.  You will receive a Bus error message.
  1682.  
  1683.  ;          Furthermore, you will not be able to assemble a program until
  1684.  ;          you reset the system.
  1685.  
  1686.  ;          There is only one thing you can do if you execute a LSR program
  1687.  ;          from within AssemPro; you must reset the system by turning it
  1688.  ;          completely off, then back on.  You can try a warm reset, but
  1689.  ;          all bets are off if you do that. 
  1690.  
  1691. program_start:                  ; Calculate program size and retain result.
  1692.  lea        program_end, a3     ; Fetch address of last memory location occupied
  1693.  movea.l    a3, a4              ; by the program.  Copy into scratch register.
  1694.  suba.l     4(a7), a3           ; Subtract basepage address from program end 
  1695.                                 ; address.  After this, the basepage address
  1696.                                 ; is no longer needed in this program.
  1697. fetch_stack_address:
  1698.  lea        stack, a7
  1699.  
  1700. print_memory_locations:   
  1701.  lea        load_message, a0
  1702.  bsr.s      print_string
  1703.  lea        program_start, a0
  1704.  move.l     a0, d1              ; Transfer to D1 for binary to ASCII
  1705.                                 ; hexadecimal conversion.
  1706.  
  1707.  ; Note: Above, must load address into an address register first, then move
  1708.  ; to the data register for binary to hexadecimal conversion, in order to
  1709.  ; permit PC-relative assembly.
  1710.  
  1711.  bsr.s      bin_to_hex          ; bin_to_hex expects binary number in D1.
  1712.  lea        hexadecimal, a0     ; Print the hexadecimal string.
  1713.  bsr.s      print_string
  1714.  lea        separator, a0       ; Print a separator between addresses.
  1715.  bsr.s      print_string
  1716.  move.l     a4, d1              ; Program end address is in scratch register.          
  1717.  bsr.s      bin_to_hex      
  1718.  lea        hexadecimal, a0
  1719.  bsr.s      print_string
  1720.  bsr.s      print_newline
  1721.  
  1722. wait_for_keypress:              ; Give time to write down memory addresses.
  1723.  move.w     #8, -(sp)           ; Function = c_necin = GEMDOS $8.
  1724.  trap       #1                  ; GEMDOS call.
  1725.  addq.l     #2, sp
  1726.  
  1727.  ; Note: GEMDOS function $31 doesn't need the basepage address.
  1728.  
  1729. relinquish_processor_control:   ; Maintain memory residency.
  1730.  move.w    #0, -(sp)            ; See page 121 of Internals book.
  1731.  move.l    a3, -(sp)            ; Program size.
  1732.  move.w    #$31, -(sp)          ; Function = ptermres = GEMDOS $31.
  1733.  trap      #1
  1734.  
  1735.  ; 
  1736.  ; SUBROUTINES
  1737.  ;
  1738.  
  1739.  ; The binary to ASCII hexadecimal conversion routine expects a number to be
  1740.  ; passed as a longword in register D1.  Beginning with the most significant
  1741.  ; nibble (a nibble = four bits), each nibble is converted to its ASCII
  1742.  ; hexadecimal equivalent and stored in "hexadecimal", a null terminated
  1743.  ; buffer.  Maximum size of the binary number is 32 bits = 8 nibbles.
  1744.  
  1745.  ; The algorithm discards leading zeroes.
  1746.  
  1747.  ; The conversion from binary nibble to hex digit is accomplished by 
  1748.  ; extracting the character in the hex table that is located at the position
  1749.  ; defined by the decimal value of the nibble.  For example, if the nibble
  1750.  ; is "1111", the decimal value is 15; the 15th element of the hex table is
  1751.  ; the letter F.  The location in the table is specified by an offset from
  1752.  ; the address of the first character of the table, which is stored in A1.
  1753.  ; The value of the offset is stored in register D0.  The addressing mode
  1754.  ; used to locate the appropriate table entry is "address register indirect
  1755.  ; with offset".  
  1756.  
  1757. bin_to_hex:                      ; Expects binary number in D1.   
  1758.  lea        hexadecimal, a0      ; A0 is pointer to array "hexadecimal".
  1759.  tst.l      d1                   ; Test for contents = 0.
  1760.  beq.s      zero_passed          ; Branch if number is 0.
  1761.  lea        hex_table, a1        ; A1 is pointer to array "hex_table".
  1762.  lea        hex_table, a1        ; A1 is pointer to array "hex_table".
  1763.  moveq      #7, d2               ; D2 is the loop counter for 8 nibbles.
  1764.  
  1765. discard_leading_zeroes: 
  1766.  rol.l      #4, d1               ; Rotate most significant nibble to the
  1767.                                  ; least significant nibble position.
  1768.  move.b     d1, d0               ; Copy least significant byte of D1 to D0.
  1769.  andi.b     #$F, d0              ; Mask out most significant nibble of D0.
  1770.  bne.s      store_digit          ; Branch and store if not leading zero.
  1771.  dbra       d2, discard_leading_zeroes
  1772. continue:
  1773.  rol.l      #4, d1               ; Rotate most significant nibble.
  1774.  move.b     d1, d0               ; Copy least significant byte of D1 to D0.
  1775.  andi.b     #$F, d0              ; Mask out most significant nibble of D0.
  1776. store_digit:
  1777.  move.b     0(a1,d0.w), (a0)+    ; Store ASCII hexadecimal digit in buffer.
  1778.  dbra       d2, continue         ; Continue looping until D2 = -1.
  1779.  move.b     #0, (a0)             ; Terminate hexadecimal string with a null.
  1780.  rts
  1781. zero_passed:
  1782.  move.b     #$30, (a0)+         ; Store an ASCII zero in "hexadecimal".
  1783.  move.b     #0, (a0)            ; Terminate ASCII hexadecimal string with null.
  1784.  lea        hexadecimal, a0
  1785.  rts
  1786.  
  1787. print_string:                   ; Expects address of string to be in A0.
  1788.  pea        (a0)                ; Push address of string onto stack.
  1789.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  1790.  trap       #1                  ; GEMDOS call
  1791.  addq.l     #6, sp              ; Reset stack pointer to top of stack.
  1792.  rts
  1793.  
  1794. print_newline:                  ; Prints a carriage return and linefeed.
  1795.  pea        newline             ; Push address of string onto stack.
  1796.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  1797.  trap       #1                  ; GEMDOS call
  1798.  addq.l     #6, sp
  1799.  rts
  1800.  
  1801.  data
  1802. hex_table:       dc.b  '0123456789ABCDEF'
  1803. newline:         dc.b  $D,$A,0
  1804. load_message:    dc.b  'Installing PRG_4AP between hex addresses: ',0
  1805. separator:       dc.b  ' - ',0
  1806.  align
  1807.  bss
  1808. hexadecimal:     ds.l  3   ; Output buffer.  Must be NULL terminated.
  1809.                  ds.l 16
  1810. stack:           ds.l  1
  1811. program_end:     ds.l  0
  1812.  end
  1813.  
  1814.  
  1815. Program 12 Execution Results: The addresses you see will probably be very
  1816. different.
  1817.  
  1818.    Installing PRG_4AP between hex addresses: 1CB10 - 1CC56
  1819.   
  1820.  
  1821.      As you can see, a new number conversion algorithm is 
  1822. introduced in program 12.  The addresses printed by the 
  1823. program must be shown in ASCII hexadecimal so that they 
  1824. match the system in which addresses are shown in the 
  1825. debugger output field.  Since the conversion from binary to 
  1826. hexadecimal is so simple the algorithm is not at all 
  1827. complex.
  1828.      The binary number passed to the subroutine is composed 
  1829. of eight nibbles, each nibble representing a four binary 
  1830. digit hexadecimal digit.  Beginning with the most 
  1831. significant nibble, each nibble is rotated, in turn, to the 
  1832. least significant position and transferred to another 
  1833. register as the least significant nibble of a byte of data.  
  1834. The least significant byte of the new register is masked 
  1835. with $0F so that it can be used to fetch the least 
  1836. significant nibble's ASCII character from an array of 
  1837. hexadecimal characters.  Masking is required because the 
  1838. smallest portion of data that can be transferred from one 
  1839. data register to another is one byte.  But the upper nibble 
  1840. of that byte corrupts the element of data needed to access 
  1841. characters in the table.  Therefore, we render that upper 
  1842. nibble impotent by anding it with 0.
  1843.      You will find similar algorithms in many of the 
  1844. references.  These algorithms will also show you how to use 
  1845. the movem.l instruction to save the content of registers 
  1846. used by the subroutine whenever you find that to be 
  1847. desirable.  I try to avoid that by judicious register 
  1848. control and variable assignments in my programs.
  1849.  
  1850. Confirming the Validity of the Conversion Algorithm
  1851.  
  1852.      The limited use of the binary to ASCII hexadecimal 
  1853. algorithm in program 12 did not prove its validity with a 
  1854. variety of binary values.  Program 14 was written to 
  1855. exercise the algorithm to at least some pertinent degree of 
  1856. confidence.  But program 14 will not function until program 
  1857. 13 is installed as a LSR program.  Therefore, program 14 
  1858. will follow program 13.
  1859.      Program 13 is designed to provide user traps for 
  1860. algorithms that will be used in many of the programs to be 
  1861. introduced.  Invoking traps for the most often used routines 
  1862. will permit those programs to be smaller and less intricate.  
  1863. Some execution speed will be sacrificed, but the advantages 
  1864. gained compensate for the loss.  In your own programs you 
  1865. will have to decide when the compromises are acceptable.
  1866.  
  1867. Program 13. Using GEMDOS Function $31 to Install Custom 
  1868. Traps.
  1869.  
  1870.  ; Program Name: TRAPS.S
  1871.  ; Version 1.005
  1872.  
  1873.  ; Assembly Instructions:
  1874.  
  1875.  ;    Assemble in PC-relative mode and save with a PRG extension.
  1876.  
  1877.  ; Program Function:
  1878.  
  1879.  ;    This is a LSR program that establishes user defined traps.  It may be
  1880.  ; executed from the desktop, but you may prefer to copy it to the AUTO
  1881.  ; folder of your boot partition or floppy disk so that it will execute
  1882.  ; automatically during boot.
  1883.  
  1884.  ; NOTE: If the program has been loaded into memory during boot, the
  1885.  ;       operating system will not let you remove it from the AUTO folder.
  1886.  
  1887.  ;       After an attempt is made to remove the file, an Alert box will
  1888.  ; appear with the message:
  1889.  
  1890.  ;       !  An item with this name already exists in the
  1891.  ;          directory, or this item is set to Read-only status.
  1892.  
  1893.  ;    To remove a program from the AUTO folder after this message appears,
  1894.  ; click once on the file's icon, then select Show Info under the File menu.
  1895.  ; Change the filename's extension from PRG to PRX.  The next time the
  1896.  ; system is booted you will be able to remove the file.
  1897.  
  1898.  ;    The best method of dealing with programs in the AUTO folder and with
  1899.  ; desk accessories is via a program that permits you to select or deselect
  1900.  ; them from a displayed list at boot.  I use MichTron's STSELECT, which
  1901.  ; is one of 21 utilities in their STuff package.  When you deselect an AUTO
  1902.  ; folder program, STSELECT changes the program's suffix to PRX.  The
  1903.  ; suffix change prevents the program from being executed until its suffix
  1904.  ; is altered manually, or via STSELECT, to PRG.
  1905.  
  1906.  ;    The STSELECT utility is especially useful when you are not sure that
  1907.  ; a program in the AUTO folder or a desk accessory will execute properly.
  1908.  ; A malfunction in one of these programs at boot will prevent the system
  1909.  ; from booting, but will not prevent it from trying to boot, thereby putting
  1910.  ; the system in an infinite loop from which it cannot escape.  With STSELECT
  1911.  ; you won't have to worry about that problem.  If one of the programs disrupts
  1912.  ; the boot process, simply deselect it when STSELECT displays its list.
  1913.  
  1914.  ;    The custom traps are installed so that the algorithms here will not
  1915.  ; have to be repeated in all of the programs in the book that use them, not
  1916.  ; because they offer any other advantages.  I want you to know this because,
  1917.  ; in general, I prefer in-line code to the invocation of traps, except when
  1918.  ; they offer a clear cut speed advantage to in-line code, as does the
  1919.  ; custom trap #0.  This trap is a significant improvement over GEMDOS $20
  1920.  ; when forcing the processor into the supervisor mode.
  1921.  
  1922. program_start:                  ; Calculate program size and retain result.
  1923.  lea        program_end, a3     ; Fetch program end address.
  1924.  suba.l     4(a7), a3           ; Subtract basepage address.
  1925.  
  1926. enter_supervisor_mode:
  1927.  move.l     #0, -(sp)           ; The zero turns on supervisor mode.
  1928.  move.w     #$20, -(sp)         ; Function = super = GEMDOS $20.
  1929.  trap       #1                  ; Go to supervisor mode.
  1930.  addq.l     #6, sp              ; Supervisor stack pointer (SSP) returned in D0.
  1931.  movea.l    d0, a5              ; Save SSP in scratch register.
  1932.  
  1933.  ; NOTE: Because the processor is now in supervisor mode, the system trap
  1934.  ;       vectors may be referenced directly; that is, loading the starting
  1935.  ;       value $80 in an address register and referencing all trap vectors
  1936.  ;       as an offset to the starting value using "address register indirect
  1937.  ;       with displacement addressing" is not really necessary.  I am doing
  1938.  ;       that only to show how it is done when it is necessary.  A faster
  1939.  ;       method is shown in the program CUSTOM.S.
  1940.  
  1941. install_trap_0_routine:         ; Trap #0 = set supervisor mode.
  1942.  lea        $80, a0             ; Fetch trap #0 vector address.
  1943.  lea        trap_0_routine, a1  ; Fetch custom trap #0 vector.
  1944.  move.l     a1, (a0)            ; Store custom trap #0 vector.
  1945.  
  1946. install_trap_3_routine:         ; Trap #3 = get time (contents of $4BA).
  1947.  lea        trap_3_routine, a1  ; Fetch custom trap #3 vector.
  1948.  move.l     a1, $C(a0)          ; Store custom trap #3 vector.
  1949.  
  1950. install_trap_4_routine:         ; Trap #4 = convert binary number in D1 to
  1951.  lea        trap_4_routine, a1  ; ASCII decimal, suppressing leading zeroes.
  1952.  move.l     a1, $10(a0)         ; Returns address of decimal string in A0.
  1953.  
  1954. install_trap_5_routine:         ; Trap #5 = convert binary number in D1 to
  1955.  lea        trap_5_routine, a1  ; ASCII hexadecimal, suppressing leading zeroes.
  1956.  move.l     a1, $14(a0)         ; Returns address of hexadecimal string in A0.
  1957.  
  1958. install_trap_6_routine:         ; Trap #6 accepts a program's end address in
  1959.  lea        trap_6_routine, a1  ; A0 and basepage address in A1.  It computes
  1960.  move.l     a1, $18(a0)         ; the program size and returns unused memory.
  1961.  
  1962. install_trap_7_routine:         ; Trap #7 = convert binary number in D1 to
  1963.  lea        trap_7_routine, a1  ; ASCII hexadecimal, retaining leading zeroes.
  1964.  move.l     a1, $1C(a0)         ; Returns address of hexadecimal string in A0.
  1965.  
  1966. install_trap_8_routine:         ; Terminate with GEMDOS $0 after a keypress
  1967.  lea        trap_8_routine, a1  ; if program was not spawned by SPEEDTST.TTP,
  1968.  move.l     a1, $20(a0)         ; else terminate with GEMDOS $4C.
  1969.  
  1970. enter_user_mode:
  1971.  pea        (a5)                ; Restore supervisor stack pointer.
  1972.  move.w     #$20, -(sp)         ; Function = super = GEMDOS $20.
  1973.  trap       #1                  ; Go to user mode.
  1974.  addq.l     #6, sp              ; Reset stack pointer to top of stack. 
  1975.  
  1976. relinquish_processor_control:   ; Maintain memory residency.
  1977.  move.w    #0, -(sp)            ; See page 121 of Internals book.
  1978.  move.l    a3, -(sp)            ; Program size.
  1979.  move.w    #$31, -(sp)          ; Function = ptermres = GEMDOS $31.
  1980.  trap      #1
  1981.  
  1982. trap_0_routine:                 ; Sets bit 13 of status register.
  1983.  
  1984.  ; When trap #0 is invoked, the CPU pushes the calling program's return
  1985.  ; address onto the supervisor stack, then it pushes the calling program's
  1986.  ; status register contents onto the supervisor stack.
  1987.  
  1988.  ; This custom trap #0 handler sets bit #13 of the calling program's status
  1989.  ; register.  The bset instruction can be used to set a bit in the first
  1990.  ; byte of the word on the top of the stack.  Bit #13 of the status register
  1991.  ; is bit #5 of that byte.  Note: to set a bit means to make it equal to 1.
  1992.  
  1993.  ; When the rte instruction is executed, the CPU will return to the calling
  1994.  ; program with the altered copy of its status register, and the calling
  1995.  ; program will now be executing in supervisor mode.
  1996.  
  1997.  ; Register A7 will contain the address of the supervisor stack.  The calling
  1998.  ; program now has complete control of the system.  But a decision about
  1999.  ; which stack is to be used must be made. 
  2000.  
  2001.  ; The calling program can choose to continue with A7 pointing to the 
  2002.  ; supervisor stack, or it can save the contents of A7 = SSP and load the
  2003.  ; address of a user stack into A7.  Processing can continue in that manner
  2004.  ; until it is necessary to return to user mode.  The return to user mode
  2005.  ; can be accomplished by reloading A7 with the SSP, then by resetting bit
  2006.  ; #13 of the status register.  Note: resetting a bit means to make it equal
  2007.  ; to 0.
  2008.  
  2009.  bset       #5, (sp)            ; Places calling program in supervisor mode.
  2010.  rte                           
  2011.  
  2012. trap_3_routine:                 ; Returns contents of $4BA in D0.
  2013.  
  2014.  ; The get_time subroutine obtains the current value of the system variable
  2015.  ; _hz_200 (memory location $4BA).  This variable is incremented every 200
  2016.  ; hertz, which means that the period between increments is 5 milliseconds
  2017.  ; (1/200 = .005).  In turn, this means that the variable measures time with 
  2018.  ; a resolution of 5 milliseconds.
  2019.  
  2020.  ; Use this subroutine to measure the elapsed time of an event as follows:
  2021.  
  2022.  ; 1. Read and store the content of $4BA at the beginning of the event.
  2023.  ; 2. At the conclusion of the event, read the content of $4BA again.
  2024.  ; 3. Subtract the first value from the second.  This yields the number of
  2025.  ;    5 millisecond intervals which occurred during the event.
  2026.  ; 4. Multiply the difference by 5 to convert the elapsed time to milliseconds.
  2027.  
  2028.  move.l     $4BA, d0
  2029.  rte
  2030.  
  2031. trap_4_routine:                 ; Binary to ASCII decimal conversion.
  2032.  
  2033.  ; This is a binary to ASCII decimal conversion subroutine.  It converts a
  2034.  ; 32-bit binary number in register D1 to an ASCII string that is stored in
  2035.  ; an array called "decimal".
  2036.  
  2037.  ; The subroutine uses an algorithm based on "repeated subtraction".  The
  2038.  ; subtrahends are powers of ten which range from 10^1 to 10^9.  These values
  2039.  ; were chosen to accommodate up to ten digit integers.  The binary value
  2040.  ; passed to the subroutine is the initial current minuend.  After a decimal
  2041.  ; digit is extracted from a current minuend, a new current minuend is
  2042.  ; produced.
  2043.  
  2044.  ; During the subtraction process, the minuend is gradually reduced to a
  2045.  ; negative number.  The number of times that each power of ten can be
  2046.  ; subtracted from a current minuend before the minuend becomes negative
  2047.  ; yields a significant digit for that power of ten.
  2048.  
  2049.  ; After the current minuend becomes negative a "new" minuend is formed by
  2050.  ; adding the current subtrahend to the negative value.  The process 
  2051.  ; continues until the current subtrahend is 0.
  2052.  
  2053.  ; Register D2 is the subtractions counter.  In it is accumulated the number
  2054.  ; of times that a subtrahend can be subtracted from a minuend before the
  2055.  ; minuend becomes negative.  At the start of the subroutine D2 is initialized
  2056.  ; to the ASCII code for decimal zero, therefore, as the first digit is
  2057.  ; extracted, it is the digit's ASCII code which is accumulated in D2.
  2058.  
  2059.  ; But observe that D2 is initialized to $2F in the section of the routine
  2060.  ; labeled "loop_setup".  The reason: after registers are initialized in the
  2061.  ; loop_setup section, execution branches to the section labeled "subtract".
  2062.  ; The first instruction in that section adds 1 to the value in D2.
  2063.  
  2064.  ; I chose to add 1 to the subtractions counter at that particular location
  2065.  ; because it was a convenient point in the algorithm to do so upon exit from
  2066.  ; the "discard leading zeroes" loop.  Upon exit from that loop, a valid
  2067.  ; subtraction has already been performed, therefore, it must be accumulated.
  2068.  ; Since D2 contains the initialized value $30 at that point in the execution,
  2069.  ; adding 1 puts the correct count in D2.
  2070.  
  2071.  ; Because I want to branch to the same section upon exit from the loop setup
  2072.  ; section, I must pre-initialize D2 to $2F, which is one less than $30.
  2073.  ; Then, after 1 is added to D2 via the first instruction in the subtract
  2074.  ; section, the subtractions counter is fully initialized to $30, as it
  2075.  ; should be.
  2076.  
  2077.  ; As each decimal digit is extracted from D1, its ASCII code is accumulated
  2078.  ; as part of a string in the array called "decimal".
  2079.  
  2080.  ; The algorithm discards leading zeroes using a loop that subtracts
  2081.  ; subtrahends from the number in D1, beginning with the largest, until a
  2082.  ; subtrahend which does immediately yield a negative difference is detected.
  2083.  
  2084. bin_to_dec_2:                  
  2085.  lea        decimal, a0         ; Put address of array "decimal" in A0.
  2086.  lea        subtrahend, a1      ; Put address of subtrahend table in A1.
  2087.  move.l     (a1)+, d0           ; Put first subtrahend in D0.
  2088.  move.b     #$30, d2            ; Initialize subtractions counter to ASCII zero.
  2089. get_sign:
  2090.  tst.l      d1                  ; Is binary number positive, negative or zero?
  2091.  beq.s      zero_passed         ; Branch if number is 0.
  2092.  bpl.s      positive            ; Branch if positive.
  2093.  move.b     #$2D, (a0)+         ; Store a minus sign in array "decimal".
  2094.  neg.l      d1                  ; Change binary number from neg to pos.
  2095.  bra.s      discard_leading_zeroes
  2096. positive:                       ; Branch to here when number is positive.
  2097.  move.b     #$20, (a0)+         ; Store a space in array decimal.
  2098. discard_leading_zeroes:         ; Subtract subtrahend from minuend.
  2099.  sub.l      d0, d1              ; Loop till difference is positive,
  2100.  bpl.s      subtract            ; indicating that digit is not zero.
  2101.  add.l      d0, d1              ; Restore minuend.
  2102.  move.l     (a1)+, d0           ; Get next subtrahend.
  2103.  bra.s      discard_leading_zeroes
  2104. subtract:
  2105.  addq.b     #1, d2              ; Increment subtractions counter.
  2106.  sub.l      d0, d1              ; Subtract subtrahend from D1.
  2107.  bpl.s      subtract            ; Loop until D1 becomes negative.
  2108.  move.b     d2, (a0)+           ; Store the ASCII digit in array "decimal".
  2109. loop_setup:
  2110.  add.l      d0, d1              ; Restore the minuend.
  2111.  move.b     #$2F, d2            ; Pre-initialize subtractions counter to $30-1.
  2112.  move.l     (a1)+, d0           ; Get next subtrahend.
  2113.  bne.s      subtract            ; Loop back until subtrahend = 0.
  2114.  move.b     #0, (a0)            ; Terminate decimal string with a null.
  2115.  lea        decimal, a0
  2116.  rte
  2117. zero_passed:
  2118.  move.b     #$20, (a0)+         ; Store a space in array "decimal".
  2119.  move.b     d2, (a0)+           ; Store an ASCII zero in array "decimal".
  2120.  move.b     #0, (a0)            ; Terminate ASCII decimal string with a null.
  2121.  lea        decimal, a0
  2122.  rte
  2123.  
  2124. trap_5_routine:
  2125.  
  2126.  ; The binary to ASCII hexadecimal conversion routine expects a number to be
  2127.  ; passed as a longword in register D1.  Beginning with the most significant
  2128.  ; nibble (a nibble = four bits), each nibble is converted to its ASCII
  2129.  ; hexadecimal equivalent and stored in "hexadecimal", a null terminated
  2130.  ; buffer.  Maximum size of the binary number is 32 bits = 8 nibbles.
  2131.  
  2132.  ; The algorithm discards leading zeroes.
  2133.  
  2134.  ; The conversion from binary nibble to hex digit is accomplished by 
  2135.  ; extracting the character in the hex table that is located at the position
  2136.  ; defined by the decimal value of the nibble.  For example, if the nibble
  2137.  ; is "1111", the decimal value is 15; the 15th element of the hex table is
  2138.  ; the letter F.  The location in the table is specified by an offset from
  2139.  ; the address of the first character of the table, which is stored in A1.
  2140.  ; The value of the offset is stored in register D0.  The addressing mode
  2141.  ; used to locate the appropriate table entry is "address register indirect
  2142.  ; with offset".
  2143.  
  2144. bin_to_hex:                     
  2145.  lea        hexadecimal, a0      ; A0 is pointer to array "hexadecimal".
  2146.  tst.l      d1                   ; Test for contents = 0.
  2147.  beq.s      _zero_passed         ; Branch if number is 0.
  2148.  lea        hex_table, a1        ; A1 is pointer to array "hex_table".
  2149.  moveq      #7, d2               ; D2 is the loop counter for 8 nibbles.
  2150. omit_leading_zeroes:
  2151.  rol.l      #4, d1               ; Rotate most significant nibble to the
  2152.                                  ; least significant nibble position.
  2153.  move.b     d1, d0               ; Copy least significant byte of D1 to D0.
  2154.  andi.b     #$F, d0              ; Mask out most significant nibble of D0.
  2155.  bne.s      store_digit          ; Branch if digit is not zero.
  2156.  dbra       d2, omit_leading_zeroes
  2157. continue:
  2158.  rol.l      #4, d1               ; Rotate most significant nibble.
  2159.  move.b     d1, d0               ; Copy least significant byte of D1 to D0.
  2160.  andi.b     #$F, d0              ; Mask out most significant nibble of D0.
  2161. store_digit:
  2162.  move.b     0(a1,d0.w), (a0)+    ; Store ASCII hexadecimal digit in buffer.
  2163.  dbra       d2, continue         ; Continue looping until D2 = -1.
  2164.  move.b     #0, (a0)             ; Terminate hexadecimal string with a null.
  2165.  lea        hexadecimal, a0
  2166.  rte
  2167. _zero_passed:
  2168.  move.b     #$30, (a0)+         ; Store an ASCII zero in "hexadecimal".
  2169.  move.b     #0, (a0)            ; Terminate ASCII hexadecimal string with null.
  2170.  lea        hexadecimal, a0
  2171.  rte
  2172.  
  2173. trap_6_routine:
  2174.  
  2175.  ; Expects program_end address in A0 and basepage address in A1.
  2176.  
  2177.  ; This routine does several things.  First, using the values passed in
  2178.  ; registers A0 and A1, it calculates the invoking program's size and
  2179.  ; returns excess memory to the operating system.
  2180.  
  2181.  ; Then it checks to see if the process invoking trap #6 was spawned.  If
  2182.  ; it was, then a variable is set to true; if not, then the variable is set
  2183.  ; to false.  In addition, if the process was spawned, the after_load time
  2184.  ; is saved in a variable so that it can be returned by trap #8.
  2185.  
  2186. calculate_program_size:
  2187.  suba.l     a1, a0             
  2188. prepare_stack_to_return_unused_memory:
  2189.  pea        (a0)                 ; Push program length.
  2190.  pea        (a1)                 ; Push basepage address.
  2191.  move.l     #$4A0000, -(sp)      ; Function = m_shrink = GEMDOS $4A.
  2192.  
  2193. spawned_test:
  2194.  lea        spawned, a0          ; Fetch boolean variable's address.
  2195.  move.l     $2C(a1), a1          ; Fetch environmental string address.
  2196.  cmpi.l     #'TERM', (a1)        ; Compare environmental string to "TERM".
  2197.  seq        (a0)                 ; Set spawned to FF if program was spawned.
  2198.  bne.s      return_memory        ; Branch if "TERM" string not present.
  2199. save_after_load_time:
  2200.  lea        after_load_time, a0  ; Fetch variable's address. 
  2201.  move.w     d0, (a0)
  2202. return_memory:
  2203.  trap       #1                   ; Invoke GEMDOS exception.
  2204.  lea        $C(a7), sp           ; Reset stack pointer to top of stack.
  2205.  rte
  2206.  
  2207. trap_7_routine:
  2208.  
  2209.  ; The binary to ASCII hexadecimal conversion routine expects a number to be
  2210.  ; passed as a longword in register D1.  Beginning with the most significant
  2211.  ; nibble (a nibble = four bits), each nibble is converted to its ASCII
  2212.  ; hexadecimal equivalent and stored in "hexadecimal", a null terminated
  2213.  ; buffer.  The leading zeroes are retained in this routine.
  2214.  
  2215. bin_to_hex_with_zeroes:
  2216.  lea        hexadecimal, a0      ; A0 is pointer to array "hexadecimal".
  2217.  tst.l      d1                   ; Test for contents = 0.
  2218.  beq.s      _zero__passed        ; Branch if number is 0.
  2219.  lea        hex_table, a1        ; A1 is pointer to array "hex_table".
  2220.  moveq      #7, d2               ; D2 is the loop counter.
  2221.  moveq      #0, d0               ; D0 is not zero.  That's proved in a later
  2222.                                  ; program, so it must be cleared before use.
  2223. rotate_and_convert:
  2224.  rol.l      #4, d1               ; Rotate most significant nibble to the
  2225.                                  ; least significant nibble position.
  2226.  move.b     d1, d0               ; Copy least significant byte of D1 to D0.
  2227.  andi.b     #$F, d0              ; Mask out most significant nibble of D0.
  2228.  move.b     0(a1,d0.w), (a0)+    ; Store ASCII hexadecimal digit in buffer.
  2229.  dbra       d2, rotate_and_convert
  2230.  move.b     #0, (a0)             ; Terminate hexadecimal string with a null.
  2231.  lea        hexadecimal, a0      ; Return address of hex string in A0.
  2232.  rte
  2233. _zero__passed:
  2234.  move.b     #$30, (a0)+          ; Store an ASCII zero in "hexadecimal".
  2235.  move.b     #0, (a0)             ; Terminate ASCII hexadecimal string with null.
  2236.  lea        hexadecimal, a0
  2237.  rte
  2238.  
  2239. trap_8_routine:
  2240.  
  2241.  ; When custom trap #8 is invoked by a program, the state of the boolean
  2242.  ; variable "spawned" is tested.  If the state is true (equal to FF), the
  2243.  ; program invoking trap #8 is terminated with GEMDOS function $4C and the
  2244.  ; after-load time, which was saved by custom trap #6, is returned to the
  2245.  ; parent program in D0.
  2246.  
  2247.  ; If the state of "spawned" is false, GEMDOS function $8 is executed so
  2248.  ; that execution of the spawned program will pause for a keypress.  When
  2249.  ; the keypress is received, GEMDOS function $0 is executed.
  2250.  
  2251.  ; In this manner, custom trap #8, working in conjunction with custom trap
  2252.  ; #6, eliminates the "wait for keypress" algorithm automatically when a
  2253.  ; program is spawned by one of the speed testing programs.  This prevents
  2254.  ; the spawned program's computed execution time from being corrupted with
  2255.  ; a time period that involves a wait for keyboard input.
  2256.  
  2257.  lea        spawned, a0          ; Fetch address of variable.
  2258.  tst.b      (a0)                 ; Spawned test.
  2259.  beq.s      not_spawned          ; Branch if program not spawned.
  2260.  lea        after_load_time, a0  ; Pass after-load time to parent.
  2261.  move.w     (a0), -(sp)          ; Push after-load time.
  2262.  move.w     #$4C, -(sp)          ; Function = p_term = GEMDOS $4C.
  2263.  trap       #1                   ; Terminate and return to parent.
  2264. not_spawned:
  2265.  move.w     #8, -(sp)            ; Function = c_necin = GEMDOS $8.
  2266.  trap       #1                   ; GEMDOS call.
  2267.  addq.l     #2, sp               ; Reposition stack pointer at top of stack.
  2268. terminate:
  2269.  move.w     #0, -(sp)            ; Function = p_term_old = GEMDOS $0.
  2270.  trap       #1                   ; Terminate and return to desktop.
  2271.  
  2272.  data
  2273. subtrahend:       dc.l   $3B9ACA00,$5F5E100,$989680,$F4240,$186A0,$2710
  2274.                   dc.l   $3E8,$64,$A,$1,$0
  2275. hex_table:        dc.b   '0123456789ABCDEF'
  2276. spawned:          dc.b   $0
  2277.  bss
  2278.  align                       ; Align storage on a word boundary.
  2279. after_load_time:  ds.w   1   ; Spawned program's after load time. 
  2280. decimal:          ds.l   3   ; Output buffer.  Must be NULL terminated.
  2281. hexadecimal:      ds.l   3   ; Output buffer.  Must be NULL terminated.
  2282. program_end:      ds.l   0
  2283.  end      
  2284.  
  2285.  
  2286. Program 14. A program to test the validity of the binary to 
  2287. hexadecimal algorithm.  Simultaneously, this program 
  2288. confirms that the algorithm has been installed as a custom 
  2289. trap by program 13.
  2290.  
  2291.  ; Program Name: HEX_TEST.S
  2292.  
  2293.  ; Assembly Instructions:
  2294.  
  2295.  ;    Assemble in PC-relative mode and save with a TOS suffix.
  2296.  
  2297.  ; Program Function:
  2298.  
  2299.  ;    Used to verify the accuracy of the binary to ASCII hexadecimal
  2300.  ; algorithm used in PRG_4AP.TOS and TRAPS.PRG.  The hexadecimal algorithm
  2301.  ; is invoked with a trap #5 instruction.
  2302.  
  2303.  ; Execution Instructions:
  2304.  
  2305.  ;    Execute from the desktop.  Compare the inputs to the conversion
  2306.  ; algorithm to its outputs.  Press the Return key to terminate the program.
  2307.  
  2308.  ; NOTE: The custom traps must be resident when this program is executed.
  2309.  ;       This means that program TRAPS.PRG must be executed from the AUTO
  2310.  ;       folder or from the desktop before program HEX_TEST.TOS is executed.
  2311.  
  2312. release_excess_memory:
  2313.  lea        program_end, a0     ; Put "end of program" address in A0.
  2314.  movea.l    4(a7), a1           ; Put "basepage" address in A1.
  2315.  trap       #6                  ; Calculate program size and release memory.
  2316.  lea        stack, a7           ; Point A7 to this program's stack.
  2317.  
  2318. mainline:                       ; Marks the beginning of program proper.
  2319.  lea        heading, a0         ; Print heading for program's output.
  2320.  bsr.s      print_string
  2321.  
  2322. test_1:
  2323.  lea        string_1, a3
  2324.  lea        value_1, a4
  2325.  bsr.s      test_routine
  2326.  
  2327. test_2:
  2328.  lea        string_2, a3
  2329.  lea        value_2, a4
  2330.  bsr.s      test_routine
  2331.  
  2332. test_3:
  2333.  lea        string_3, a3
  2334.  lea        value_3, a4
  2335.  bsr.s      test_routine
  2336.  
  2337. test_4:
  2338.  lea        string_4, a3
  2339.  lea        value_4, a4
  2340.  bsr.s      test_routine
  2341.  
  2342. wait_for_keypress:
  2343.  move.w     #8, -(sp)           ; Function = c_necin = GEMDOS $8.
  2344.  trap       #1                  ; GEMDOS call.
  2345.  addq.l     #2, sp              ; Reposition stack pointer at top of stack.
  2346.  
  2347. terminate:
  2348.  move.w     #0, -(sp)           ; Function = p_term_old = GEMDOS $0.
  2349.  trap       #1                  ; GEMDOS call.
  2350.  
  2351.  ; 
  2352.  ; SUBROUTINES
  2353.  ;
  2354.  
  2355. test_routine:
  2356.  lea        input_msg, a0
  2357.  bsr.s      print_string
  2358.  lea        (a3), a0
  2359.  bsr.s      print_string
  2360.  bsr.s      print_newline
  2361.  lea        output_msg, a0
  2362.  bsr.s      print_string
  2363.  move.l     (a4), d1
  2364.  trap       #5                   ; Returns address of "hexadecimal" in A0.
  2365.  bsr.s      print_string
  2366.  bsr.s      print_newline
  2367.  rts
  2368.  
  2369. print_string:                   ; Expects address of string to be in A0.
  2370.  pea        (a0)                ; Push address of string onto stack.
  2371.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  2372.  trap       #1                  ; GEMDOS call
  2373.  addq.l     #6, sp              ; Reset stack pointer to top of stack.
  2374.  rts
  2375.  
  2376. print_newline:                  ; Prints a carriage return and linefeed.
  2377.  pea        newline             ; Push address of string onto stack.
  2378.  move.w     #9, -(sp)           ; Function = c_conws = GEMDOS $9.
  2379.  trap       #1                  ; GEMDOS call
  2380.  addq.l     #6, sp
  2381.  rts
  2382.  
  2383.  data
  2384. string_1:        dc.b  'ABCDEF01',0
  2385. value_1:         dc.l  $ABCDEF01
  2386. string_2:        dc.b  '23456789',0
  2387. value_2:         dc.l  $23456789
  2388. string_3:        dc.b  '00FC1000',0
  2389. value_3:         dc.l  $00FC1000
  2390. string_4:        dc.b  '00000000',0
  2391. value_4:         dc.l  $00000000
  2392. newline:         dc.b  $D,$A,0
  2393. heading:         dc.b  'HEX_TEST Execution Results',$D,$A,$D,$A,0
  2394. input_msg:       dc.b  '   Input to algorithm:   $',0
  2395. output_msg:      dc.b  '   Ouput from algorithm: $',0
  2396.  align
  2397.  bss
  2398.                  ds.l 16
  2399. stack:           ds.l  1
  2400. program_end:     ds.l  0
  2401.  end
  2402.  
  2403.  
  2404. HEX_TEST Execution Results
  2405.  
  2406.    Input to algorithm:   $ABCDEF01
  2407.    Ouput from algorithm: $ABCDEF01
  2408.    Input to algorithm:   $23456789
  2409.    Ouput from algorithm: $23456789
  2410.    Input to algorithm:   $00FC1000
  2411.    Ouput from algorithm: $FC1000
  2412.    Input to algorithm:   $00000000
  2413.    Ouput from algorithm: $0
  2414.    
  2415.  
  2416. Conclusion
  2417.   
  2418.      As you have seen so far, an initialization sequence for 
  2419. ST programs can range from nothing at all to those discussed 
  2420. in this chapter, which are not in the least intricate, but 
  2421. which can be complicated by an association with the method 
  2422. used to relinquish processor control.  I shall get to the 
  2423. more complicated initialization procedures after suitable 
  2424. preparation.
  2425.      Program 13 represents an alteration to the operating 
  2426. system environment because it establishes algorithms which 
  2427. are treated as operating system commands by other 
  2428. algorithms.  As such, it can be considered to be a set of 
  2429. initial conditions which supplements the unblemished 
  2430. operating system subenvironment.  Indeed, its presence in 
  2431. memory is a prerequisite to the execution of program 14.
  2432.      Other programs that are executed at boot behave 
  2433. similarly to configure the operating system environment, the 
  2434. execution environment or both.  Such programs may be located 
  2435. in the AUTO folder or in the root directory of the boot 
  2436. partition or disk.  Remember that there are boot programs 
  2437. which permit you to choose a set of programs to be executed 
  2438. during boot.
  2439.  
  2440.