Author note: Many people have made me aware that this series was moving
rather too slowly for their liking :-) so I have rearranged things somewhat.
This chapter is not the one that was planned which was a discussion of data
sizes, the toc and bss sections. Instead I have opted for a more practical
approach. These things are used in what follows but I figure people are
smart enough to figure out what's going on from example - for instance,
how to use global variables off of the bss and how the toc points to data
in the data section. I will of course fully explain these things in a future
Some of the text in this chapter is reproduced out of the old beginners guide.
We appreciate all your email, corrections and feedback regarding this series, so don't be shy!
An overview of current PowerPC processors.
601 - This first generally available processor is intended as a bridge between POWER and PowerPC architectures. It has three separate pipelines: The Branch Processing Unit (2 stages), the Integer Unit (3 stages) and the Floating Point Unit (four stages) together with a unified 32k instruction and data cache. A 64 bit data bus and a 32 bit address bus. Speed ranges from 50 Mhz upwards.
603 - This is a true PowerPC implementation designed for high performance and low cost. This has four separate pipelines: Branch Processing Unit (2 stages), Integer Unit (3 stage), Floating Point Unit (Six stage) and a Load/Store unit (five stage). Coupled with separate 8kb data and instruction caches. 64 bit data bus, 32 bit address bus. The data bus can be configured for 32 bit operation. What is confusing is that the 603 is less powerful than a 601 but is available in speeds up to 350 MHz and beyond.
604 - This processor is designed for mid-price workstations. It has Six separate pipelines: The Branch Processing Unit (2 stage), three Integer Units (three stage), Floating Point Unit (six stage) and a Load/Store Unit (five stage), together with separate 16Kb data and instruction caches - it is designed to run upwards from 100Mhz.
604e - Basically a 604 with bigger caches (32K a piece) and some tweaks.
620 - The first full 64 bit implementation. Similar to a 604 except the caches are 32 Kbyte each. It also has an embedded secondary cache controller to drive standard Static RAM chips.
750 - 32 bit modified 603e with direct connect second level cache. Optimized for integer rather than floating point operations. For all intents and purposes it can be considered a tweaked 603e.
The best news is that at last, floating point is an integral part of the specification - that is ALL PowerPC chips have an FPU, so we can start using real numbers rather than integer imitations, making life a lot easier for everybody, and because the FPU's run in parallel with the rest of the processor, it's faster too.
This is RISC isn't it - shouldn't we be a little scared of it?
Well, that's up to you - personally, having used PowerFantasm for the last two years, I'd rather write in PPC any day. Ok, so the transition is a bit traumatic - it's all brand new, but once you get into it, it's great.
Here are the big differences:
1. If you have been used to 68K, you'll know that you can perform operations on data in memory - e.g. addq.l #1,fred(a5). In PowerPC you can only perform operations on data in registers.
This is a real bind, but OK once you get the hang of it.
2. PowerPC instructions very often have lots of operands, and they are backwards compared to 68K.
For example: add r3,r4,r5
Adds r5 to r4 and stores the result in r3
3. Flags are NOT implicitly set when you move data. For example in 68K, if you move.w fred(a5),d0, if fred contains zero, the zero flag will be set. In PowerPC this is not the case - you need to explicitly compare the data, either with a cmpwi (CoMPare Word Immediate instruction) or by using a dotted form of an instruction - addic. r3,r4,r5.
4. Everything must go through the processor - so no moving memory to memory instructions.
5. The sizes of data is (are?) different:
PowerPC 68K Description Byte Byte Same as a byte on anything else - 8 bits. Halfword Word 16 bits Word Longword 32 bits Longword Not used 64 bits
That in a nutshell is the main differences. We assume you have read the previous chapters about how data is represented, what logical operations (AND, OR etc) are, and how a computer actually runs. We'll dive straight into the practicalities. We don't doubt for a moment this guide will turn you into an "on the metal" PPC coder overnight - there is a lot you can do with the PowerPC chip - we just want to get you walking, the running is up to you.
The pratical basics
Any program can be split into 3 logical sections - initialization, processing, termination.
The initialization stage consists of loading the program into memory, setting its variables to the right starting values, and setting up any storage space, or memory, the program may need.
The processing stage is when the program actually does what it's intended to do and produces its results, and finally the program must exit gracefully from the system.
These three stages can be broken down into smaller and smaller sections until one is finally happy that the "algorithm" or mechanical design of the program can be translated into actual processing statements, or instructions. With that in mind, our first example will be adding two numbers. Before we start, just take some time to scan over the PowerPC instruction set as given in Fantasm's reference manual (LSA0041). Don't just look at the likely candidates for the upcoming example, but take some time to examine all of them. Print it out, take it to school or work, and just browse through it.
We will put two numbers into registers, and add them together - see if you can scribble down the program, then compare it to the one given below, meanwhile we'll have a little interlude in the form of "installing Macsbug".
The version we will be talking about here is 6.5.3 or later which runs just great on PowerMacs.
If you haven't already, put Macsbug in your system folder and reboot. Now by hitting the APPLE key and the Power On/Off key on your keyboard, you should drop into Macsbug. Type "G" return, and you should be back to where you were before entering Macsbug. Now that's installed we can call Macsbug from our programs, in order that at relevant points we can examine the registers and memory.
If you have the Extension Manager on your PowerMac, make a new set called "Programming" which includes a minimal set AND Macsbug - by minimal we mean just the bare essentials - for example you don't need ~Aaron running.
Ok, back to our first example - lets check out the PPC's maths.
Here it is:
li r3,4 *First number is 4. li r4,8 *8 is second number add r5,r4,r3 *add r3 to r4 and store result in r5
Load Immediate (li) is actually an "extended" instruction provided by Fantasm, formed from the "addi" instruction - li is actually addi rx,ry,si where ry is zero, so the instruction adds the si (signed integer) to zero and stores the result in rx. The signed immediate data is only 16 bits, not the full 32 bits and so is sign extended to 32 bits before the addition is performed as the PowerPC ALU (Arithmetic and Logic Unit) only deals with 32 bit operands (64 bit processors excepted). NOTE: In any trinary operand instruction, if ry is r0, it is taken as zero, zilch, nothing but only some instructions, mainly arithmetic - add and sub - can use this form.
The program then, loads decimal 4 into r3, decimal 8 into r4, then adds r3 to r4 and stores the result in r5.
A quick note about numbers in Fantasm. You may use binary, hexadecimal, decimal and ASCII for normal numbers, Floating point (scientific notation - 1e6, 3.142 etc) can be used for some floating point directives. Binary numbers are preceded by a percent character - %101101. Hexadecimal can be preceded by a dollar character - $f0fe OR by 0x as in C - 0x1234f0fe. Hex numbers can be in lower or upper case - 0xF0FE. Character constants up to 32 bits can be defined by enclosing the string in double quotes (all strings in Fantasm are delimited by double quotes) - "FRED".
Before we actually try it out, we need to know a little about the practicalities of a PowerPC program for the Macintosh. First off, as you may be aware, every "native" Mac program has a "TOC", or more correctly a "Table Of Contents". The toc is a table of data pointers in the programs data section that points to any initialised data in use. The physical register is called rtoc and is really the PowerPC integer register r2, viz:
lwz r3,fred(rtoc) *r3 is now pointing to fred which is a pascal type string. Xcall DrawString *Print the string blr fred: pstring "Hello!" *5,H,e,l,l,o is placed in the data section by Fantasm.
The physical run time interfacing problems are handled in Fantasm with some handy "macros" (new term I know, I'll explain in a second) that take the stress out of these "interfacing" procedures. These are simply used at the right times, and all will be well (apologies for sounding so patronising but I don't want these terms to get in the way of what we are trying to learn here). For example the first lines of any native program you write should be:
Entry *Tell Fantasm where execution starts start_up *A macro that saves the current machine state. Use tidy_up before exit
This will cause Fantasm to insert the macro "start_up" at this point in the source code. When your program finishes, you use "tidy_up" and when you want to call an Operating System function you can use "Xcall <OS function name>". Note that the PowerPC assembler is "case sensitive" - this means that Xcall is different to XCALL - XCALL will not work. This is because in PowerPC, all Macintosh Operating System functions are called by name. The calling mechanism is case sensitive, so it makes sense that the assembler is also case sensitive. For more information on the PowerPC assemblers' label definitions (instructions, directives etc) see the manuals LSA00040 and 41.
You may have used library functions in Fantasm before - these are pre-assembled code snippets designed to perform a simple function - for example "Getkey" returns a keyboard key (if any). Well a macro is just as handy as a library function, except that a macro is simply text inserted where its name is used. You can examine these macros in Anvil - open the file "LS_ppc_macros.def" in the "Anvil Low Level Defs" folder. Fantasm macros can get incredibly complex (for example we have a set that translates 68K assembly language to PowerPC), but for the sake of this guide, the only thing you need to know is that we'll be seeing a lot of these three macros - start_up, Xcall and tidy_up.
Armed with this knowledge, we can write the practical version of the add program as follows:
includeh ls_ppc_macros.def *include this file from "headers" includeh general_usage.def *and this one **Program to add two numbers entry *tell Fantasm where program execution starts start_up *the start_up macro as detailed above - sets up the toc etc Xcall Debugger *call Macsbug so we can see it happen. **Processing start li r3,4 *First number is 4. li r4,8 *8 is second number add r5,r4,r3 *add r3 to r4 and store result in r5 **Processing end tidy_up *clear up processors registers and get ready to exit blr *back to system.
There are two new instructions we are not familiar with. The first line has the instruction includeh - if you've swotted up on Fantasm's manuals you may know that this is not an instruction at all, but actually a "directive" - a command to Fantasm. In this case it tells Fantasm to include a file from the "low level defs" folder - in this case the PowerPC macros so this program may have access to start_up etc.
NOTE: If you use include directives, they should be at the the very start of the file.
The last line of the above program also has a new instruction - blr - Branch to Link Register. This is a register that can contain the return address for a subroutine, or any other piece of code. The start_up macro saves it for us, and tidy_up restores the link register, so when we execute a blr instruction, the processor branches to the contents of the Link Register - in this case, back to whatever launched our program (normally the Anvil).
If you branch to your own subroutine, with a branch and link instruction
(bl), you must save the Link register (probably in another register) so
you can restore it to return to the caller under the following circumstances:
1. You use the Xcall macro to call an OS function.
2. You branch and link to another subroutine.
If your subroutine does not do either of these, then there is no need to save the link register.
Note that Xcall destroys the Link Register and the Count Register (which we haven't talked about yet, but is included in this discussion for accuracy). E.g.
bl my_function1 *Call a routine called my_function1 add r3,r4,r5 the rest of your program my_function1: mflr r29 *Save the return address (currently in the link register) in r29 your processing code mtlr r29 *Restore the return address into the link register blr *branch to the link register (back to the caller)
The bl instruction branches to a routine and saves the next instruction address in the link register - in this case it is the address of the add folowing the bl insstruction.
Making the project
I could have made this incredibly easy for you and simply uploaded a Fant 5 project, but I haven't on the grounds that this is as good a time as any to learn how to create a new project with Anvil.
Here's what we're going to do. First we will create the project. Next we will create a source file and enter the code. Finally we will build the project and then run it and examine the program with Macsbug.
Follow me though:
1. Launch Anvil and from the project menu select "Create New Project".
1a. Click on the little Apple help icon in top right to get the items titles displayed.
2. From the New Project dialog, select the template "PPC Fantasm App" from the "Project template" pop-up menu at the top of the dialog. When we build the project this will give us a standard Macintosh application.
3. Give the project a name in the text box labelled "Target name?"
4. Click the big "OK" button. You will be asked with a file selector where to create the project. Find somewhere, maybe make a new folder for it and click OK. The project will be created and opened. You will note the project window that opens has the build and run icons crossed out in red. This means you can't build the project (there are no files) and the project has not been built, so you can't yet run it.
5. From the Edit Menu hit New to create a new text file - it will be called "Unititled 1".
5a. If you haven't set up Anvil's general preferences to default to PowerPC, select "This file's preferences" from the edit menu and change the language to PowerPC. After this operation Anvil will ask you to save the file - give it a name and save it next to your project file.
6. Enter the program text as above and if you haven't already, save the file and give it a name.
7. Add your new file to project (it will apear in the project window in the _Src area) and then Build the project (Use Apple B, click the Anvil icon in the project window or select Build from the Project menu).
It will build and you will find the target icon is now available. Click it, or hit Apple R to run your program.
If Macsbug is installed you will immediately enter Macsbug, if not you will get an error reporting an unimplemented trap and you will have to reboot, install Macsbug and try again.
Assuming you are in Macsbug, hitting APPLE S three times should display the following lines:
Step (into) No procedure name 004CE9D0 lwz RTOC,0x0014(SP)| 80410014 004CE9D4 li r3,0x0004 | 38600004 004CE9D8 li r4,0x0008 | 38800008 004CE9DC add r5,r4,r3 | 7CA41A14
APPLE S is the Macsbug command to step an instruction - that is, run just one instruction then stop again. The first instruction lwz RTOC,0x0014(sp) is the last instruction of the Xcall macro - you will always see this line if stepping a system call in PowerPC. The next three lines are our program, and the lines following that are the "tidy_up" macro code.
In Macsbug, enter "G" followed by the return key - you will be returned to Anvil. Didn't hurt too much I hope :-)
Now, we need to follow the program through - run it again (APPLE R), and step past the first lwz RTOC,0x0014(SP) instruction. Macsbug should now be pointing at the first line of our program - li r3,0x0004. To the left of the disassembly, you will see the processors registers. Step over this instruction with S return and examine r3 - it will contain 4. Now step the next instruction, and r4 will contain 8. Now step the final instruction add r5,r4,r3, and r5 will contain 0x000C - which is hexadecimal for 12. If you like, step through the instructions that follow (the tidy_up macro code), and eventually you will come to the blr instruction - at this point, when you execute this, your Mac will switch back to 68k code, and you will be in Anvil's code - just type "G return" to run Anvil.
Easy? Any problems? If yes, re - run through the above until you understand what we did. If you are really keen, you can modify the program to your hearts content. Remove the Xcall Debugger line for example (tip - just comment it our rather than deleting it - make the first character of the line a semi-colon or a splat character "*").
A closer look at the architecture.
By now you should be getting the whole point of RISC architectures - the instructions are simple, there's lots of registers and things happen quickly. Whereas in 68k, one tends to use the stack extensively, in PPC, it's better to find your own register convention. For example the following registers are used for:
sp - obviously the stack pointer (r1).
rtoc - the toc pointer (r2)
r31 - modify this at your peril if you call any 68K code or an OS function it's best to leave this alone.
Apart from the above registers, all the other are free. However, when passing parameters to a System function (or trap), the parameters are generally passed in r3 to r10, and the results passed back similarly in r3. For more information we suggest you check with Inside Macintosh PowerPC System Software, but generally the above holds true. Fantasm's reference manual gives you detailed register volatility rules. This is handy when determining if calling an OS function will destroy a register.
One method is to use the low numbered registers as scratch registers, and the high numbered registers as longer term storage. For example, we use r29 to store the LR in when going to a subroutine. If that subsequently calls another routine, that routine will either save r29 in memory first or use r28 to store LR in. The emphasis is on speed, and if you can get away without having to reference memory, then do it.
Stacks are implemented in software, using whatever method you prefer - however, the Update form of instructions are handy for this, as the NEW effective address is stored, not the previous effective address - e.g.
68K move.l d3,-(sp) PPC stwu r3,-4(sp) 68K move.l (sp)+,d3 PPC lwz r3,(sp) addi sp,sp,4
Note the lmw instruction. This moves registers from the processor to memory quickly:
stmw r3,0(fred) *save r3 to r3 in memory at location fred lmw r3,0(fred) *load r3 to r31 from memory at fred fred: ds.l 4*32 *reserve 128 bytes of storage in the data section
The instruction set is detailed in Fantasms reference manual - we will not replicate it here, but will note specific practices.
The "carry", "overflow", and "extended" option bits: The standard PowerPC arithmetic instructions do not set the carry bit or test for overflows. The "c" and "o" suffixes are used to designate the instruction forms which modify the carry and/or overflow bits, as in "add carrying" (addc), "add carrying with overflow enabled" (addco), and "add carrying with overflow enabled and CR update" (addco.). The "extended" arithmetic instructions include the carry bit in their calculation, to implement multiple-precision arithmetic. The extended instructions include "add extended" (adde), "subtract from extended" (subfe) and "add to zero extended" (addze).
The "record" bit: Unlike the 68K processor which nearly always set the condition codes depending on the outcome of an operation, in PowerPC we use use special "record" versions of the arithmetic instructions to set Condition Register field 0 (CR0). Most arithmetic instructions have a "record" form indicated by appending a period (".") at the end of the instruction name, as in subtract from (subf.).
Immediate and "shifted immediate" values : Some of the instructions have an "immediate" form where one of the operands is contained within the instruction word. (This differs from the 68K where Immediate is an addressing mode and the information follows the instruction.) Since immediate data is limited to values that can fit within the instruction word, immediate values are usually limited to a 16-bit halfword. The PowerPC also supplies "immediate shifted" instruction forms that take a 16-bit immediate value and shifts it left by 16 bits into the upper half of a word, allowing the loading of fullword (32 bit) immediate data with two instructions - LIS and ORI for example.
A practical example.
In this section we'll dissect the "PPC_GRAPH_DEMO" program supplied on the Fantasm 5 CD. The main aim of this example is to show just how a PowerPC program is structured. We have 2 source files, 1 globally included file (the BSS offsets) and a Build Control File. The two source files are simply the main source file "PPC_graph_demo.s" and the initialisation file - "graph_demo_init.s".
The aim is to open a window, use QuickDraw to draw some nice shapes, and then quit.
The first thing we do is equate some registers to names, so as to make the code more readable:
param1: reg r3 *Set up the names of the regs used for parameter passing param2: reg r4 param3: reg r5 param4: reg r6 bss: reg r30 *The register we use for global data
These registers are used as parameter holders during system calls using the "Xcall" macro.
Next, the program proper starts:
ppc_graph_demo: ENTRY *Prog starts here start_up *save all the regs and set up r30 for global la r3,qd(`bss) *get the address of the QD array into r3 addic r3,r3,206-4 Xcall InitGraf *Init managers Xcall InitFonts Xcall InitWindows Xcall InitMenus Xcall TEInit li `param1,0 Xcall InitDialogs Xcall InitCursor **Open our window and copy its viewrect. bl graph_demo_init *initialise and open a window and get its viewrect *into viewrect_1(bss)
The ENTRY directive tells Fantasm that this is where the program starts . Next we use the "start_up" macro to save the PowerPC registers, and set r30 to point to the BSS section. Following on is the normal Macintosh initialisation - we could have used a library function here (init_mac), but thought it better to show the process. Finally we branch and link to graph_demo_init which is in the initialisation source file. graph_demo_init carries out two functions. Firstly it opens our window, and secondly it sets up two rectangles that we will be drawing into:
**File:graph_demo_init.s param1: reg r3 *Set up the names of the regs used for parameter passing param2: reg r4 param3: reg r5 param4: reg r6 bss: reg r30 *The register we use to point to "global" variables graph_demo_init: mflr r29 *save return address bl open_window mtlr r29 blr open_window: mflr r28 *save return address from link register li `param1,128 *window resource id is 128 li `param2,0 *clear param2 - window storage - let the OS find some li `param3,-1 *behind no other windows(i.e. in front) Xcall GetNewCWindow *Note NewCWindow, else we could have problems *with the colors. stw `param1,window_1_ptr(`bss) **get the viewable rectangle (top,left,bottom,right) la r3,16(r3) *windowptr+16=viewrect (la is "load address") la r4,viewrect_1(`bss) *Storage is in the bss section lfs f0,(r3) *32 bit move into f0 stfs f0,(r4) *32 bit store into viewrect_1 lfs f1,4(r3) stfs f1,4(r4) **And copy to our second rectangle as well la r4,viewrect_2(`bss) stfs f0,(r4) *into viewrect_2 stfs f1,4(r4) **set the port to our window lwz `param1,window_1_ptr(`bss) Xcall SetPort lwz r10,white(rtoc) *r10 points to colour white mtlr r28 *restore the return address blr *and branch to it **** global graph_demo_init extern_data white
Note the use of the FPU (stfs - store floating single) to transfer the 8 byte rectangle definition into viewrect_1 and 2.
Now we have a window and know its coordinates (viewrect) we can start drawing.
**First lets set the foreground colour to white lwz `param1,white(rtoc) Xcall RGBForeColor *That should do it
This piece of code sets the current pen colour to white. Now we fill the window with horizontal lines by drawing each line and decrementing r22 until it is zero:
**now a simple horiz test line - draw r22 white lines la r3,viewrect_1(`bss) *top,left,bott,right lhz r20,2(r3) *left of window lhz r21,6(r3) *right of window lhz r22,4(r3) *bottom of window into r22 **use MoveTo and LineTo to draw the line line_loop: bl draw_line *draw a line(r20 to r21 at y position r22) subic. r22,r22,1 *up 1 line bne line_loop *and if line y isn't zero draw another line.
We then repeat the process, but this time we change the current pen colour as well and do it 50 times:
**now we'll do the same, but change the colours dynamically this time and **fill the window 50 times li r26,50 *do it all 50 times outer_loop: **reset the x and y's la r3,viewrect_1(`bss) *top,left,bott,right lhz r20,2(r3) *left of window lhz r21,6(r3) *right of window lhz r22,4(r3) *bottom **Draw line and alter the components of the colour line_loop_2: bl draw_line *draw this line lwz r23,white1(rtoc) *r23 points to our colour that we are altering lhz r24,(r23) *get the red value subic r24,r24,64 *subtract 64 from the red sth r24,(r23) *save the new colour back in memory lhz r24,2(r23) *get the green value subic r24,r24,32 *subtract 32 from the green sth r24,2(r23) *save the new colour back in memory lhz r24,4(r23) *get the blue value subic r24,r24,128 *subtract 128 from the blue sth r24,4(r23) *save the new colour back in memory lwz `param1,white1(rtoc) Xcall RGBForeColor *Set new foreground colour to white1 subic. r22,r22,1 *up 1 line bne line_loop_2 *and if not top of window (line=0) draw next line in new *colour. subic. r26,r26,1 *do it all r26 times bne outer_loop
Next we go from drawing lines, to a little scroll test. Rather than put the code in line, we have used a scroll routine called:
bl clear_window *clear the window out by scrolling
This looks like this:
**Clears our window by first scrolling diagonally, and then virtically (virtically?). clear_window: mflr r29 *save return address in r29 **First lets set the foreground colour to white lwz `param1,white(rtoc) Xcall RGBForeColor *That should do it la r20,viewrect_1(`bss) *r20 points to viewrect_1 lhz r22,4(r20) *bottom of rect for use as a loop count **Scroll diagonally *extern pascal void ScrollRect(const Rect *r, short dh, short dv, RgnHandle *updateRgn) scroll_diag_loop: la `param1,viewrect_1(`bss) *top,left,bott,right li `param2,1 *dh = 1 = scroll horizontal +1 li `param3,1 *dv = 1 -= scroll vertical by 1 as well=diagonal scroll li `param4,0 *no updatergn Xcall ScrollRect *scroll by 1 pixels subic. r22,r22,1 *Decrement loop count bne scroll_diag_loop *and branch if not zero to scroll again la r20,viewrect_1(`bss) lhz r22,4(r20) *bottom ** Now Scroll down scroll_down_loop: la `param1,viewrect_1(`bss) *top,left,bott,right li `param2,0 *no dh this time li `param3,1 *just dv li `param4,0 *no updatergn Xcall ScrollRect *scroll by 2 pixels subic. r22,r22,1 *Decrement loop count bne scroll_down_loop *and branch if not zero to scroll again mtlr r29 *get return address in link register blr *and return to caller.
Note the way the Pascal header translates to PPC - very easily. The parameters go left to right into r3 onwards (up to and including a maximum of r10).
When clear_window returns, the window will be cleared and we can draw some ever larger circles just as easily:
**now lets draw a circles li r28,3 *do the zoomy circles 3 times rgb_zooms: lwz `param1,red(rtoc) Xcall RGBForeColor *Set new foreground colour bl draw_circles *draw a zoomy circle in red. lwz `param1,green(rtoc) Xcall RGBForeColor *Set new foreground colour bl draw_circles *draw a zoomy circle in green lwz `param1,blue(rtoc) Xcall RGBForeColor *Set new foreground colour bl draw_circles *draw a zoomy circle in blue subic. r28,r28,1 bne rgb_zooms
We call draw_circles 3 times. the first time we set the pen colour to red and call draw_circles. Next we set the pen to green and call draw_circles and finally we draw blue circles. Again, draw_circles is a subroutine:
draw_circles: mflr r29 *save return addr. **First reduce our viewrect_2 down to a small size la r20,viewrect_1(`bss) *copy this rect la r22,viewrect_2(`bss) *to this rect whilst making it smaller lhz r21,(r20) addi r21,r21,100 sth r21,(r22) *top+100 lhz r21,2(r20) addi r21,r21,100 sth r21,2(r22) *left+100 lhz r21,4(r20) subic. r21,r21,100 sth r21,4(r22) *bottom-100 lhz r21,6(r20) subic r21,r21,100 sth r21,6(r22) *right-100 li r26,100 *loop count - draw 100 circles, each slightly bigger. **Now draw r26 circles, each slightly larger than the last circles: la `param1,viewrect_2(`bss) *top,left,bott,right - viewrect_2 is a *copy of viewrect_1 Xcall PaintOval *takes a rectangle as its only parameter - *simple? la r20,viewrect_2(`bss) lhz r21,(r20) subic. r21,r21,2 sth r21,(r20) *top-2 lhz r21,2(r20) subi r21,r21,2 sth r21,2(r20) *left-2 lhz r21,4(r20) addi r21,r21,2 sth r21,4(r20) *bottom+2 lhz r21,6(r20) addi r21,r21,2 sth r21,6(r20) *right+2 subic. r26,r26,1 *Note the dot on this one to set the condition flags bne circles mtlr r29 blr
PaintOval takes a rectangle, and draws a filled disc within the confines of the rectangle. Thus by making the rectangle slightly larger on each pass of the loop (circles) we can draw slightly bigger circles. After drawing the disc, we make the rectangle slightly larger, so the next disc will be slightly larger. We use a copy of the viewrect, called viewrect_2 as set up in graph_demo_init.
Finally it is simply a case of quitting as follows:
tidy_up *restore all the regs back to how they were before this *program started. blr *bye bye - end of program.
We call the macro "tidy_up" which restores all the registers to how they were before our program started, and puts the return address into the link register - then it's simply a blr instruction to quit.
Notice at the bottom of the program are the declarations of the predefined data used in the program, external calls used and global declarations. Look closly at the code and see how the RGB palettes of white1 are modified to get a large array of colours when the lines are drawn.
********************************************************* **Pre defined Data that goes in the data section **Our colours defined as red,green,blue strengths. white: dc.h 0xffff,0xffff,0xffff white1: dc.h 0xffff,0xffff,0xffff red: dc.h 0xffff,0,0 green: dc.h 0,0xffff,0 blue: dc.h 0,0,0xffff **** ********************************************************* **The declarations. global ppc_graph_demo extern graph_demo_init *External initialisation subroutine in graph_demo_init.s extern init_mac
Before proceeding any further with this guide, it's best to take "time out" to ensure you fully understand ppc_graph_demo.
Copyright Lightsoft 1997/8.