



C) PowerPC Support in C or C++
==============================

Principially PPC Developpement in C/C++ runs in 5 phases:

1) Rewrite all 68k ASM Stuff in C
2) Adapt Source to ANSI/StormC
3) Adapt to PPC
4) Contextswitch-Optimizing
5) Further Adaptions

Contrary  to  what  you  might believe, 3) is only a very small step, the big
step  is 2).  And yes, you can do this already, even if you do not own a PPC,
mainly.   I  will  explain the different steps of developpement now in a more
detailed way.

It  has  to  be  outlined, that it is advised to do steps 1)/2) already while
developping  an  68k version, even if at first no PPC Version is planned.  It
will  simplify  the  PPC Developpement much, and it in fact does not need too
much extra work...

It has also to be noted, that things are not that easy using the PPC Software
from  Phase 5.  This is a special feature of the WarpOS Software, that things
can be such easy.




I  won't discuss rewriting 68k ASM to C Source here, you should be able to do
this yourselves.

2) Adapt Source to ANSI/StormC
------------------------------

The  most work is not the adaption to PPC, but the adaption from SAS/C or GNU
C to StormC.  StormC is a strict ANSI compiler, because of that it knows only
Standard-C-Functions  that  are  contained in the ANSI-Standard.  Some of the
not-supported  functions  can be emulated using the not-yet-released UnixLib,
though.

It  should  be noted, that, if your program compiles on SAS/C with the STRICT
ANSI  mode  set.   You  can think of StormC as a compiler that ALWAYS runs in
STRICT ANSI mode.

The  following  SAS/C  Functions  are  not  contained  in  ANSI, and thus not
supported  by  StormC  (most  of  them  are quite exotic functions, and it is
possible  that  you  do  not  even  know  a  lot  of  them, even if you are a
proficient C Coder) :

astcsma     isascii     iscsym      iscsymf     toascii     scdir       stcpm
stcpma      stcsma      stccpy      stpcpy      stcis       stcisn     stclen
stpbrk      stpchr      stpchrn     strcmpi     stricmp     strnicmp  strnset




strset      stcarg      stpsym      stptok      stpblk      strbpl     strdup
strins      strmid      stcd_i      stcd_l      ecvt        fcvt         gcvt
stch_i      stch_l      stci_d      stci_h      stci_o      stcl_d     stcl_h
stcl_o      stco_i      stco_l      stcu_d      stcul_d     toascii   stpdate
stptime     __datecvt   __timecvt   utpack      utunpk      cot          iabs
max         min         pow2        __emit      getreg      putreg       geta
isatty      ovlyMgr     dqsort      fqsort      lqsort      sqsort     strsrt
tqsort      drand48     erand48     jrand8      lcong48     lrand48    mrand8
nrand48     seed48      srand48     __autoopenfail        chkabort  Chk_Abort
_CXBRK      __exit      onexit      _XCEXIT     forkl     forkv       onbreak
wait        waitm       bldmem      rstmem      sizmem     chkml       getmem
getml       halloc      lsbrk       sbrk        _MemCleanup rbrk       rlsmem
rlsml       memccpy     movmem      repmem      setmem      swmem      except
__matherr   poserr      datecmp     timer       __tzset     getch    fgetchar
fputchar    _dread      _dwrite     read        write       clrerr      close
_dclose     fcloseall   creat       _dcreat     _dcreatx    fdopen     fileno
fmode       iomode      open        _dopen      flushall    mkstemp    mktemp
setnbf      _dseek      lseek       tell        access      chkufb      chmod
fstat       getfa       getft       stat        stcgfe      stcgfn     stcgfp
strmfe      strmfn      strmfp      strsfn      unlink      argopt     chgclk
dos_packet  getclk      getasn      getdfs      putenv    rawcon   stackavail
stacksize   stackused   chdir       closedir    dfind       dnext    findpath
getcd       getcwd      getfnl      getpath     mkdir      opendir    readdir
rmdir       seekdir     rewinddir   telldir     readlocale scr_beep    scr_bs




scr_cdelete scr_cinsert scr_clear   scr_cr   scr_curs   scr_cursrt scr_cursup
scr_eol     scl_home    scr_ldelete scr_lf      scr_linsert scr_tab   _CXFERR
_CXOVF      _EPILOG     _PROLOG

