PPC Kurs - Teil 1

von Steffen Haeuser

     In  dieser  und  den naechsten Ausgaben werde ich einen kleinen PPC
     Programmierkurs abhalten.  Im Grunde genommen wird es eher ein Kurs
     in  sauberer  ANSI-Programmierung  sein,  weil  die  Verwendung von
     100%igem  ANSI-Code eigentlich das wichtigste ist.  Der eigentliche
     Wechsel  von  68k->PPC  bedingt nur ganz minimale Aenderungen.  Ich
     werde  bei  diesem  Kurs  die  Kenntnis  der  Programmiersprache  C
     voraussetzen.   Zu meiner Person:  Ich bin (u.a.) der Programmierer
     von ZhaDoom und rtgmaster.

     Mein  Kurs  wird  sich  spezifisch  der Programmierung unter WarpUP
     widmen.   ppc.library  Fans  koennen  jetzt eigentlich aufhoeren zu
     lesen.  Die ganze Sache ist nur unter WarpUP so einfach.  Mein Kurs
     setzt  sich momentan mit StormC auseinander, ich hoffe jedoch, dass
     bald auch der Public-Domain Compiler fuer WarpUP verfuegbar ist.
 
     Einteilung der PPC Entwicklung in 5 Phasen:

     1. Allen 68k Assembler Code nach C oder PPC Assembler umschreiben
     2. Allen Source an ANSI/StormC anpassen
     3. Allen Source an PPC anpassen
     4. Kontextswitch-Optimierungen
     5. Weitere Anpassungen
     
     Viele  Programmierer  - selbst bei komerziellen Firmen - denken, 3)
     sei das Aufwendigste.  Ganz im Gegenteil.  3) ist geradezu minimal.
     2) ist die eigentliche Arbeit, und kann sogar durchgefuehrt werden,
     ohne  einen  PPC oder auch StormC zu besitzen.  Besitzern von SAS/C
     wird  empfohlen,  mal  mit STRICT ANSI zu compilieren, das verhaelt
     sich  dann  ziemlich  aehnlich wie StormC.  Der StormC ist naemlich
     IMMER   STRICT  ANSI.   Ich  empfehle,  bereits  waehrend  der  68k
     Entwicklung  eine  PPC-Anpassung parallel durchzufuehren.  Spart ne
     Menge  Arbeit.   Auch  wenn  mir  viele "komerzielle" nicht glauben
     wollen  ("Wir  machen  erst  die  68k Version").  Das is eigentlich
     Quatsch  so.  Man halst sich nur zusaetzliche Arbeit auf, indem man
     Sachen,  die  am  Anfang  nebenher gehen, zu einer ARBEIT anwachsen
     laesst.
     
     Ich   werde   auf   1)   nicht  naeher  eingehen.   Dies  ist  kein
     Assembler-Kurs.
     
     Der   naechste   (und   ueberhaupt   wichtigste   Punkt)   ist  die
     ANSI-Anpassung.   Eine  einfache  Methode  ist  es,  einfach mal zu
     compilieren  (unter StormC einfach compilieren, unter SAS/C - falls
     man  noch  kein  StormC  hat  -  unter  STRICT  ANSI  compilieren).
     Natuerlich  muessen  sich  SAS/C  Leute "irgendwann am Ende" StormC
     besorgen, oder aber ihren Source jemandem geben, der StormC hat, da
     SAS/C  kein  WarpUP  unterstuetzt.   Oder  auf  den  Public  Domain
     Compiler  fuer  WarpUP  warten.   Wobei  es da dann sicher minimale
     Aenderungen  gibt, dieser Kurs setzt sich hauptsaechlich mit StormC
     auseinander.
     
     Dann betrachtet man alle Fehlermeldungen und bearbeitet sie.  SAS/C
     Leute  am Besten auch Warnings rausschmeissen, falls SAS/C wo nicht
     100% ANSI-konform war.
     
     Folgende  Nicht-ANSI-konformen Funktionen stehen unter StormC nicht
     zur Verfuegung (meist recht exotische...):
     
     astcsma    isascii     iscsym      iscsymf     toascii     scdir       stcpm
     stcpma     stcsma      stccpy      stpcpy      stcis       stcisn      stclen
     stpbrk     stpchr      stpchrn     strcmpi     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
      
     Die  bedeutsamsten  darunter sind die Unix-File-I/O Funktionen.  Es
     sei darauf hingewiesen, dass es eine "Unixlib", die diese emuliert,
     gibt  (derzeit  bin ich der Entwickler der Unixlib, habe aber lange
     nix  mehr  dran  gemacht...  aber ich setze sie auch hin und wieder
     ein, is ne Linkerlib).
     
     Es gibt noch einige andere Dinge zu beachten:
     
     1. "Strong Typing"
     
     char *zeugs=malloc(300);
     
     ist ein FEHLER. Korrekt ist nach ANSI:
     
     char *zeugs=(char *)malloc(300);
     
     2. "Feldgrenzen sind KONSTANT"
     
     int a=5;
     int b[a];
     
     ist laut ANSI ein FEHLER.
     
     3. "Bitfields sind C++ Code"
     
     Bitfields existieren laut ANSI nicht in C, sondern nur in C++
     
     4. "fclose nur wenn Files, offen sind"
     
     Files, die niemals offen waren, duerfen auch nicht geschlossen werden.
     
     Also: 
     
     if (fil) fclose(fil);
     
     Nur zur Sicherheit...
     
     5. "__attribute__((packed)) considered harmful"
     
     Das  GNU  C  Feature  Attribute-Packed ist nicht nur nicht Teil von
     ANSI  (also  ein Fehler), zudem wuerde es die Rechengeschwindigkeit
     des  PPC  aufgrund von Nichteinhaltung von Alignment-Spielregeln in
     die KNIE zwingen.  Nicht benutzen !!!
     
     6. "K&R Syntax => Trashcan"
     
     Jedes Auftreten der K&R Syntax wie
     
     void main(argc,argv)
     int argc;
     char **argv;
     
     muss durch normale Syntax wie
     
     void main(int argc, char **argv)
     
     ersetzt werden.
     
     7. "Die Textkonstanten"
     
     Eine Formulierung wie:
     
     char bla[]={"..."};
     
     mag StormC nicht. Bitte umformulieren in
     
     char *bla="..."; (ohne geschweifte Klammern)
     
     8. Spezialitaeten
     
     U.U. muss man einige Defines setzen:
     
     #define __stdargs
     #define __regargs
     #define __asm
     #define __far FAR
     #define __inline inline
     #define __volatile volatile
     
     Es sei zudem erwaehnt, dass es nach ANSI nicht moeglich ist, inline
     und  static  zu  kombinieren (man muss sich fuer eins entscheiden),
     inline   static   ergibt   eh  keinen  Sinn  (aber  geuebte  GNU  C
     Programmierer  setzen  es  hin und wieder ein, einfach static draus
     machen...), auch heisst es
     
     volatile int i;
     
     und nicht
     
     int volatile i;
     
     __chip, __fast und __interrupt gibt es bei StormC nicht. Hier sei auf die 
     entsprechenden Betriebssystemsroutinen verwiesen.
     
     9. TCP/IP
     
     Wer  StormC-kompatible  Includes hierzu braucht, bitte Mail an mich
     schreiben     (MagicSN@Birdland.es.bawue.de),    ich    habe    die
     AmiTCP-Includes angepasst (was nicht allzu schwer ist).
     
     10. "Ein heisser Tip zum Schluss"
     
     Wer     Probleme     mit     Typecasts     hat    (besonders    bei
     Funktionspointern...), verwende folgendes Verfahren:
     
     1. Compilieren
     2. Alles, was nach Pointer aussieht auf void * casten, alles Andere auf 
        int, long, float oder double casten.
     
     Nun ist es fast geschafft.  Es fehlen nur noch die PPC-Anpassungen.
     
     1. Includes
     
     Normale 68k Compiler machen fuer OS-Includes:
     
      #include  
      #include 
     
      oder
     
      #include 
      #include 
     
      oder
     
      #include 
      #include 
     
      oder
      
      #include 
     
      Fuer PPC Code bitte:
     
      #include 
      
      Fuer  Code,  der  sich  fuer  sowohl  PPC  als  auch 68k mit StormC
      uebersetzen lassen soll:
     
      #include 
      #ifndef __PPC__
      #include 
      #endif   
     
      __PPC__ wird automatisch vom Compiler korrekt gesetzt. Man braucht also 
      lediglich zwei Projektfiles. Ueberhaupt laesst sich der meiste halbwegs 
      vernuenftig programmierte Source mit zwei Projektfiles und ein paar 
      Aenderungen in den Includes bereits uebersetzen.
     
     2. Tasks
     
     PPC-Tasks   erschafft   man   mittels   CreateTaskPPC()   aus   der
     powerpc.library,  nicht  Anders  !!!   Auch  andere Task-Funktionen
     stehen  zur  Verfuegung.   Die  API  ist (nahezu) identisch wie bei
     Exec.   Ich  verweise hier auf die Autodocs der powerpc.library (im
     WarpUP Paket enthalten).
     
     3. Tags
     
     Es  sei  darauf  hingewiesen, dass aeltere Beta-Versionen scheinbar
     Probleme mit der Schreibweise
     
      OpenWindowTags()
     
     hatten  (im  Gegensatz  zu  OpenWindowTagList()).  In der kaeuflich
     erwerbbaren  Version  von  StormC PPC funktioniert das jedoch.  Ich
     hab mir dennoch angewoehnt, immer ueber ...TagList() zu gehen.
     
     4. BeginIO()
     
     Anbei  Ersatz-Code fuer BeginIO (der auf dem PPC-Compiler nicht zur
     Verfuegung steht):
     
      #include 
      #include 
     
      void BeginIOAudioPPC(struct IORequest *arg1)
      {
              extern struct Library *AudioBase;
              ULONG regs[16];
              regs[9] = (ULONG) arg1;
              __CallLibrary(AudioBase,-30,regs);
      }
      
     Ein Beispiel, wie das eingesetzt wird:
     
        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
      
     (Man muss die LibBase immer definieren !!!)
     
     Zum   Abschluss  meines  Kurses  kommen  wir  noch  auf  das  Thema
     Kontextswitches  zu  sprechen.  Einige haben sich sicher gewundert,
     wann das endlich kommt.  Die grosse Ueberraschung:  Bei WarpUP muss
     man  das  (zumindest mit StormC) nicht selber machen.  Der Compiler
     erledigt das von selbst !!!
     
     Es gibt zwei Arten von Kontextswitches:
     
     a) "Funktions-Kontextswitches"
     
     Sehen  bei  StormC  wie  normale Funktionsaufrufe aus.  Sind nur in
     Mixed Binaries zu finden (siehe in einen spaetereen Kursteil).  Man
     muss  sie  in  keinster  Weise  "vorbereiten", einfach die Funktion
     aufrufen,  dass  die  nun  auf  einem anderen Prozessor ausgefuehrt
     wird,  interessiert  ja  den  Programmierer  nicht,  das ist ja nur
     Technik :)
     
     b) "Library-Kontextswitches"
     
     Sehen  aus wie normale Library-Aufrufe.  Dass die auf einem anderen
     Prozessor ausgefuehrt werden, ist wiederum nur Technik.
     
     Eine  Kleinigkeit muss dennoch gesagt werden.  Es muss ein PPC-Stub
     existieren.   Dieser  existiert  bereits  in ppcamiga.lib fuer alle
     OS-Librareis  und  fuer  rtgmaster.library  (die 68k Funktionen von
     rtgmaster,  die  PPC-Funktionen  brauchen ja keinen Kontextswitch).
     Falls  dieser fuer eine exotische Library nicht existiert, kann man
     ihn aus dem protos- und dem FD-File mittels
     
     genppcstub mylib_protos.h mylib.fd VERBOSE
     
     generieren (is dann ein C-Source, den man dazulinkt, oder eine .lib
     draus bastelt, mit dem StormLibrarian).
     
     Fuer Mixed Binaries gilt: Der erste Compiliervorgang muss mit 
     DEBUGGINGINFORMATION durchgefuehrt werden (gilt nur fuers erste Compilieren). 
     Naehere Infos foglen im Kursteil ueber Mixed Binaries.
     
     Nun  ist  der  Kontextswitch bei WarpUP ja sehr schnell (0.5 ms auf
     ner  200 MHz Karte, waehrend er auf ppc.library etwa 1 ms braucht).
     Dennoch sollte man auf "minimale Kontextswitches" optimieren, z.B.:
     
     - Files niemals mit fgetc/fputc bearbeiten, IMMER fread/fwrite nehmen, wenn
       irgend moeglich (und dann auf einem Buffer im Fastram arbeiten)
     - NIEMALS WritePixel nehmen, immer auf einem Buffer arbeiten, und den dann
       mittels CopyRtgBlit oder WritePixelArray8 darstellen
     
     (Fortsetzung folgt:) )
               

Zurück zum Inhalt