The  most  important  of  the  "not  allowed"  functions  are the Level 0 I/O
functions (open,close,read,write).  Use fopen,fclose,fread,fwrite instead.

But  STRICT  ANSI  does  not  only  limit  the functions, there are also some
things,  that  cause  a  warning  from SAS/C, but an error from a strict ANSI
Compiler.

Things like:

char *string=malloc(300);

cause an error from StormC. Correct would be:

char *string=(char *)malloc(300);

ANSI  wants  STRONG  TYPING.  If you do not own StormC, but want to make your
code  as  easy  compilable  with  StormC PPC later, compile with STRICT ANSI.
Problems  appear  especially with function pointers.  If you are not sure how
to  cast a thing for STRICT ANSI, maybe you should try void *, it works often
for not strongly typed source.




You should also replace all K&R Syntax (example)

void main(argv,argc)
int argv;
char **argc;

by the normal syntax (example)

void main(int argv,char **argc);

Also a code like

int a=5;
int stuff[a];

is  not  legal  on ANSI.  Array Dimensions have to be constants.  If you need
them variable, use dynamic allocation using malloc.

A good method to convert to "Strict ANSI" is the following:

1. Just compile it, and look at every warning and error
2. Typecast everything that looks like a pointer (and causes
   an error) to void *, everything else that causes problems,
   to a int, long or double.




3. If some things still don't work, have a look at them now.

Some  Sources  (like  the  Source  of  Doom)  require  parts  of the Unix/TCP
includes.   If  you need such things, please contact me, i have converted the
needed things to StormC (contact address see below).

Now  we  are  nearly  done  with  the  ANSI/StormC Adaption.  At the end some
keyword have to be defined differently:

#define __stdargs
#define __regargs
#define __asm
#define __far FAR
#define __inline inline

__chip,  __fast  and  __interrupt  do  not  exist  on StormC, they have to be
replaced by the appropriate OS Functions.

Some  words to __attribute__ ((packed)).  It does not exist, and is a feature
that  would  slow  down the PPC *much*, if it would exist.  Please do not use
__attribute  ((packed)).   The  PPC  needs a certain alignment to get optimal
speed.




You should not use text constants longer than a line.

If you have done all this, you now (should) have a working StormC 68k Source.
Now  we  go  to the PPC stuff.  The most work is done now.  Only small things
remain to do.  PPC-handling is mostly done internal by the compiler.

C. Adapt to PPC
---------------

At first we have to change register parameters:

void test(register __a0 mytest);

has to be changed (for example) to

void test(register mytest);

The  PPC does not know a register a0.  But you can tell him to use a register
by usage of the keyword "register", without specifying a register number.

Next we have to do some changes to OS-Includes:

up to now, depending on which compiler you used, you did (example):




#include <clib/exec_protos.h>
#include <pragmas/exec_pragmas.h>

or

#include <clib/exec_protos.h>
#include <pragma/exec_lib.h>

or

#include <clib/exec_protos.h>
#include <inline/exec.h>

or

#include <proto/exec.h>

For StormC PPC you do:

#include <clib/exec_protos.h>

Do  not  include  any  pragmas/pragma  files,  or  you  will  be  swamped  by
error-messages.  Also do not include any proto/ files.




If you want to compile your source for both 68k and PPC (without changing the
source) you do:

#include <clib/exec_protos.h>
#ifndef __PPC__
#include <pragma/exec_lib.h>
#endif

__PPC__ is always set correctly.

Yet  another  difference  between 68k and PPC concerns the usage of Subtasks.
If  you  want to do the Subtask as PPC Task (recommended) you have to replace
functions  like  CreateTask()  by  CreateTaskPPC() of the powerpc.library.  I
won't  go into detail here, most of the time the API is absolutely identic to
the  usual  functions, with the exception of a PPC at the end of the function
name.  Read the documentation of WarpOS for more information.

The  other  method  would  be  doing  the  subtask  as  68k  task and calling
CreateTask().   To  do so you would have to make your program a mixed Binary,
though,  and you also would not get full PPC Speedup.  So usually (unless the
subtask  does  many  OS  Calls)  the  CreateTaskPPC()  approach is the better
method.   Also, it is recommended not to use 68k Subtasks in PPC programs, so
that  your  program  will  get optimal speed on a 100% PPC Amiga System (that
surely will appear some time in the future).




Next  thing  to  discuss would be the Tag-List notation of some OS-Functions.
The  PPC-Compiler  can  do OpenScreenTagList (the one with the array), but it
*cannot*  do OpenScreenTags.  So if you use the "Tags"-version of OS Calls in
your source, you have to change this to the "TagList"-version.

Then  we  come  to  the  BeginIO-Function.   This function only exists with a
Library Base on the PPC Compiler.  You can use the following code (example is
for audio.device):

#include <libraries/powerpc.h>
#include <ppcamiga.h>

void BeginIOAudioPPC(struct IORequest *arg1)
{
        extern struct Library *AudioBase;
        ULONG regs[16];
        regs[9] = (ULONG) arg1;
        __CallLibrary(AudioBase,-30,regs);
}

An example how this can be used (out of the Sound-Code of ZhaDoom...):




  AudioBase = (struct Library *)audio_io->ioa_Request.io_Device;
  c = &channel_info[cnum];
  c->audio_io->ioa_Request.io_Command = CMD_WRITE;
  c->audio_io->ioa_Request.io_Flags = ADIOF_PERVOL;
  c->audio_io->ioa_Data = &chip_cache_info[cache_chip_data (id)].chip_data[8];
  c->audio_io->ioa_Length = lengths[id] - 8;
  c->audio_io->ioa_Period = period_table[pitch];
  c->audio_io->ioa_Volume = vol << 2;
  c->audio_io->ioa_Cycles = 1;
#ifdef __PPC__
  BeginIOAudioPPC((struct IORequest *)c->audio_io);
#else
  BeginIO ((struct IORequest *)c->audio_io);
#endif

You  see?   You  always  have to read out the LibraryBase of a device to do a
BeginIO on PPC...

Some   readers   now   probably   ask   themselves   what  about  the  famous
"Context-Switch".    Well,   the   truth   is,  under  StormC,  the  Compiler
automatically  deals  with  the Contextswitch.  You won't have to think about
it...  i will lose some words about it anyways:




There are two sorts of Contextswitches:

a) Function-Contextswitches

You have to compile with Debugging-Information the first time you compile the
Source.   Then the compiler handles the Contextswitches automatically.  Later
you can compile without Debugging-Information, if you want.

b) Library-Contextswitches

These  need  so-called  "function-stubs".   ppcamiga.lib already contains the
function-stubs  for  all  Amiga-OS-functions,  and  for  the 68k-functions of
rtgmaster  (But  for rtgmaster also PPC-functions exist, and it is adviced to
use these).  To create a stub for a not yet supported library, you do:

genppcstub mylib_protos.h mylib.fd VERBOSE

You  need  the  proto-  and  the FD-File to create the stub.  The stub is a C
Source  file  that  you  link  together  with your Source.  The Contextswitch
itselves then works automatically.




D) Contextswitch-Optimizing
---------------------------

With  WarpOS a Contextswitch needs about 0.5 milliseconds (with a 200 MHz PPC
604e Board...).  It should be avoided to do "many Contextswitches per Second"
(BTW:  The Phase 5 Software needs about 1 millisecond for a Contextswitch).

Example of things to avoid:

- Load Files on a Byte-per-Byte basis with fgetc (use fread instead
  and load to a Fastram Buffer, from which you get the stuff on a
  Byte-Per-Byte-Basis then)
- WritePixel (work on a Fastram-Buffer instead)
- OS-Calls that are called often per second


Graphics  can be handled completely PPC Native by using rtgmaster.  rtgmaster
is a PPC Shared Library.

Notice,  that  some  of the Standard-C-Functions do Contextswitches.  I think
clock()  is  among  them,  but  am  not sure about it.  A possibility to deal
timing  without Contextswitches for sure is to use the PPC timer directly, in
PPC ASM:




double tb_scale_lo = ((double)(bus_clock >> 2)) / 35.0;
double tb_scale_hi = (4.294967296E9 / (double)(bus_clock >> 2)) * 35.0;

bus_clock is set to the Bus Clock in Hz, for example 50000000 for
a 150 MHz Board, 66000000 for a 200 MHz Board.

Stopping time is then done like this (example of the I_GetTime-function
of Doom):

int I_GetTime (void)
{
 unsigned int clock[2];
 double currtics;
 static double basetics=0.0;
        ppctimer (clock);
 if (basetics == 0.0)
basetics = ((double) clock[0])*tb_scale_hi + ((double) clock[1])/tb_scale_lo;
currtics = ((double) clock[0])*tb_scale_hi + ((double) clock[1])/tb_scale_lo;
 return (int) (currtics-basetics);
}


ppctimer looks like (object code for people who do not have StormPowerASM
is contained inside this archive):




 vea
 XDEF    _ppctimer

_ppctimer:      mftbu   r4
                mftbl   r5
                mftbu   r6
                cmpw    r4,r6
                bne     _ppctimer

                stw     r4,0(r3)
                stw     r5,4(r3)
                blr

But  well,  as  i said, i am not sure, if clock() does use Contextswitches or
not.   Only  i  had  the  feeling that ZhaDoom speed up, after i replaced the
usage of clock() by the usage of ppctimer().


5) Further Adaptions
--------------------

Note:   The  following  is  fully  optional  !!!  (But it might speed up some
things)




It  is  possible  to  declare  waste  memory-areas  as non-cachable using the
BAT-registers  of  the PPC.  How this is exactly done, read the documentation
of WarpOS.

Another  optimization would be re-writing parts of the code in PPC Assembler.
As to this, see below.

In  some newsgroups it was discussed to run program parts asynchronely on the
68k.   Some  people even claimed this would only be possible with the Phase 5
software.   This  is not true, if you want to implement it, you would use the
PPC-Native  Message-System  of  WarpOS  (keyword "AllocXMsg", refer to WarpOS
documentation).   But  i want to outline the disadvantages of this "parallel"
method:

1) On PPC-only machines such code would have serious disadvantages. And such
   systems will come...
2) The PowerUP-Hardware is  not good for true Multi-Processoring.  As  soon as
   your 68k/PPC  tasks share  memory, you will get  serious  problems. I won't 
   get into  detail, it was discussed enough  in the newsgroup. And it  really
   is not worth the effort.

I  seriously  recommend to work only "synchrone", doing Sub-Tasks only on the
same CPU the mainprogram also is running on.




Sometimes  it  is  also  useful  to  do  a  manual Contextswitch to a 68k ASM
function.   If the ASM functions contains tons of OS calls, for example.  But
if  you  have  such  code,  i recommend using a Mixed Binary, anyways.  Makes
things more easy.

PowerPC ASM Optimization
------------------------

At  last  this one.  Again i have to say, that it makes no sense to implement
the whole stuff in PPC ASM.  You start like this:

1) Implement all in C
2) Compile it for 68k and use the Profiler of StormC (the profiler currently
   only exists for 68k, but its data is also useful for PPC)

When  you use the profiler you run the program, and it does a statistic about
which functions use how much CPU time.  Then you implement the functions that
take the most CPU time in PPC ASM.  It is that simple.

You have to keep in mind, though:

- even ASM can't speedup massive numbers of Context-Switches
- ASM also can't speed up the slow GFX Bus of the Amiga (Even Zorro III is
  slow as to today's standards...)




Remember always:

Doing  a fast implementation in C and then using a Profiler to find out which
functions  are  worth  a  ASM  Optimization  is  much  more clever than doing
everything in PPC ASM.

Of course the profiler is only available, if you own StormC.  SAS/C and GNU C
do not have a profiler.

Now,  what  do you do, if your "original" source is in ASM, not in C ?  Well,
you  insert  timing  checks  and  write  some  timing data to a file ("manual
Profiling")  at  places  where you think the most time is wasted.  Of course,
real  profiling  (using  StormC)  is  much  more easy.  Also remember, that C
defines it's functions like:

_Functionname

So if you want to profile ASM-Stuff you have to add a leading _ to all functionnames,
and to XDEF them all.




Example:

stuff.asm
---------

start:

jsr morestuff
; lots of code

rts
; lots of functions
morestuff:
; lots of code
rts

Would have to be changed to:

startit.c
---------

extern void start(void);




void main()
{
 start();
}

stuff.asm
---------

    XDEF _start
    XDEF _morestuff
    ;... lots of functions
_start:
    jsr _morestuff
    ;lots of code

    rts
_morestuff:
    ;lots of code
    rts

Well,  and  now you can start profiling...  the C thing simply starts the ASM
main function...

